Android 性能優化之RecycleView的性能優化
一、概述
RecyclerView有着極高的靈活性,能實現ListView、GridView的所有功能,也能輕鬆實現ListView、GridView不易實現的功能,如多 Type 佈局列表。在日常開發中,RecyclerView使用非常廣泛,如果使用不當將會出現閃爍、卡頓、佔用內存過高等問題,影響應用性能,也會影響用戶體驗,所以有必要了解一下RecyclerView的性能優化方法。
二、RecycleView的性能優化原則
1.數據處理和視圖加載分離
數據處理的一些耗時邏輯可以考慮放在異步裏面處理,這樣Adapter在notify change後,ViewHolder就可以簡單無壓力的做數據與視圖的綁定邏輯。比如:
mTextView.setText(Html.fromHtml(data).toString());
這裏的 Html.fromHtml(data) 方法可能就是比較耗時的,存在多個 TextView 的話耗時會更爲嚴重,這樣便會引發掉幀、卡頓,而如果把這一步與網絡異步線程放在一起,站在用戶角度,最多就是網絡刷新時間稍長一點。
2.數據優化
- 分頁拉取網絡數據
- 同時對拉取下來的數據進行緩存,提升再次加載速度;
- 對於新增或刪除等數據變化情況通過DiffUtil(DiffUtil是support-v7:24.2.0中的新工具類,它用來比較兩個數據集,尋找出舊數據集-》新數據集的最小變化量。 DiffUtil會自動計算新老數據集的差異,並根據差異情況,自動調用RecycleView的adapter的四個方法更新數據)來進行局部刷新數據,DiffUtil刷新數據時會有動畫效果,而且比全局刷新效率高。
使用DiffUtil,代碼如下:
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallBack(mDatas, newDatas), true);
diffResult.dispatchUpdatesTo(mAdapter);
DiffUtil會自動計算新老數據集的差異,並根據差異情況,自動調用以下四個方法:
adapter.notifyItemRangeInserted(position, count);
adapter.notifyItemRangeRemoved(position, count);
adapter.notifyItemMoved(fromPosition, toPosition);
adapter.notifyItemRangeChanged(position, count, payload);
3.recyclerView.setHasFixedSize(true);
當RecyclerView的Item的高度固定時,設置這個選項可以提高性能,器原理就是要避免整個佈局繪製,即避免requestLayout。
4.佈局優化
減少佈局層級,防止出現過度繪製,如可以考慮用ConstraintLayout或者自定義View替代ItemView。用 ConstraintLayout 可以最大程度減少層級。
佈局優化的另一種手段是採用標籤、標籤和ViewStub:
- 標籤:如果當前佈局和包含的佈局中都是豎直方向,那麼使用標籤可以去掉多餘的LinearLayout,一般和標籤一起使用,從而減少佈局的層級;
- 標籤:佈局重用,不用把已經寫過的佈局重新寫一遍,而且可以把同一個佈局中用到的重複佈局抽離出來,使用時用include,佈局重用是代碼複用的思想;
- ViewStub:繼承view,非常輕量級且寬高都是0,自己不參加任何佈局的繪製過程,提供了按需加載功能,當需要時纔將ViewStub中的佈局加載到內存,這提高了程序的初始化效率。
5.減少xml文件inflate時間
這裏的 xml 文件不僅包括 layout 的 xml,還包括 drawable 的 xml,xml 文件 inflate 出 ItemView 是通過耗時的 IO 操作,尤其當 Item 的複用機率很低的情況下,隨着 Type 的增多,這種 inflate 帶來的損耗是相當大的,此時我們可以用代碼去生成佈局,即 new View() 的方式,只要搞清楚 xml 中每個節點的屬性對應的 API 即可。
6.減少View對象的創建
一個稍微複雜的 Item 會包含大量的 View,而大量的 View 的創建也會消耗大量時間,所以要儘可能簡化 ItemView;設計 ItemType 時,對多 ViewType 能夠共用的部分儘量設計成自定義 View,減少 View 的構造和嵌套。
7.用RecycledViewPool複用
如果多個RecycledView的 item 相同,比如可以滑動的tab頁面,如果每個頁面的 ViewHolder 都是一樣的,就可以用共享一個對象池RecycledViewPool,代碼如下:
RecyclerView.RecycledViewPool sharedPool = new RecyclerView.RecycledViewPool();
RecyclerView.setRecycledViewPool(sharedPool);
RecycledViewPool是依據ItemViewType來索引ViewHolder的,所以不同頁面的相同的item的type必須是一樣的值才能被準確的複用。
RecycledViewPool的核心思想是多個RecycledView共用 ViewHolder,多個tab切換時,可以減少後面RecyclerView調用onCreateViewHolder的次數。爲了防止多個tab快速切換時出現的卡頓問題,可以考慮提前創建ViewHolder,用空間換時間,在創建ViewHolder的時候就可以直接從池裏獲取,而不用再去 createViewHolder。參考代碼如下:
RecyclerView.RecycledViewPool sharedPool = new RecyclerView.RecycledViewPool();
MyAdapter myAdapter = new MyAdapter(datas);
RecyclerView.ViewHolder viewHolder = myAdapter.createViewHolder(recyclerView, 0);//先把 sharedPool 和 ViewHolder 創建出來
pool.putRecycledView(viewHolder);
//下面是真正創建recyclerView
recyclerView.setRecycledViewPool(sharedPool);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(myAdapter);
8.可選項:升級 RecycleView 版本到 25.1.0 及以上使用 RecyclerView 的預取 Prefetch 功能
Prefetch 功能對視圖複雜度比較高的情形,改善效果比較明顯,可通過GPU呈現模式分析-打開的GPU渲染條形圖進行對比測試。
如果你使用 RecyclerView 提供的默認 layout manager,你將自動獲得這種優化。然而,如果你使用嵌套 RecyclerView 或者自己寫 layout manager,你需要改變你的代碼來利用這個特性。
對於嵌套 RecyclerView 而言,要獲取最佳的性能,在內部的 LayoutManager 中調用 LinearLayoutManager 的 setInitialItemPrefetchCount()方法(25.1版本起可用)。例如,如果你豎直方向的list至少展示三個條目,調用 setInitialItemPrefetchCount(4)。
如果你實現了自己的 LayoutManager,你需要重寫 LayoutManager.collectAdjacentPrefetchPositions()方法。該方法在數據預取開啓時被 RecyclerView 調用(LayoutManager 的默認實現什麼都不做)。第二,在嵌套的內層 RecyclerView 中,如果你想讓你的 LayoutManager 預取數據,你同樣應當實現 LayoutManager.collectInitialPrefetchPositions()。
9.其他方面
- 通過設置setItemViewCacheSize增加RecyclerView的緩存,用空間換時間提高滑動列表時的流暢性;
- 對 ItemView 設置監聽器,不要對每個 Item 都調用 addXxListener,應該共用一個 XxListener,根據 ID 來進行不同的操作,減少了對象頻繁創建帶來的資源消耗;
- 通過 getExtraLayoutSpace 來增加 RecyclerView 預留的額外空間(顯示範圍之外,應該額外緩存的空間);
- 設置 RecyclerView.addOnScrollListener(listener); 來對滑動過程中停止加載的操作;
- 如果不要求動畫,可以通過 ((SimpleItemAnimator) rv.getItemAnimator()).setSupportsChangeAnimations(false); 把默認動畫關閉來提升效率;
- 通過重寫 RecyclerView.onViewRecycled(holder) 來回收資源;