新聞中心
在整理前幾篇文章的時候有朋友提出寫一下ListView的性能優(yōu)化方面的東西,這個問題也是小馬在面試過程中被別人問到的…..今天小馬就借此機會來整理下,網(wǎng)上類似的資料蠻多的,倒不如自己寫一篇,記錄在這個地方,供自己以后使用,不用再翻來翻去的找了,用自己寫的…呵呵,不多講其它了,說起優(yōu)化我想大家第一反應跟小馬一樣吧?想到利用ViewHolder來優(yōu)化ListView數(shù)據(jù)加載,僅僅就此一條嗎?其實不是的,首先,想要優(yōu)化ListView就得先了解ListView加載數(shù)據(jù)原理,這是前提,但是小馬在這個地方先做一些簡單的補充,大家一定仔細看下,保證會有收獲的:

成都創(chuàng)新互聯(lián)公司專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務,包含不限于網(wǎng)站設計、成都網(wǎng)站制作、萊州網(wǎng)絡推廣、成都小程序開發(fā)、萊州網(wǎng)絡營銷、萊州企業(yè)策劃、萊州品牌公關(guān)、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運營等,從售前售中售后,我們都將竭誠為您服務,您的肯定,是我們最大的嘉獎;成都創(chuàng)新互聯(lián)公司為所有大學生創(chuàng)業(yè)者提供萊州建站搭建服務,24小時服務熱線:18980820575,官方網(wǎng)址:www.cdcxhl.com
列表的顯示需要三個元素:
-
ListVeiw: 用來展示列表的View。
-
適配器 : 用來把數(shù)據(jù)映射到ListView上
-
數(shù)據(jù): 具體的將被映射的字符串,圖片,或者基本組件。
根據(jù)列表的適配器類型,列表分為三種,ArrayAdapter,SimpleAdapter和SimpleCursorAdapter,這三種適配器的使用大家可學習下官網(wǎng)上面的使用或者自行百度谷歌,一堆DEMO!?。∑渲幸訟rrayAdapter最為簡單,只能展示一行字。SimpleAdapter有最好的擴充性,可以自定義出各種效果。SimpleCursorAdapter可以認為是SimpleAdapter對數(shù)據(jù)庫的簡單結(jié)合,可以方便的把數(shù)據(jù)庫的內(nèi)容以列表的形式展示出來。
系統(tǒng)要繪制ListView了,他首先用getCount()函數(shù)得到要繪制的這個列表的長度,然后開始繪制第一行,怎么繪制呢?調(diào)用getView()函數(shù)。在這個函數(shù)里面首先獲得一個View(這個看實際情況,如果是一個簡單的顯示則是View,如果是一個自定義的里面包含很多控件的時候它其實是一個ViewGroup),然后再實例化并設置各個組件及其數(shù)據(jù)內(nèi)容并顯示它。好了,繪制完這一行了。那 再繪制下一行,直到繪完為止,前面這些東西做下鋪墊,繼續(xù)…….
現(xiàn)在我們再來了解ListView加載數(shù)據(jù)的原理,有了這方面的了解后再說優(yōu)化才行,下面先跟大家一起來看下ListView加載數(shù)據(jù)的基本原理小馬就直接寫了:
ListView的工作原理如下:
ListView 針對每個item,要求 adapter “返回一個視圖” (getView),也就是說ListView在開始繪制的時候,系統(tǒng)首先調(diào)用getCount()函數(shù),根據(jù)他的返回值得到ListView的長度,然后根據(jù)這個長度,調(diào)用getView()一行一行的繪制ListView的每一項。如果你的getCount()返回值是0的話,列表一行都不會顯示,如果返回1,就只顯示一行。返回幾則顯示幾行。如果我們有幾千幾萬甚至更多的item要顯示怎么辦?為每個Item創(chuàng)建一個新的View?不可能?。。嶋H上Android早已經(jīng)緩存了這些視圖,大家可以看下下面這個截圖來理解下,這個圖是解釋ListView工作原理的最經(jīng)典的圖了大家可以收藏下,不懂的時候拿來看看,加深理解,其實Android中有個叫做Recycler的構(gòu)件,順帶列舉下與Recycler相關(guān)的已經(jīng)由Google做過N多優(yōu)化過的東東比如:AbsListView.RecyclerListener、ViewDebug.RecyclerTraceType等等,要了解的朋友自己查下,不難理解,下圖是ListView加載數(shù)據(jù)的工作原理(原理圖看不清楚的點擊后看大圖):
下面簡單說下上圖的原理:
- 如果你有幾千幾萬甚至更多的選項(item)時,其中只有可見的項目存在內(nèi)存(內(nèi)存內(nèi)存哦,說的優(yōu)化就是說在內(nèi)存中的優(yōu)化?。。。┲?,其他的在Recycler中
- ListView先請求一個type1視圖(getView)然后請求其他可見的項目。convertView在getView中是空(null)的
- 當item1滾出屏幕,并且一個新的項目從屏幕低端上來時,ListView再請求一個type1視圖。convertView此時不是空值了,它的值是item1。你只需設定新的數(shù)據(jù)然后返回convertView,不必重新創(chuàng)建一個視圖
- 下面來看下小馬從網(wǎng)上找來的示例代碼,網(wǎng)址搞丟了,只有一個word文檔,只能 copy過來,不然直接貼網(wǎng)址,結(jié)合上面的原理圖一起加深理解,如下:
- public class MultipleItemsList extends ListActivity {
- private MyCustomAdapter mAdapter;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mAdapter = new MyCustomAdapter();
- for (int i = 0; i < 50; i++) {
- mAdapter.addItem("item " + i);
- }
- setListAdapter(mAdapter);
- }
- private class MyCustomAdapter extends BaseAdapter {
- private ArrayList mData = new ArrayList();
- private LayoutInflater mInflater;
- public MyCustomAdapter() {
- mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- }
- public void addItem(final String item) {
- mData.add(item);
- notifyDataSetChanged();
- }
- @Override
- public int getCount() {
- return mData.size();
- }
- @Override
- public String getItem(int position) {
- return mData.get(position);
- }
- @Override
- public long getItemId(int position) {
- return position;
- }
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- System.out.println("getView " + position + " " + convertView);
- ViewHolder holder = null;
- if (convertView == null) {
- convertView = mInflater.inflate(R.layout.item1, null);
- holder = new ViewHolder();
- holder.textView = (TextView)convertView.findViewById(R.id.text);
- convertView.setTag(holder);
- } else {
- holder = (ViewHolder)convertView.getTag();
- }
- holder.textView.setText(mData.get(position));
- return convertView;
- } }
- public static class ViewHolder {
- public TextView textView;
- } }
執(zhí)行程序,查看日志:
getView 被調(diào)用 9 次 ,convertView 對于所有的可見項目是空值(如下):
然后稍微向下滾動List,直到item10出現(xiàn):
convertView仍然是空值,因為recycler中沒有視圖(item1的邊緣仍然可見,在頂端)再滾動列表,繼續(xù)滾動:
convertView不是空值了!item1離開屏幕到Recycler中去了,然后item11被創(chuàng)建,再滾動下:
此時的convertView非空了,在item11離開屏幕之后,它的視圖(…0f8)作為convertView容納item12了,好啦,結(jié)合以上原理,下面來看看今天最主要的話題,主角ListView的優(yōu)化:
首先,這個地方先記兩個ListView優(yōu)化的一個小點:
1. ExpandableListView 與 ListActivity 由官方提供的,里面要使用到的ListView是已經(jīng)經(jīng)過優(yōu)化的ListView,如果大家的需求可以用Google自帶的ListView滿足的的話盡量用官方的,絕對沒錯!
2.其次,像小馬前面講的,說ListView優(yōu)化,其實并不是指其它的優(yōu)化,就是內(nèi)存是的優(yōu)化,提到內(nèi)存…(想到OOM,折騰了我不少時間),很多很多,先來寫下,如果我們的ListView中的選項僅僅是一些簡單的TextView的話,就好辦啦,消耗不了多少的,但如果你的Item是自定義的Item的話,例如你的自定義Item布局ViewGroup中包含:按鈕、圖片、flash、CheckBox、RadioButton等一系列你能想到的控件的話, 你要在getView中單單使用文章開頭提到的ViewHolder是遠遠不夠的,如果數(shù)據(jù)過多,加載的圖片過多過大,你BitmapFactory.decode的猛多的話,OOM搞死你,這個地方再警告下大家,是警告……….也提醒下自己:
小馬碰到的問題大家應該也都碰到過的,自定義的ListView項亂序問題,我很天真的在getView()中強制清除了下ListView的緩存數(shù)據(jù)convertView,也就是convertView = null了,雖然當時是解決了這個問題讓其它每次重繪,但是犯了大錯了,如果數(shù)據(jù)太多的話,出現(xiàn)最最惡心的錯,手機卡死或強制關(guān)機,關(guān)機啊哥哥們……O_O,客戶殺了我都有可能,但大家以后別犯這樣的錯了,單單使用清除緩存convertView是解決不了實際問題的,繼續(xù)……
下面是小記:圖片用完了正確的釋放…
- if(!bmp.isRecycle() ){
- bmp.recycle() //回收圖片所占的內(nèi)存
- system.gc() //提醒系統(tǒng)及時回收
- }
下面來列舉下真正意義上的優(yōu)化吧:
- ViewHolder Tag 必不可少,這個不多說!
- 如果自定義Item中有涉及到圖片等等的,一定要狠狠的處理圖片,圖片占的內(nèi)存是ListView項中最惡心的,處理圖片的方法大致有以下幾種:
2.1:不要直接拿個路徑就去循環(huán)decodeFile();這是找死….用Option保存圖片大小、不要加載圖片到內(nèi)存去;
2.2: 拿到的圖片一定要經(jīng)過邊界壓縮
2.3:在ListView中取圖片時也不要直接拿個路徑去取圖片,而是以WeakReference(使用WeakReference代替強引用。比如可以使 用WeakReference mContextRef)、SoftReference、WeakHashMap等的來存儲圖片信息,是圖片信息不是圖片哦!
2.4:在getView中做圖片轉(zhuǎn)換時,產(chǎn)生的中間變量一定及時釋放,用以下形式: - 盡量避免在BaseAdapter中使用static 來定義全局靜態(tài)變量,我以為這個沒影響 ,這個影響很大,static是Java中的一個關(guān)鍵字,當用它來修飾成員變量時,那么該變量就屬于該類,而不是該類的實例。所以用static修飾的變量,它的生命周期是很長的,如果用它來引用一些資源耗費過多的實例(比如Context的情況最多),這時就要盡量避免使用了..
- 如果為了滿足需求下必須使用Context的話:Context盡量使用Application Context,因為Application的Context的生命周期比較長,引用它不會出現(xiàn)內(nèi)存泄露的問題
- 盡量避免在ListView適配器中使用線程,因為線程產(chǎn)生內(nèi)存泄露的主要原因在于線程生命周期的不可控制
- 記下小馬自己的錯誤:
- 之前使用的自定義ListView中適配數(shù)據(jù)時使用AsyncTask自行開啟線程的,這個比用Thread更危險,因為Thread只有在run函數(shù)不 結(jié)束時才出現(xiàn)這種內(nèi)存泄露問題,然而AsyncTask內(nèi)部的實現(xiàn)機制是運用了線程執(zhí)行池(ThreadPoolExcutor,要想了解這個類的話大家加下我們的Android開發(fā)群五號,因為其它群的存儲空間快滿了,所以只上傳到五群里了,看下小馬上傳的Gallery源碼,你會對線程執(zhí)行池、軟、弱、強引用有個更深入的認識),這個類產(chǎn)生的Thread對象的生命周期是不確定的,是應用程序無法控制的,因此如果AsyncTask作為Activity的內(nèi)部類,就更容易出現(xiàn)內(nèi)存泄露的問題。這個問題的解決辦法小馬當時網(wǎng)上查到了記在txt里了,如下:
6.1:將線程的內(nèi)部類,改為靜態(tài)內(nèi)部類。
6.2:在線程內(nèi)部采用弱引用保存Context引用
示例代碼如下:
- public abstract class WeakAsyncTask extends AsyncTask {
- protected WeakReference mTarget;
- public WeakAsyncTask(WeakTarget target) {
- mTarget = new WeakReference(target);
- }
- @Override
- protected final void onPreExecute() {
- final WeakTarget target = mTarget.get();
- if (target != null) {
- this.onPreExecute(target);
- }
- }
- @Override
- protected final Result doInBackground(Params... params) {
- final WeakTarget target = mTarget.get();
- if (target != null) {
- return this.doInBackground(target, params);
- } else {
- return null;
- }
- }
- @Override
- protected final void onPostExecute(Result result) {
- final WeakTarget target = mTarget.get();
- if (target != null) {
- this.onPostExecute(target, result);
- }
- }
- protected void onPreExecute(WeakTarget target) {
- // No default action }
- protected abstract Result doInBackground(WeakTarget target, Params... params);
- protected void onPostExecute(WeakTarget target, Result result) {
- // No default action } }
好啦,ListVIew的優(yōu)化問題,小馬就暫時先理解記錄這么多了,如果朋友們有什么更好的優(yōu)化建議什么的,留言指點下小馬,一定會及時添加到進來的,先謝謝啦,其實在ListView適配器的getView()方法中可以做很多的優(yōu)化,我記得還有可以優(yōu)化findViewById()這個方法來尋址資源信息效率的方法,資料太多了,小馬發(fā)現(xiàn)了會及時更新的哦,天太晚了,先休息了,吼吼,大家加油,一起努力學習!??!O_O
分享文章:Android之ListView原理學習與優(yōu)化總結(jié)
URL鏈接:http://m.5511xx.com/article/coihjdi.html


咨詢
建站咨詢
