Android中ListView常見優化方案

我們設置或者優化ListView的性能很多時候都是在getView中完成的,反過來說就是很多性能問題都是由于沒有正確使用getView造成的。

public View getView(int position, View convertView, ViewGroup parent)

那么問題就來了。

在一次顯示ListView的界面時,getView會被執行幾次?

在繪制ListView前往往要計算它的高度,所以一個ListView界面上可以看到6個ItemView,但是getView的執行次數卻有可能是12次,多出的次數用來計算高度(這個可以通過設置ListView的height為0來避免)。所以要避免在getView中進行邏輯運算,兩次計算同一邏輯完全是浪費。

每次getView執行時間有多久?

1秒之內屏幕大概可以完成30幀的繪制,人才能看到它比較流暢,每幀可使用的時間:1000ms/30 = 33.33 ms,每個ListView一般要顯示6個ListItem,加上1個重用convertView:33.33ms/7 = 4.76ms,即是說,每個getView要在4.76ms內完成工作才會較流暢,但是事實上,每個getView間的調用也會有一定的間隔(有可能是由于handler在處理別的消息),UI的handler處理不好的話,這個間隔也可難會很大(0ms-200ms)。結論就是,留給getView使用的時間應該在4ms之內,如果不能控制在這之內的話,ListView的滑動就會有卡頓的現象。
(轉載:Android面試一天一題(11 Day) —— goeasyway

有哪些常見的優化方案?

1. 使用ConvertView

也是最普通的優化,就在MyAdapter類中的getView方法中,我們注意到,上面的寫法每次需要一個View對象時,都是去重新inflate一個View出來返回去,沒有實現View對象的復用,而實際上對于ListView而言,只需要保留能夠顯示的最大個數的view即可,其他新的view可以通過復用的方式使用消失的條目的view,而getView方法里也提供了一個參數:convertView,這個就代表著可以復用的view對象,當然這個對象也可能為空,當它為空的時候,表示該條目view第一次創建,所以我們需要inflate一個view出來,所以在這里,我們使用下面這種方式來重寫getView方法:

@Override  
public View getView(int position, View convertView, ViewGroup parent) {   
   View view;   
   // 判斷convertView的狀態,來達到復用效果   
   if (null == convertView) {    
       //如果convertView為空,則表示第一次顯示該條目,需要創建一個view    
       view = View.inflate(MainActivity.this, R.layout.listview_item,null);   
    } else {    
       //否則表示可以復用convertView    
       view = convertView;   
    }   
    // listview_item里只有一個textview   
    TextView tv_item = (TextView) view.findViewById(R.id.tv_item);     
    tv_item.setText(list.get(position));   
    return view;  
}
2. 使用View Holder模式

經過上面的優化之后,我們不需要每一個view都重新生成了。下面我們來解決下一個每一次都需要做的工作,那就是view中組件的查找:

TextView tv_item = (TextView) view.findViewById(R.id.tv_item);

實際上,findViewById是到xml文件中去查找對應的id,可以想象如果組件多的話也是挺費事的,如果我們可以讓view內的組件也隨著view的復用而復用,就能對ListView進行很好的優化

private static class ViewHolder {  
   private TextView tvHolder; 
}

@Override  
public View getView(int position, View convertView, ViewGroup parent) {   
   View view;   
   ViewHolder holder;   
   // 判斷convertView的狀態,來達到復用效果   
   if (null == convertView) {    
       // 如果convertView為空,則表示第一次顯示該條目,需要創建一個view    
       view = View.inflate(MainActivity.this, R.layout.listview_item, null);    
       //新建一個viewholder對象    
       holder = new ViewHolder();    
       //將findviewbyID的結果賦值給holder對應的成員變量    
       holder.tvHolder = (TextView) view.findViewById(R.id.tv_item);    
       // 將holder與view進行綁定    
       view.setTag(holder);   
    } else {    
       // 否則表示可以復用convertView    
       view = convertView;    
       holder = (ViewHolder) view.getTag();  
    }   
    // 直接操作holder中的成員變量即可,不需要每次都findViewById   
    holder.tvHolder.setText(list.get(position));   
    return view;  
}

ps:
這里的ViewHolder類需要不需要定義成static,根據實際情況而定,如果item不是很多的話,可以使用,這樣在初始化的時候,只加載一次,可以稍微得到一些優化。
不過,如果item過多的話,建議不要使用。因為static是Java中的一個關鍵字,當用它來修飾成員變量時,那么該變量就屬于該類,而不是該類的實例。所以用static修飾的變量,它的生命周期是很長的,如果用它來引用一些資源耗費過多的實例(比如Context的情況最多),這時就要盡量避免使用了。

3. 分批加載與分頁加載相結合

我們需要進行分批加載,比如說1000條新聞的List集合,我們一次加載20條,等到用戶翻頁到底部的時候,我們再添加下面的20條到List中,再使用Adapter刷新ListView,這樣用戶一次只需要等待20條數據的傳輸時間,不需要一次等待好幾分鐘把數據都加載完再在ListView上顯示。其次這樣也可以緩解很多條新聞一次加載進行產生OOM應用崩潰的情況。

實際上,分批加載也不能完全解決問題,因為雖然我們在分批中一次只增加20條數據到List集合中,然后再刷新到ListView中去,假如有10萬條數據,如果我們順利讀到最后這個List集合中還是會累積海量條數的數據,還是可能會造成OOM的情況,這時候我們就需要用到分頁,比如說我們將這10萬條數據分為1000頁,每一頁100條數據,每一頁加載時都覆蓋掉上一頁中List集合中的內容,然后每一頁內再使用分批加載,這樣用戶的體驗就會相對好一些。

除此之前還有一些優化建議:

  1. 使用異步線程加載圖片(一般都是直接使用圖片庫加載,如Glide, Picasso);
  1. 在adapter的getView方法中盡可能的減少邏輯判斷,特別是耗時的判斷;
  2. 避免GC(可以從LOGCAT查看有無GC的LOG);
  3. 在快速滑動時不要加載圖片;
  4. 將ListView的scrollingCache和animateCache這兩個屬性設置為false(默認是true);
  5. 盡可能減少List Item的Layout層次(如可以使用RelativeLayout替換LinearLayout,或使用自定的View代替組合嵌套使用的Layout);

(轉載:Android面試一天一題(11 Day) —— goeasyway

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容