Android ListView组件的优化

对于Android ListView的优化,我从遇到问题,到解决问题来简介。首先我先写一个简单的ListView的例子具体代码如下。大概说一下ListView的工作原理。其实很简单,就是mvc,其中m指的是数据,v指的是ListView,c指的是adapter。一句话就是通过adapter将数据显示到ListView.


1 MainActivity

package com.listviewtestdemo;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    ListView listView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //查找对应的空间
        listView = (ListView) findViewById(R.id.lv);

        //设置关联
        listView.setAdapter(new MyAdapter());
    }

    //创建适配器
    private class MyAdapter extends BaseAdapter{

        @Override
        public int getCount() {
            return 1000000000;//注意次数
        }

        @Override
        public Object getItem(int i) {
            return null;
        }

        @Override
        public long getItemId(int i) {
            return 0;
        }

        @Override
        public View getView(int i, View view, ViewGroup viewGroup) {
            TextView textView = new TextView(getApplicationContext());
            textView.setText("Item " + i);
            Log.d(TAG,"Item " + i);
            return textView;
        }
    }
}


2 activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.listviewtestdemo.MainActivity">

    <ListView
        android:id="@+id/lv"
        android:fastScrollEnabled="true"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</RelativeLayout>


3 ListView中的Item对象复用

3.1 问题描述

从上面的代码很简单了,就是在相对布局中增加了一个ListView组件,然后在主方法中增加了内部类适配器MyAdapter类,继承自BaseAdapter类。下面我要说的是第一个问题,当时上面的代码被运行之后,由于我们的次数非常大(1000000000).所以此示例代码布局以后就会显示1000000000个Item,如果我们慢慢的往下拉动屏幕,程序不会有问题,但是如果我们非常快速的不间断的拖动屏幕上的Item,那么这个程序将会发生崩溃,LOG中的原因是OOM(内存溢出)。


3.2 问题原因


内存溢出的原因是,每次我们调用getView都会new一个新的TextView对象,所以当我们非常快速的拖动屏幕的时候,短时间内会出现大量的TextView对象,那些不在屏幕上显示的TextView对象在这么短的时间内还没有来的及被垃圾回收机制回收,就导致所有TextView对象所占的空间累加之后,大于了DVM所分配给我们APP的内存空间,这样就发生了OOM,引起了APP crash。


3.3 解决办法


使用getView中的view(早期版本变量名是convertView)进行TextView对象的复用。怎么理解呢?

电梯的例子,电梯是一级一级的台阶,总的台阶数量是一定的,假如说总的台阶有20级台阶,其中电梯斜面上的台阶数最多10级台阶就能铺满电梯斜面,还有10级台阶在看不见的电梯斜面下边。我们就假定每级台阶上都会占有一个人,所以10级台阶可以一次性运送10个人,当第1级台阶上个人到达电梯顶端之后,同时电梯底部第11级台阶出来,然后上来第11个人,接着第2级台阶上的人到达电梯顶部之后,同时第12级台阶出来,然后上来第12个人,依次类推,总的台阶级数没有发生变化,但是却运送了大量的人。这就是典型的复用


其中电梯的级数是一定,而且从来没有增加或者减少,这就像是我们创建的TextView对象的个数,假如说我们屏幕一下子能显示20个Item,那么就相当于一下子创建了20个TextView对象,然后载着不同的数据显示在屏幕上,当第0个数据不再在屏幕上显示的时候,这第0个的TextView对象将会被convertView暂时保存,此时第21个数据就会显示出来,但是由于convertView已经保存有第0个的TextView对象,所以第21个再显示的时候就不会再创建新的TextView对象,而是将convertView中的第0个的TextView对象的数据换成第21个的数据,然后显示到屏幕上。


总的来说就是同一个人穿上了不同的衣服出门,一个人每次出门虽然穿的衣服不同,但是还是那个人,只不过穿了不同的衣服。这不同的衣服就是不同的数据,赤裸裸的人就是那个TextView对象载体。


