RecyclerView性能優化

概述

RecyclerView有着極高的靈活性,能實現ListView、GridView的所有功能。在日常開發中,使用非常廣泛,如果使用不當將會影響到應用的整體性能,所以有必要了解一下如何更高效的使用。

數據處理與視圖綁定分離

RecyclerView的 bindViewHolder方法是在UI線程進行的,如果在該方法進行耗時操作,將會影響滑動的流暢性。

優化前:

class Task {
    Date dateDue;
    String title;
    String description;

    // getters and setters here
}

class MyRecyclerView.Adapter extends RecyclerView.Adapter {

    static final TODAYS_DATE = new Date();
    static final DATE_FORMAT = new SimpleDateFormat("MM dd, yyyy");

    public onBindViewHolder(Task.ViewHolder tvh, int position) {
        Task task = getItem(position);

        if (TODAYS_DATE.compareTo(task.dateDue) > 0) {
            tvh.backgroundView.setColor(Color.GREEN);
        } else {
            tvh.backgroundView.setColor(Color.RED);
        }

        String dueDateFormatted = DATE_FORMAT.format(task.getDateDue());
        tvh.dateTextView.setDate(dueDateFormatted);
    }
}

上面的 onBindViewHolder方法中進行了日期的比較和日期的格式化,這個是很耗時的,在 onBindViewHolder方法中,應該只是將數據 set到視圖中,而不應進行業務的處理。

優化後:

public class TaskViewModel {
    int overdueColor;
    String dateDue;
}

public onBindViewHolder(Task.ViewHolder tvh, int position) {
    TaskViewModel taskViewModel = getItem(position);
    tvh.backgroundView.setColor(taskViewModel.getOverdueColor());
    tvh.dateTextView.setDate(taskViewModel.getDateDue());
}

數據優化

  1. 分頁加載遠端數據,對拉取的遠端數據進行緩存,提高二次加載速度;
  2. 對於新增或刪除數據通過 DiffUtil,來進行局部數據刷新,而不是一味的全局刷新數據。

DiffUtil是support包下新增的一個工具類,用來判斷新數據和舊數據的差別,從而進行局部刷新。

DiffUtil的使用,在原來調用 mAdapter.notifyDataSetChanged()的地方:

// mAdapter.notifyDataSetChanged()
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallBack(oldDatas, newDatas), true);
diffResult.dispatchUpdatesTo(mAdapter);

DiffUtil最終是調用Adapter的下面幾個方法來進行局部刷新:

mAdapter.notifyItemRangeInserted(position, count);
mAdapter.notifyItemRangeRemoved(position, count);
mAdapter.notifyItemMoved(fromPosition, toPosition);
mAdapter.notifyItemRangeChanged(position, count, payload);

佈局優化

減少過度繪製

減少佈局層級,可以考慮使用自定義View來減少層級,或者更合理的設置佈局來減少層級。

Note: 目前不推薦在RecyclerView中使用 ConstraintLayout,在ConstraintLayout1.1.2版中,性能還是表現不佳,後續的版本可能這個問題就解決了,需要持續關注。

減少xml文件inflate時間

xml文件包括:layout、drawable的xml,xml文件inflate出ItemView是通過耗時的IO操作。可以使用代碼去生成佈局,即 newView()的方式。這種方式是比較麻煩,但是在佈局太過複雜,或對性能要求比較高的時候可以使用。

減少View對象的創建

一個稍微複雜的 Item 會包含大量的 View,而大量的 View 的創建也會消耗大量時間,所以要儘可能簡化 ItemView;設計 ItemType 時,對多 ViewType 能夠共用的部分儘量設計成自定義 View,減少 View 的構造和嵌套。

設置高度固定

如果item高度是固定的話,可以使用 RecyclerView.setHasFixedSize(true);來避免requestLayout浪費資源。

共用RecycledViewPool

在嵌套RecyclerView中,如果子RecyclerView具有相同的adapter,那麼可以設置 RecyclerView.setRecycledViewPool(pool)來共用一個RecycledViewPool。

Note: 如果LayoutManager是LinearLayoutManager或其子類,需要手動開啓這個特性: layout.setRecycleChildrenOnDetach(true)

class OuterAdapter extends RecyclerView.Adapter<OuterAdapter.ViewHolder> {
    RecyclerView.RecycledViewPool mSharedPool = new RecyclerView.RecycledViewPool();

...

    @Override
    public OuterAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        RecyclerView innerLLM = new RecyclerView(inflater.getContext());

        LinearLayoutManager innerLLM = new LinearLayoutManager(parent.getContext(), LinearLayoutManager.HORIZONTAL);
        innerLLM.setRecycleChildrenOnDetach(true);
        innerRv.setLayoutManager(innerLLM);
        innerRv.setRecycledViewPool(mSharedPool);
        return new OuterAdapter.ViewHolder(innerRv);
    }

RecyclerView數據預取

RecyclerView25.1.0及以上版本增加了 Prefetch功能。 用於嵌套RecyclerView獲取最佳性能。 詳細分析:https://juejin.im/entry/58a3f4f62f301e0069908d8f

Note: 只適合橫向嵌套

// 在嵌套內部的LayoutManager中調用LinearLayoutManger的設置方法
// num的取值:如果列表剛剛展示4個半item,則設置爲5
innerLLM.setInitialItemsPrefetchCount(num);

加大RecyclerView的緩存

用空間換時間,來提高滾動的流暢性。

recyclerView.setItemViewCacheSize(20);
recyclerView.setDrawingCacheEnabled(true);
recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);

增加RecyclerView預留的額外空間

額外空間:顯示範圍之外,應該額外緩存的空間

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

減少ItemView監聽器的創建

對ItemView設置監聽器,不要對每個item都創建一個監聽器,而應該共用一個XxListener,然後根據 ID來進行不同的操作,優化了對象的頻繁創建帶來的資源消耗。

優化滑動操作

設置 RecyclerView.addOnScrollListener();來在滑動過程中停止加載的操作。

處理刷新閃爍

調用notifyDataSetChange時,適配器不知道整個數據集中的那些內容以及存在,再重新匹配ViewHolder時會花生閃爍。 設置adapter.setHasStableIds(true),並重寫getItemId()來給每個Item一個唯一的ID

回收資源

通過重寫 RecyclerView.onViewRecycled(holder)來回收資源。

參考鏈接

  1. https://blankj.com/2018/09/29/optimize-recycler-view/
  2. https://blog.csdn.net/a8688555/article/details/79634295
  3. https://stackoverflow.com/questions/27188536/recyclerview-scrolling-performance
  4. https://stackoverflow.com/questions/27993627/optimizing-recyclerview-listview
  5. http://www.cnblogs.com/ldq2016/p/9039979.html
  6. https://juejin.im/entry/58a3f4f62f301e0069908d8f
  7. https://blog.csdn.net/qq_25867141/article/details/52769332
  8. https://www.cnblogs.com/dasusu/p/9159904.html
  9. https://www.cnblogs.com/dasusu/p/9255335.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章