針對RecyclerView的優化整理

整理了RecyclerView可以優化的點,並不是需要都使用,需根據具體情況分析。

瞭解RecyclerView緩存機制後,可以說RecyclerView性能優化的本質就是針對onCreateViewHolderonBindViewHolder的優化,總結之後分爲以下幾類。


1.減少onCreateViewHolder調用次數

1.1 兩個數據源大部分相似時使用swapAdapter代替setAdapter

rv的setadapter大家都會使用,沒什麼好說的,但關於swapadapter可能就有些人不太知道了,這兩個方法最大的不同之處就在於setadapter會直接清空rv上的所有緩存,而swapadapter會將rv上的holder保存到pool中,google提供swapadapter方法考慮到的一個應用場景應該是兩個數據源有很大的相似部分的情況下,直接使用setadapter重置的話會導致原本可以被複用的holder全部被清空,而使用swapadapter來代替setadapter可以充分利用rv的緩存機制,可以說是一種更爲明智的選擇。

1.2 共用回收池

對於一個頁面中的多個RecyclerView,如果使用同一個Adapter,可以使用setRecycledViewPool(pool),共用回收池,
避免來每一個RecyclerView都創建一個回收池,特別是RecyclerView嵌套RecyclerView時候,內部的RecyclerView必定使用的都是同一個Adapter,這個時候就很有必要使用回收池了

1.3 增加RecycledViewPool緩存數量

每個類型默認緩存5個

此方法是拿空間換時間,要充分考慮應用內存問題,根據應用實際使用情況設置大小。


2.減少onCreateViewHolder執行時間

2.1 減少item的過度繪製

減少佈局層級,儘量少的佈局嵌套,儘量少的控件

2.2 Prefetch預取

如果你使用的是RecyclerView默認的佈局管理器,你自動的就得到了這些優化。但是如果你使用嵌套的RecyclerView,或者你自己寫LayoutManager,則需要自己實現Prefetch,重寫collectAdjacentPrefetchPositions

https://juejin.im/entry/58a3f4f62f301e0069908d8f

https://blog.csdn.net/crazy_everyday_xrp/article/details/70344638


3.減少onBindViewHolder調用次數

3.1 使用局部刷新

可以用一下一些方法,替代notifyDataSetChanged,達到局部刷新的目的。notifyDataSetChanged會觸發所有item的detached回調再觸發onAttached回調。

notifyItemChanged(int position)
notifyItemInserted(int position)
notifyItemRemoved(int position)
notifyItemMoved(int fromPosition, int toPosition) 
notifyItemRangeChanged(int positionStart, int itemCount)
notifyItemRangeInserted(int positionStart, int itemCount) 
notifyItemRangeRemoved(int positionStart, int itemCount) 

如果必須用 notifyDataSetChanged(),那麼最好設置 mAdapter.setHasStableIds(true),並重寫getItemId()來給每個Item一個唯一的ID:

 @Override
    public long getItemId(int position) {
        return mDatas.get(position).hashCode();
    }

這樣,當我們刷新數據時,RecyclerView就能確認是否數據沒有變化,ViewHolder也直接複用,減少重新佈局的煩惱。但這個使用的前提是數據的id一定是唯一的。如果id不變,但數據發生變化,可能就不會刷新了。

3.2 使用DiffUtil去局部刷新數據

採用android Support 包下的DiffUtil工具類,它主要是爲了配合 RecyclerView 使用,通過比對新、舊兩個數據集的差異,生成舊數據到新數據的最小變動,然後對有變動的數據項,進行局部刷新。

https://www.cnblogs.com/plokmju/p/7385136.html

https://zhuanlan.zhihu.com/p/26079803

3.3 視情況使用setItemViewCacheSize(size)來加大RecyclerView緩存數目,用空間換取時間提高流暢度

RecyclerView可以設置自己所需要的ViewHolder緩存數量,默認大小是2。如果對於可能來回滑動的RecyclerView,把CacheViews的緩存數量設置大一些,可以省去bindView的時間,加快佈局顯示。

