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