Android 性能優化之RecycleView的性能優化原則

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:

  1. 標籤:如果當前佈局和包含的佈局中都是豎直方向,那麼使用標籤可以去掉多餘的LinearLayout,一般和標籤一起使用,從而減少佈局的層級;
  2. 標籤:佈局重用,不用把已經寫過的佈局重新寫一遍,而且可以把同一個佈局中用到的重複佈局抽離出來,使用時用include,佈局重用是代碼複用的思想;
  3. 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) 來回收資源;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章