3.4 代码的修改


保持activity_main.xml不变,将MainActivity代码修改如下

package com.listviewtestdemo;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    ListView listView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //查找对应的空间
        listView = (ListView) findViewById(R.id.lv);

        //设置关联
        listView.setAdapter(new MyAdapter());
    }

    //创建适配器
    private class MyAdapter extends BaseAdapter{

        @Override
        public int getCount() {
            return 1000000000;//注意次数
        }

        @Override
        public Object getItem(int i) {
            return null;
        }

        @Override
        public long getItemId(int i) {
            return 0;
        }

        @Override
        public View getView(int i, View view, ViewGroup viewGroup) {
            TextView textView;
            if (view == null) {
                textView = new TextView(getApplicationContext());
                Log.d(TAG,"New Item " + i);
            }else{
                textView = (TextView) view;
                Log.d(TAG,"Old Item " + i);
            }
            textView.setText("Item " + i);
            Log.d(TAG,"Item " + i);
            return textView;
        }
    }
}

其他位置代码都没有发生变化,只是修改了getView方法中的代码。这样就保证了不是每次都创建新的TextView对象。而是复用之前消失在屏幕上的TextView对象。


4 减少无用的执行次数

4.1问题描述

为了复现这个问题我修改了布局文件activity_main.xml,将ListView组件中的layout_height由match_parent改为wrap_content。然后为了减小运行的次数,我将getCount方法中的次数修改为7次(此数随便定,只要不是太大就可以,最好是10以内的)。代码如下


activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.listviewtestdemo.MainActivity">

    <ListView
        android:id="@+id/lv"
        android:fastScrollEnabled="true"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</RelativeLayout>

MainActivity

package com.listviewtestdemo;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    ListView listView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //查找对应的空间
        listView = (ListView) findViewById(R.id.lv);

        //设置关联
        listView.setAdapter(new MyAdapter());
    }

    //创建适配器
    private class MyAdapter extends BaseAdapter{

        @Override
        public int getCount() {
            return 7;//注意次数
        }

        @Override
        public Object getItem(int i) {
            return null;
        }

        @Override
        public long getItemId(int i) {
            return 0;
        }

        @Override
        public View getView(int i, View view, ViewGroup viewGroup) {
            TextView textView;
            if (view == null) {
                textView = new TextView(getApplicationContext());
                Log.d(TAG,"New Item " + i);
            }else{
                textView = (TextView) view;
                Log.d(TAG,"Old Item " + i);
            }
            textView.setText("Item " + i);
            Log.d(TAG,"Item " + i);
            return textView;
        }
    }
}

然后看两套代码运行时所产生的Log.首先看一下未修改 activity_main前的代码运行Log,当然getCount次数

都为7。


getCount是7所以调用了7次getView方法。


接下来看一下将layout_height由match_parent改为wrap_content后所产生的Log

