RecyclerView 優(yōu)化

一晃听、了解 RecyclerView

五虎上將

RecyclerView.LayoutManager

類型 作用
RecyclerView.LayoutManager 負責(zé)Item視圖的布局的顯示管理
RecyclerView.ItemDecoration 給每一項Item添加子View,例如可以進行畫分割線之類
RecyclerView.ItemAnimator 負責(zé)處理數(shù)據(jù)添加或刪除時的動畫效果
RecyclerView.Adapter 為每一項Item創(chuàng)建視圖
RecyclerView.ViewHolder 承載Item視圖的子布局
RecyclerView.Recycler 負責(zé)處理View的緩存

調(diào)用關(guān)系

調(diào)用關(guān)系

二、緩存 ViewHolder

緩存機制

RecyclerView緩存機制

緩存流程

  • scrap --> cache --> ViewCacheExtension --> RecycledViewPool --> 創(chuàng)建新的ViewHolder
  • 滑出屏幕表項對應(yīng)的ViewHolder會被回收到mCachedViews+mRecyclerPool結(jié)構(gòu)中介却,mCachedViews是ArrayList谴供,默認存儲最多2個ViewHolder,當它放不下的時候齿坷,按照先進先出原則將最先進入的ViewHolder存入回收池的方式來騰出空間桂肌。mRecyclerPool是SparseArray,它會按viewType分類存儲ViewHolder永淌,默認每種類型最多存5個崎场。
緩存流程1
緩存流程2

總結(jié)

Scrap

說明 結(jié)果
優(yōu)先級 一級
重新創(chuàng)建ViewHolder false
重新綁定數(shù)據(jù) false
容量 無大小限制,但最多包含屏幕可見表項
用途 用于布局過程中屏幕可見表項的回收和復(fù)用
類型 ArrayList<ViewHolder>
  • mAttachedScrap : 緩存顯示在屏幕上的itemViewHolder
    • 按照idposition來查找ViewHolder
  • mChangedScrap : 緩存發(fā)生變化的itemViewHolder(notifXXX)
    • 按照idposition來查找ViewHolder

Cached

說明 結(jié)果
優(yōu)先級 二級
重新創(chuàng)建ViewHolder false
重新綁定數(shù)據(jù) false
容量 默認大小限制為2
用途 用于移出屏幕表項的回收和復(fù)用遂蛀,且只能用于指定位置的表項谭跨,有點像“回收池預(yù)備隊列”
類型 ArrayList<ViewHolder>
  • mCachedViews : 滑動過程中的回收和復(fù)用都是先處理的這個List
    • 只能復(fù)用于指定位置
    • 最大儲存mViewCacheMax = mRequestedCacheMax + extraCacheextraCache是由prefetch的時候計算出來的)
    • 用于解決RecyclerView滑動抖動時的情況,還有用于保存Prefetch

ViewCacheExtension

說明 結(jié)果
優(yōu)先級 二級
重新創(chuàng)建ViewHolder /
重新綁定數(shù)據(jù) /
容量 /
用途 開發(fā)者可自定義的一層緩存
類型 /
  • mViewCacheExtension : 開發(fā)者可自定義的一層緩存李滴,是虛擬類ViewCacheExtension的一個實例螃宙,開發(fā)者可實現(xiàn)方法getViewForPositionAndType(Recycler recycler, int position, int type)來實現(xiàn)自己的緩存

Pool : 回收池

說明 結(jié)果
優(yōu)先級 三級
重新創(chuàng)建ViewHolder false
重新綁定數(shù)據(jù) true
容量 默認每種類型最多存5個
用途 用于移出屏幕表項的回收和復(fù)用,且只能用于指定viewType的表項
類型 ViewHolder按類型分類存儲在SparseArray<ScrapData>中所坯,同類ViewHolder存儲在ScrapData中的ArrayList
  • mRecyclerPool : 在有限的mCachedViews放不下的時候谆扎,按先進先出原則將最先進入的ViewHolder存入mRecyclerPool

三、優(yōu)化

Ⅰ芹助、基礎(chǔ)優(yōu)化

1.onBindViewHolder

  • 1.不要在onBind設(shè)置監(jiān)聽堂湖。因為mRecyclerPoolViewHolder時要重新調(diào)用onBindonClickListener應(yīng)設(shè)置在ViewHolder状土。
/*=========== Adapter ===========*/     
...
public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) {
    ...
    // 把item對應(yīng)的實體類傳給itemView苗缩,方便點擊事件發(fā)生時得到對應(yīng)的資源。
    viewHolder.itemView.setTag(i);
}  

...
class ViewHolder extends RecyclerView.ViewHolder {
    ...
    private LinearLayout mItem;

    ViewHolder(@NonNull final View itemView) {
        ...
        mItem = itemView.findViewById(R.id.ll_item);

        // onClickListener 不要在 onBind 的時候設(shè)置声诸,因為 mRecyclerPool 取 ViewHolder 時要重新調(diào)用 onBind
        mItem.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (null != mItemClick) {
                    // 設(shè)置點擊監(jiān)聽
                    mItemClick.onItemClick(v, (int) itemView.getTag());
                }
            }
        });
    }
    ...
}

