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的優化。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章