• android-----带你一步一步优化ListView(一)


       ListView作为android中最常使用的控件,可以以条目的形式显示大量的数据,经常被用于显示最近联系人列表,对于每一个 Item,均要求adapter的getView方法返回一个View,因此ListView的实现是离不开Adapter的,如果以MVC的思想来看ListView的话,ListView的显示相当于V,Adapter部分相当于C,而数据部分就相当于M了,接下来的几篇博客计划对ListView自己所了解的一些优化措施总结一下,希望能够帮助到大家;

            先来看看如果我们不使用任何优化措施的话,使用ListView的方法:

            这里先补充下获得LayoutInflater的三种方法:

            (1)如果是在Activity中的话,可以调用

                LayoutInflater inflater = getLayoutInflater();

            (2)如果不是在Activity中,则可以将context上下文作为参数,通过

                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

            (3)如果不是在Activity中,则可以将context上下文作为参数,通过

                LayoutInflater inflater = LayoutInflater.from(context);

            首先定义Activity界面布局listview.xml,很简单,里面就只有一个ListView

    1.  
      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    2.  
      xmlns:tools="http://schemas.android.com/tools"
    3.  
      android:layout_width="match_parent"
    4.  
      android:layout_height="match_parent"
    5.  
      tools:context=".MainActivity" >
    6.  
      <ListView
    7.  
      android:id="@+id/listView"
    8.  
      android:layout_width="match_parent"
    9.  
      android:layout_height="match_parent" />
    10.  
      </LinearLayout>

            再定义ListView的每个item的布局item.xml,也很简单,只有一个TextView

    1.  
      <?xml version="1.0" encoding="utf-8"?>
    2.  
      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    3.  
      android:layout_width="match_parent"
    4.  
      android:layout_height="match_parent"
    5.  
      android:orientation="vertical" >
    6.  
      <TextView
    7.  
      android:id="@+id/textView"
    8.  
      android:layout_width="wrap_content"
    9.  
      android:layout_height="50dp"/>
    10.  
      </LinearLayout>

            接下来定义一个Adapter,继承自BaseAdapter,用来为ListView填充数据

    1.  
      public class ListViewAdapter extends BaseAdapter{
    2.  
       
    3.  
      List<String> list = new ArrayList<String>();
    4.  
      LayoutInflater inflater = null;
    5.  
       
    6.  
      public ListViewAdapter(List<String> list,Context context) {
    7.  
      this.list = list;
    8.  
      inflater = LayoutInflater.from(context);
    9.  
      }
    10.  
      @Override
    11.  
      public int getCount() {
    12.  
      return list.size();
    13.  
      }
    14.  
       
    15.  
      @Override
    16.  
      public Object getItem(int position) {
    17.  
      return list.get(position);
    18.  
      }
    19.  
       
    20.  
      @Override
    21.  
      public long getItemId(int position) {
    22.  
      return position;
    23.  
      }
    24.  
       
    25.  
      @Override
    26.  
      public View getView(int position, View convertView, ViewGroup parent) {
    27.  
      View view = null;
    28.  
      System.out.println("before: "+list.get(position)+"------- "+convertView);
    29.  
      view = inflater.inflate(R.layout.item, null);
    30.  
      System.out.println("after: "+list.get(position)+"------- "+view);
    31.  
      TextView textView = (TextView) view.findViewById(R.id.textView);
    32.  
      textView.setText(list.get(position));
    33.  
      return view;
    34.  
      }
    35.  
      }

           可以发现,我们在getView里并没有做任何的优化,待会我们看看这样的方式会出什么问题;

            定义Activity,将Adapter绑定到ListView上面

    1.  
      public class MainActivity extends Activity {
    2.  
       
    3.  
      List<String> list = new ArrayList<String>();
    4.  
      ListView listView = null;
    5.  
      @Override
    6.  
      protected void onCreate(Bundle savedInstanceState) {
    7.  
      super.onCreate(savedInstanceState);
    8.  
      setContentView(R.layout.listview);
    9.  
      listView = (ListView) findViewById(R.id.listView);
    10.  
      for(int i = 1;i < 50;i++)
    11.  
      {
    12.  
      list.add("item "+i);
    13.  
      }
    14.  
      ListViewAdapter adapter = new ListViewAdapter(list, this);
    15.  
      listView.setAdapter(adapter);
    16.  
      }
    17.  
      }

            很简单,我们模拟了有50个条目的ListView,并且通过ListViewAdapter的构造函数将其传递给Adapter用于在界面展示这些数据;

            运行效果图:

                                                 

            对应的Logcat输出:      

                                   

            可以发现初始状态有10个item显示在界面上,并且他们的before view均是null的,调用inflate方法之后生成了新的view,因此after view非空了;

            接着我们向上拖动屏幕,可以在Logcat看到有如下输出:

            

             可以发现此时的item 19和item 20在before之前convertView得地址都是@4181a590,这个view是已经划出屏幕的item 9的,这点可以从刚开始第一屏幕的输出看出来,因为item 9已经被加入到了RecycleBin缓存中了,所以在调用getView方法的时候convertView获取到的是缓存中的随机一个view,item 19和item 20完全有可能获取到同一个view,但是他们的after view是不可能出现相同的,也就是说我们这个例子中的after view的地址值是不可能相同的,因为我们模拟了50个item,所以会有50个view被放到RecycleBin缓存中,也许现在对于我们显示来说没什么,那么如果有大量的信息需要显示的话,直接就会报内存的;

             通过上面我们会发现一点,在调用getView方法的时候,他的第二个参数可能不会是null的,原因就是RecycleBin缓存帮我们暂存了那些划出屏幕的view,所以我们在convertView非空的情况下我们也没什么必要重新调用inflate方法加载布局了,因为这个方法毕竟也是要解析xml文件的,至少是要花时间的,直接使用从缓存中取出的view即可啦,先来看看ListView提供的RecycleBin缓存图解:

               

    图片来自于:https://hit-alibaba.github.io/interview/Android/basic/ListView-Optimize.html

             从上面的图上可以看到当item 1被划出屏幕之后,会被放到Recycle缓存中,当item 8要划入屏幕的时候,如果他和item 1的类型相同的话,则直接从Recycle中获得即可,即此时的convertView不再是null;如果他和item 1类型不一致的话,则会新建view视图,即此时convertView等于null;

            好的,那我们接下来就该充分使用android提供给我们的RecycleBin机制来优化ListView了;

            只需要修改ListViewAdapter类的getView方法即可了:

    1.  
      @Override
    2.  
      public View getView(int position, View convertView, ViewGroup parent) {
    3.  
      View view = null;
    4.  
      System.out.println("before: "+list.get(position)+"------- "+convertView);
    5.  
      if(convertView == null)
    6.  
      {
    7.  
      view = inflater.inflate(R.layout.item, null);
    8.  
      }else
    9.  
      view = convertView;
    10.  
      TextView textView = (TextView) view.findViewById(R.id.textView);
    11.  
      textView.setText(list.get(position));
    12.  
      return view;
    13.  
      }

            不滑动屏幕的时候,程序输出:

                                              

              接着我们滑动屏幕,查看输出:

              

             注意红色部分,发现没经过11个item,都会复用之前的view,这也就是说我们的RecycleBin缓存中将只有11个view了,不像前面那样有50个view,这在很大程度上节约了内存,想想如果有上千万条数据需要显示,每个数据条目都有一个view在RecycleBin中是一件多么可怕的事情,进行了convertView是否为null的判断之后,将只会缓存一屏幕的view,当然有可能会多那么几个吧;

             上面我们通过判断convertView是否为空对ListView进行了优化,接下来我们看看getView方法,里面在获取TextView的时候,我们使用了findViewById方法,这个方法是与IO有关的操作,想必也会影响性能吧,他只要的目的是获得某一个view的布局罢了,我们如果有了view的话,其实只需要第一次将该view和其布局绑定到一起就可以了,没必要每次都为view设置布局了,这也就是使用setTag的目的了;

             这里也仅仅只是对ListViewAdapter的geyView方法进行修改即可:

    1.  
      @Override
    2.  
      public View getView(int position, View convertView, ViewGroup parent) {
    3.  
      View view = null;
    4.  
      ViewHolder viewHolder = null;
    5.  
      System.out.println("before: "+list.get(position)+"------- "+convertView);
    6.  
      if(convertView == null)
    7.  
      {
    8.  
      view = inflater.inflate(R.layout.item, null);
    9.  
      viewHolder = new ViewHolder();
    10.  
      viewHolder.textView = (TextView) view.findViewById(R.id.textView);
    11.  
      view.setTag(viewHolder);
    12.  
      }else
    13.  
      {
    14.  
      view = convertView;
    15.  
      viewHolder = (ViewHolder) view.getTag();
    16.  
      }
    17.  
      viewHolder.textView.setText("item "+list.get(position));
    18.  
      return view;
    19.  
      }
    20.  
      static class ViewHolder
    21.  
      {
    22.  
      TextView textView;
    23.  
      }

            这里采用静态内部类的方式用于定义item 的各个控件 ,如果convertView非空的话,表示该view对应的布局已经存在了,只需要调用getTag获取到即可了,如果convertView为空的话,则需要通过findViewById来获取这个布局中的控件,并且最后将该布局通过setTag设置到view上面即可;

            程序的输出结果和上面是一样的,这里不再列出;

            我们平常的实际应用中,每个条目有可能不都是一样的,这种情况下会出现不同的项目布局,那么这时候该怎么办呢?同样我们通过实例来学习一下这时候的RecycleBin机制是怎么实现缓存的;

            在此,我们增加一个只显示一个按钮的条目,布局文件button.xml

    1.  
      <?xml version="1.0" encoding="utf-8"?>
    2.  
      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    3.  
      android:layout_width="match_parent"
    4.  
      android:layout_height="match_parent"
    5.  
      android:orientation="vertical" >
    6.  
      <Button
    7.  
      android:id="@+id/button"
    8.  
      android:layout_width="wrap_content"
    9.  
      android:layout_height="wrap_content"
    10.  
      />
    11.  
      </LinearLayout>

            修改ListViewAdapter类如下:

    1.  
      public class ListViewAdapter extends BaseAdapter{
    2.  
       
    3.  
      List<String> list = new ArrayList<String>();
    4.  
      LayoutInflater inflater = null;
    5.  
       
    6.  
      public ListViewAdapter(List<String> list,Context context) {
    7.  
      this.list = list;
    8.  
      inflater = LayoutInflater.from(context);
    9.  
      }
    10.  
      @Override
    11.  
      public int getItemViewType(int position) {
    12.  
      //0表示显示的是TextView,1表示显示的是Button
    13.  
      if(position % 4 != 0)
    14.  
      return 0;
    15.  
      else
    16.  
      return 1;
    17.  
      }
    18.  
      @Override
    19.  
      public int getViewTypeCount() {
    20.  
      //表示有两种类型的item布局
    21.  
      return 2;
    22.  
      }
    23.  
      @Override
    24.  
      public int getCount() {
    25.  
      return list.size();
    26.  
      }
    27.  
       
    28.  
      @Override
    29.  
      public Object getItem(int position) {
    30.  
      return list.get(position);
    31.  
      }
    32.  
       
    33.  
      @Override
    34.  
      public long getItemId(int position) {
    35.  
      return position;
    36.  
      }
    37.  
       
    38.  
      @Override
    39.  
      public View getView(int position, View convertView, ViewGroup parent) {
    40.  
      View view = null;
    41.  
      TextViewHolder textViewHolder = null;
    42.  
      ButtonViewHolder buttonViewHolder = null;
    43.  
      System.out.println("before: "+list.get(position)+"------- "+convertView);
    44.  
      int type = getItemViewType(position);
    45.  
      if(convertView == null)
    46.  
      {
    47.  
      switch (type) {
    48.  
      case 0:
    49.  
      view = inflater.inflate(R.layout.item, null);
    50.  
      textViewHolder = new TextViewHolder();
    51.  
      textViewHolder.textView = (TextView) view.findViewById(R.id.textView);
    52.  
      view.setTag(textViewHolder);
    53.  
      textViewHolder.textView.setText(list.get(position));
    54.  
      break;
    55.  
      case 1:
    56.  
      view = inflater.inflate(R.layout.button, null);
    57.  
      buttonViewHolder = new ButtonViewHolder();
    58.  
      buttonViewHolder.button = (Button) view.findViewById(R.id.button);
    59.  
      view.setTag(buttonViewHolder);
    60.  
      buttonViewHolder.button.setText("button "+list.get(position));
    61.  
      break;
    62.  
      default:
    63.  
      break;
    64.  
      }
    65.  
      }else
    66.  
      {
    67.  
      view = convertView;
    68.  
      switch (type) {
    69.  
      case 0:
    70.  
      textViewHolder = (TextViewHolder) view.getTag();
    71.  
      textViewHolder.textView.setText(list.get(position));
    72.  
      break;
    73.  
      case 1:
    74.  
      buttonViewHolder = (ButtonViewHolder) view.getTag();
    75.  
      buttonViewHolder.button.setText("button "+list.get(position));
    76.  
      break;
    77.  
      default:
    78.  
      break;
    79.  
      }
    80.  
      }
    81.  
      return view;
    82.  
      }
    83.  
      static class TextViewHolder
    84.  
      {
    85.  
      TextView textView;
    86.  
      }
    87.  
      static class ButtonViewHolder
    88.  
      {
    89.  
      Button button;
    90.  
      }
    91.  
      }

            通过getViewTypeCount()返回的是你到底有多少种布局,我们这里有两种

            通过getItemViewType(int)返回的是根据你的position得到的对应布局的ID,当然这个ID是可以由你来定的;

            修改Activity类

    1.  
      public class MainActivity extends Activity {
    2.  
       
    3.  
      List<String> list = new ArrayList<String>();
    4.  
      ListView listView = null;
    5.  
      @Override
    6.  
      protected void onCreate(Bundle savedInstanceState) {
    7.  
      super.onCreate(savedInstanceState);
    8.  
      setContentView(R.layout.listview);
    9.  
      listView = (ListView) findViewById(R.id.listView);
    10.  
      for(int i = 1;i < 50;i++)
    11.  
      {
    12.  
      if(i % 4 == 0)
    13.  
      {
    14.  
      list.add("button "+i);
    15.  
      }else
    16.  
      list.add("item "+i);
    17.  
      }
    18.  
      ListViewAdapter adapter = new ListViewAdapter(list, this);
    19.  
      listView.setAdapter(adapter);
    20.  
      }
    21.  
      }

            界面运行效果图为:

     

            可以发现每隔3个条目我们都会加载另一个不同的条目,接着查看Logcat输出:

     

            在我们滑动屏幕之后,输出结果为:

            注意图中突出显示部分,可以发现对于button item,我们也得到了复用,当然这个复用顺序并不一定是按顺序来的,因为RecycleBin机制只会把你已经滑出屏幕的item缓存下来,但是在从缓存中取得时候,并不一定就是按你存进去的顺序取出来的,这点要注意啦,到此一般的ListView优化测试结束了,我们来总结一下:

            (1)通过复用view的方式来充分利用android系统本身自带的RecycleBin缓存机制,能够保证即使有再多的item实际中也仅仅会有有限多个item,大大节省内存;

            (2)使用静态内部类以及setTag方式,将view与其对应的控件绑定起来,避免了每次得到view以后都需要通过findViewById的方式来获取控件;

            (3)对于有多种布局的ListView来说,我们可以通过getViewTypeCount()获得布局的种类,通过getItemViewType(int)获得当前位置上的布局到底是属于哪一类;

            好了,这篇先介绍到这里,接下来的一篇将重点介绍ListView加载图片方面的优化措施;

    --------------------- 本文来自 她说巷尾的樱花开了 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/hzw19920329/article/details/51383864?utm_source=copy 

  • 相关阅读:
    当物联网遇上云原生:K8s向边缘计算渗透中
    NLP预训练发展小结一(Bert之前)
    netty系列之:搭建HTTP上传文件服务器
    netty系列之:搭建自己的下载文件服务器
    HTTP系列之:HTTP中的cookies
    HTTP系列之:HTTP缓存
    netty系列之:自建客户端和HTTP服务器交互
    [源码解析] 深度学习流水线并行 PipeDream(3)--- 转换模型
    [源码解析] 深度学习流水线并行 PipeDream(2)--- 计算分区
    [源码解析] 深度学习流水线并行之PipeDream(1)--- Profile阶段
  • 原文地址:https://www.cnblogs.com/Im-Victor/p/6272974.html
Copyright © 2020-2023  润新知