// Item監(jiān)聽
private OnRvItemClick mItemClick;

// 設(shè)置監(jiān)聽
public void setItemClick(OnRvItemClick mItemClick) {
    this.mItemClick = mItemClick;
}

public interface OnRvItemClick {
    // 點擊項事件
    void onItemClick(View v, int position);
}

/*=========== Other ===========*/ 
// 設(shè)置點擊項監(jiān)聽事件
Adapter.setItemClick(new OnRvItemClick() {
    @Override
    public void onItemClick(View v, int data) {
        mAdapter.getItem(data);
    }
});
  • 2.不要在onBindViewHolder做邏輯判斷和計算
    bindViewHolder方法是在UI線程進行的酱讶,如果在該方法進行耗時操作,將會影響滑動的流暢性彼乌。

2.設(shè)置高度固定

  • 如果RecyclerView固定寬高泻肯,只是用于展示固定大小的組件,可設(shè)置recyclerView.setHasFixedSize(true)這樣可避免每次繪制Item時慰照,不再重新計算高度灶挟。
RecyclerView.setHasFixedSize(true);

3.重寫 onScroll

  • 對于大量圖片、復(fù)雜布局的RecyclerView考慮重寫onScroll事件毒租,滑動暫停后再加載稚铣。
/*
// 空閑狀態(tài)
RecyclerView.SCROLL_STATE_IDLE
// 滾動狀態(tài)
RecyclerView.SCROLL_STATE_FLING
// 觸摸后狀態(tài)
RecyclerView.SCROLL_STATE_TOUCH_SCROLL
 */
 
// 添加滑動監(jiān)聽
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
        // 空閑狀態(tài)時加載圖片(數(shù)據(jù))
        if (newState == RecyclerView.SCROLL_STATE_IDLE) {
            Glide.with(mContext).resumeRequests();
        }else {
            Glide.with(mContext).pauseRequests();
        }
    }
});

4.最大程度不使用 notifyDataSetChanged

  • 對于新增或刪除數(shù)據(jù)通過DiffUtil,來進行局部數(shù)據(jù)刷新,而不是一味的全局刷新數(shù)據(jù)惕医。
    DiffUtil是support包下新增的一個工具類耕漱,用來判斷新數(shù)據(jù)和舊數(shù)據(jù)的差別,從而進行局部刷新抬伺。
    DiffUtil的使用螟够,在原來調(diào)用mAdapter.notifyDataSetChanged()的地方:
// 設(shè)置數(shù)據(jù)
public void setData(final List<String> newData) {
    //notifyDataSetChanged();
    // 數(shù)據(jù)優(yōu)化
    // 對于新增或刪除數(shù)據(jù)通過DiffUtil,來進行局部數(shù)據(jù)刷新峡钓,而不是一味的全局刷新數(shù)據(jù)妓笙。
    DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() {
        @Override
        public int getOldListSize() {
            return getItemCount();
        }

        @Override
        public int getNewListSize() {
            return newData == null ? 0 : newData.size();
        }

        @Override
        public boolean areItemsTheSame(int i, int i1) {
            return true;
        }

        @Override
        public boolean areContentsTheSame(int i, int i1) {
            return false;
        }
    });
    diffResult.dispatchUpdatesTo(this);
    this.mData = newData;
}

5.減少每個 ItemView 的層級嵌套,減少過度繪制

6.刷新閃爍

  • 設(shè)置穩(wěn)定Id
    eg:當調(diào)用notifyDataSetChanged的時候能岩,recyclerView不知道到底發(fā)生了什么寞宫,所以它只能認為所有的東西都發(fā)生了變化,即拉鹃,將所有的viewHolder都放入到pool中辈赋。
    但是,如果我們設(shè)置了stable ids毛俏,那么就會不一樣了:viewHolder被放入了scrap中炭庙,而不是pool中。注意煌寇,這里焕蹄,它的性能提升了很多!
    • 不用重新綁定阀溶,重新創(chuàng)建新的viewHolder腻脏,不用重新addViewaddView會導(dǎo)致重新測量…
    • 原本我們需要調(diào)用notifyItemMoved(4, 6)银锻,但是現(xiàn)在直接調(diào)用notifyDataSetChanged()就好了永品,但是測試結(jié)果不會有動畫效果。
Adapter.setHasStableIds(true);

7.使用 RecyclerView Prefetch 功能

  • Prefetch 功能
  • 橫向嵌套击纬,利用RecyclerView數(shù)據(jù)預(yù)取功能鼎姐。
// 在嵌套內(nèi)部的LayoutManager中調(diào)用LinearLayoutManger的設(shè)置方法
// num的取值:如果列表剛剛展示4個半item,則設(shè)置為5
innerLLM.setInitialItemsPrefetchCount(num);