此方法是拿空間換時間,要充分考慮應用內存問題,根據應用實際使用情況設置大小。


4.減少onBindViewHolder執行時間

4.1 數據處理與視圖綁定分離

onBindViewHolder這個方法是綁定數據,並且是在UI線程,如果在該方法進行耗時操作,將會影響滑動的流暢性。

4.2 有大量圖片時,滾動停止加載圖片,停止後再去加載圖片

https://www.jianshu.com/p/a8621f917407

4.3 使用setHasFixedSize避免requestLayout

如果item的高度固定的話可以設置setHasFixedSize(true),這樣RecyclerView在onMeasure階段可以直接計算出高度,不需要多次計算子ItemView的高度。

setHasFixedSize(true)時如果是通過Adapter的增刪改插方法去刷新RecyclerView,那麼將不需要requestLayout()。如果是通過notifyDataSetChanged()刷新界面,還是會重新調用requestLayout()

https://www.jianshu.com/p/79c9c70f6502

4.4 不要在onBindViewHolder中設置點擊事件

onBindViewHolder中設置點擊事件會導致快速滑動時重複創建很多對象,可以採取複用OnClickListener對象,然後在onBindViewHolder()方法中通過setTag(position) 和getTag() 的方式,來傳遞點擊事件的position給listener。

public class TestAdapter extends RecyclerView.Adapter implements View.OnClickListener{
   ...
   @Override
   public void onBindViewHolder(final Holder holder, final int position) {
       holder.itemView.setOnClickListener(this);
       holder.itemView.setTag(position);
       ...
   }
   @Override
   public void onClick(View v) {
       int position = (Integer) v.getTag();
       Log.d("onClick", "testBtn" + String.valueOf(position));
   }
}

在ViewHolder中設置方案:https://blog.csdn.net/qq_24956515/article/details/80985773

5.其他

5.1 對於RecyclerView,如果不需要動畫,就把item動畫取消

默認在開啓item動畫的情況下會使rv額外處理很多的邏輯判斷,notify的增刪改操作都會對應相應的item動畫效果,所以如果你的應用不需要這些動畫效果的話可以直接關閉掉,這樣可以在處理增刪改操作時大大簡化rv的內部邏輯處理。可以通過 ((SimpleItemAnimator) rv.getItemAnimator()).setSupportsChangeAnimations(false); 把默認動畫關閉。

5.2 使用getExtraLayoutSpace爲LayoutManager設置更多的預留空間

在RecyclerView的元素比較高,一屏只能顯示一個元素的時候,第一次滑動到第二個元素會卡頓。

RecyclerView (以及其他基於adapter的view,比如ListView、GridView等)使用了緩存機制重用子 view(即系統只將屏幕可見範圍之內的元素保存在內存中,在滾動的時候不斷的重用這些內存中已經存在的view,而不是新建view)。

這個機制會導致一個問題,啓動應用之後,在屏幕可見範圍內,如果只有一張卡片可見,當滾動的時 候,RecyclerView找不到可以重用的view了,它將創建一個新的,因此在滑動到第二個feed的時候就會有一定的延時,但是第二個feed之 後的滾動是流暢的,因爲這個時候RecyclerView已經有能重用的view了。

如何解決這個問題呢,其實只需重寫getExtraLayoutSpace()方法。根據官方文檔的描述 getExtraLayoutSpace將返回LayoutManager應該預留的額外空間(顯示範圍之外,應該額外緩存的空間)。

LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this) {
    @Override
    protected int getExtraLayoutSpace(RecyclerView.State state) {
        return 300;
    }
};

6.設計優化

6.1 優化解耦 RecyclerView.Adapter

我們在使用 RecyclerView 的時候,總會遇到多項 ItemType 的場景。隨着業務複雜度的增加,ItemType 會越變越多,導致代碼量越來越多,最終發展爲 “上帝類”,需要設計出一種模式,使得增刪改一種 ItemType 時的成本降到最低

https://puke3615.github.io/2018/08/26/Android-RecyclerView-Architecture-Design/

https://www.jianshu.com/p/1297d2e4d27a

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章