10-31 14:29:25.247 11784-11801/com.listviewtestdemo I/KPI-6PA-AMS-6: 79930823 enter ActivityThread.scheduleLaunchActivity() ActivityInfo{5b25628 com.listviewtestdemo.MainActivity}
10-31 14:29:25.434 11784-11784/com.listviewtestdemo I/KPI-6PA-AT-5: 79931009 enter ApplicationThread.handleLaunchActivity() : ActivityInfo{5b25628 com.listviewtestdemo.MainActivity}
10-31 14:29:25.739 11784-11784/com.listviewtestdemo I/KPI-6PA-AT-6: 79931315 leave ApplicationThread.handleLaunchActivity() :ActivityInfo{5b25628 com.listviewtestdemo.MainActivity}
10-31 14:29:25.761 11784-11784/com.listviewtestdemo D/MainActivity: New Item 0
10-31 14:29:25.761 11784-11784/com.listviewtestdemo D/MainActivity: Item 0
10-31 14:29:25.763 11784-11784/com.listviewtestdemo D/MainActivity: Old Item 1
10-31 14:29:25.763 11784-11784/com.listviewtestdemo D/MainActivity: Item 1
10-31 14:29:25.764 11784-11784/com.listviewtestdemo D/MainActivity: Old Item 2
10-31 14:29:25.764 11784-11784/com.listviewtestdemo D/MainActivity: Item 2
10-31 14:29:25.765 11784-11784/com.listviewtestdemo D/MainActivity: Old Item 3
10-31 14:29:25.765 11784-11784/com.listviewtestdemo D/MainActivity: Item 3
10-31 14:29:25.766 11784-11784/com.listviewtestdemo D/MainActivity: Old Item 4
10-31 14:29:25.766 11784-11784/com.listviewtestdemo D/MainActivity: Item 4
10-31 14:29:25.767 11784-11784/com.listviewtestdemo D/MainActivity: Old Item 5
10-31 14:29:25.767 11784-11784/com.listviewtestdemo D/MainActivity: Item 5
10-31 14:29:25.768 11784-11784/com.listviewtestdemo D/MainActivity: Old Item 6
10-31 14:29:25.768 11784-11784/com.listviewtestdemo D/MainActivity: Item 6
10-31 14:29:25.769 11784-11784/com.listviewtestdemo D/MainActivity: Old Item 0
10-31 14:29:25.769 11784-11784/com.listviewtestdemo D/MainActivity: Item 0
10-31 14:29:25.770 11784-11784/com.listviewtestdemo D/MainActivity: Old Item 1
10-31 14:29:25.770 11784-11784/com.listviewtestdemo D/MainActivity: Item 1
10-31 14:29:25.770 11784-11784/com.listviewtestdemo D/MainActivity: Old Item 2
10-31 14:29:25.771 11784-11784/com.listviewtestdemo D/MainActivity: Item 2
10-31 14:29:25.771 11784-11784/com.listviewtestdemo D/MainActivity: Old Item 3
10-31 14:29:25.771 11784-11784/com.listviewtestdemo D/MainActivity: Item 3
10-31 14:29:25.772 11784-11784/com.listviewtestdemo D/MainActivity: Old Item 4
10-31 14:29:25.772 11784-11784/com.listviewtestdemo D/MainActivity: Item 4
10-31 14:29:25.772 11784-11784/com.listviewtestdemo D/MainActivity: Old Item 5
10-31 14:29:25.773 11784-11784/com.listviewtestdemo D/MainActivity: Item 5
10-31 14:29:25.773 11784-11784/com.listviewtestdemo D/MainActivity: Old Item 6
10-31 14:29:25.773 11784-11784/com.listviewtestdemo D/MainActivity: Item 6
10-31 14:29:25.806 11784-11784/com.listviewtestdemo D/MainActivity: Old Item 0
10-31 14:29:25.807 11784-11784/com.listviewtestdemo D/MainActivity: Item 0
10-31 14:29:25.808 11784-11784/com.listviewtestdemo D/MainActivity: Old Item 1
10-31 14:29:25.808 11784-11784/com.listviewtestdemo D/MainActivity: Item 1
10-31 14:29:25.809 11784-11784/com.listviewtestdemo D/MainActivity: Old Item 2
10-31 14:29:25.810 11784-11784/com.listviewtestdemo D/MainActivity: Item 2
10-31 14:29:25.811 11784-11784/com.listviewtestdemo D/MainActivity: Old Item 3
10-31 14:29:25.811 11784-11784/com.listviewtestdemo D/MainActivity: Item 3
10-31 14:29:25.812 11784-11784/com.listviewtestdemo D/MainActivity: Old Item 4
10-31 14:29:25.812 11784-11784/com.listviewtestdemo D/MainActivity: Item 4
10-31 14:29:25.813 11784-11784/com.listviewtestdemo D/MainActivity: Old Item 5
10-31 14:29:25.813 11784-11784/com.listviewtestdemo D/MainActivity: Item 5
10-31 14:29:25.815 11784-11784/com.listviewtestdemo D/MainActivity: Old Item 6
10-31 14:29:25.815 11784-11784/com.listviewtestdemo D/MainActivity: Item 6
10-31 14:29:25.817 11784-11784/com.listviewtestdemo D/MainActivity: Old Item 0
10-31 14:29:25.817 11784-11784/com.listviewtestdemo D/MainActivity: Item 0
10-31 14:29:25.819 11784-11784/com.listviewtestdemo D/MainActivity: Old Item 1
10-31 14:29:25.819 11784-11784/com.listviewtestdemo D/MainActivity: Item 1
10-31 14:29:25.820 11784-11784/com.listviewtestdemo D/MainActivity: Old Item 2
10-31 14:29:25.820 11784-11784/com.listviewtestdemo D/MainActivity: Item 2
10-31 14:29:25.822 11784-11784/com.listviewtestdemo D/MainActivity: Old Item 3
10-31 14:29:25.822 11784-11784/com.listviewtestdemo D/MainActivity: Item 3
10-31 14:29:25.823 11784-11784/com.listviewtestdemo D/MainActivity: Old Item 4
10-31 14:29:25.824 11784-11784/com.listviewtestdemo D/MainActivity: Item 4
10-31 14:29:25.825 11784-11784/com.listviewtestdemo D/MainActivity: Old Item 5
10-31 14:29:25.825 11784-11784/com.listviewtestdemo D/MainActivity: Item 5
10-31 14:29:25.826 11784-11784/com.listviewtestdemo D/MainActivity: Old Item 6
10-31 14:29:25.827 11784-11784/com.listviewtestdemo D/MainActivity: Item 6
10-31 14:29:25.835 11784-11784/com.listviewtestdemo D/MainActivity: Old Item 0
10-31 14:29:25.835 11784-11784/com.listviewtestdemo D/MainActivity: Item 0
10-31 14:29:25.839 11784-11784/com.listviewtestdemo D/MainActivity: New Item 1
10-31 14:29:25.839 11784-11784/com.listviewtestdemo D/MainActivity: Item 1
10-31 14:29:25.843 11784-11784/com.listviewtestdemo D/MainActivity: New Item 2
10-31 14:29:25.843 11784-11784/com.listviewtestdemo D/MainActivity: Item 2
10-31 14:29:25.847 11784-11784/com.listviewtestdemo D/MainActivity: New Item 3
10-31 14:29:25.848 11784-11784/com.listviewtestdemo D/MainActivity: Item 3
10-31 14:29:25.851 11784-11784/com.listviewtestdemo D/MainActivity: New Item 4
10-31 14:29:25.851 11784-11784/com.listviewtestdemo D/MainActivity: Item 4
10-31 14:29:25.855 11784-11784/com.listviewtestdemo D/MainActivity: New Item 5
10-31 14:29:25.856 11784-11784/com.listviewtestdemo D/MainActivity: Item 5
10-31 14:29:25.859 11784-11784/com.listviewtestdemo D/MainActivity: New Item 6
10-31 14:29:25.859 11784-11784/com.listviewtestdemo D/MainActivity: Item 6

getCount的次数为7,但是getView被调用了7*(x+1)次,其中x为执行的次数。


4.2 问题原因

先说使用match_parent的时候,假设我们的屏幕分辨率是320*480的,假设我们一个Item的高度是50,那么由于我们用的是match_parent所以系统很清楚的知道我一个屏幕可以显示480/50 约等于10个。所以系统一下就计算出我们的7个是完全可以一屏显示完,所以系统只执行一次。也就出现了执行7次getView的Log。


但是如果将match_parent改为wrap_content,由于系统不清楚我们现在一个高度是多少,所以就进行多次执行getView进行确认是否显示完全,这就导致了7*(x+1)次getView的Log出现。


所以当我们使用ListView的时候请不要使用wrap_content,也是对ListView的优化。





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值