Ⅱ更振、緩存優(yōu)化

1.mCachedViews

  • 設(shè)置mCachedViews大小
    eg:我們有一個壁紙庫的列表炕桨,用戶經(jīng)常會上下(左右)滑動,那么我們增加cache的容量肯腕,就會獲得更好得性能献宫。
    然而對于feed流之類的列表,用戶很少返回实撒,所以增加cache容量意義不大姊途。
RecyclerView.setItemViewCacheSize();

2.RecycledViewPool

  • 設(shè)置每個類型儲存的容量
    eg1:在我們滑動的過程中涉瘾,一個類型的viewHolderpool中應(yīng)該一直只會存在一個(除非你使用了GridLayoutManager),所以捷兰,如果你的pool中存在多個viewHolder的話立叛,他們在滾動過程中基本上是無用的。
    eg2:當我們調(diào)用notifyDataSetChanged()或者notifyItemRangeChanged(i, c)c這個范圍非常大的時候)寂殉,那么很多viewHolder都會最終被放入到pool中囚巴,因為pool只能放置5個原在,那么多余的就會被丟棄友扰,等待回收。最重要的是會重新createbind對性能影響比較大庶柿。如果你的列表能夠容納很多行村怪,而且使用notifyDataSetChanged方法比較頻繁,那么你應(yīng)該考慮設(shè)置一下容量大小浮庐。
RecyclerView.getRecycledViewPool().setMaxRecycledViews(0, 20);
  • 多個列表公用一個RecycledViewPool
    eg1:RecyclerView嵌套RecyclerView考慮設(shè)置RecyclerPool緩存
RecyclerView.setRecycledViewPool();
// RecyclerView + RecyclerView甚负,每個item內(nèi)部RecyclerView設(shè)同一個pool
private RecyclerView.RecycledViewPool childPool;

public XXAdapter(){
    childPool = new RecyclerView.RecycledViewPool();
}

private class RcyViewHolder extends RecyclerView.ViewHolder {
    private SRecyclerView sRcy;

    public RcyViewHolder(View itemView) {
        super(itemView);
        sRcy = itemView.findViewById(R.id.rcy_child);
        LinearLayoutManager manager = new LinearLayoutManager(mContext);
        // 1.設(shè)置回收
        manager.setRecycleChildrenOnDetach(true);
        manager.setOrientation(LinearLayoutManager.HORIZONTAL);
        sRcy.setLayoutManager(manager);
        // 2.設(shè)置緩存Pool
        sRcy.setRecycledViewPool(childPool);
    }
}

Ⅲ、其他優(yōu)化

數(shù)據(jù)優(yōu)化

  • 分頁加載遠端數(shù)據(jù)审残,對拉取的遠端數(shù)據(jù)進行緩存梭域,提高二次加載速度。

四搅轿、常見解決

1.局部刷新

@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) {
  if (payloads.isEmpty()) {
    onBindViewHolder(holder, position);
  } else {
    // 局部刷新
    holder.setTitle("局部更新");
    }
}

...
// 局部刷新測試
mAdapter.notifyItemChanged(2, "payload");


相關(guān)資料
資料
資料
資料


2019/07/19 12:28:12

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末病涨,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子璧坟,更是在濱河造成了極大的恐慌既穆,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件雀鹃,死亡現(xiàn)場離奇詭異幻工,居然都是意外死亡,警方通過查閱死者的電腦和手機黎茎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進店門囊颅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人傅瞻,你說我怎么就攤上這事踢代。” “怎么了俭正?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵奸鬓,是天一觀的道長。 經(jīng)常有香客問我掸读,道長串远,這世上最難降的妖魔是什么宏多? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮澡罚,結(jié)果婚禮上伸但,老公的妹妹穿的比我還像新娘。我一直安慰自己留搔,他們只是感情好更胖,可當我...
    茶點故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著隔显,像睡著了一般却妨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上括眠,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天彪标,我揣著相機與錄音,去河邊找鬼掷豺。 笑死捞烟,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的当船。 我是一名探鬼主播题画,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼德频!你這毒婦竟也來了苍息?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤抱婉,失蹤者是張志新(化名)和其女友劉穎档叔,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蒸绩,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡衙四,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了患亿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片传蹈。...
    茶點故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖步藕,靈堂內(nèi)的尸體忽然破棺而出惦界,到底是詐尸還是另有隱情,我是刑警寧澤咙冗,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布沾歪,位于F島的核電站,受9級特大地震影響雾消,放射性物質(zhì)發(fā)生泄漏灾搏。R本人自食惡果不足惜挫望,卻給世界環(huán)境...
    茶點故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望狂窑。 院中可真熱鬧媳板,春花似錦、人聲如沸泉哈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽丛晦。三九已至奕纫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間采呐,已是汗流浹背若锁。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工搁骑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留斧吐,地道東北人。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓仲器,卻偏偏與公主長得像煤率,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子乏冀,可洞房花燭夜當晚...
    茶點故事閱讀 44,969評論 2 355