Android?源碼淺析RecyclerView?Adapter
引言
在使用 RecyclerView 時 Adapter 也是必備的,在對其進(jìn)行增刪改操作時會用到以下方法:
recyclerView.setAdapter(adapter) adapter.notifyItemInserted(index) adapter.notifyItemChanged(index) adapter.notifyItemRemoved(index) adapter.notifyItemMoved(fromIndex, toIndex) adapter.notifyDataSetChanged()
本篇博客就以此為切入點(diǎn),分析這些方法的調(diào)用流程,以及 notifyDataSetChanged 和 notifyItemXXX 的區(qū)別
源碼分析
先從最先調(diào)用的 setAdapter 入手看一下其源碼:
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2, NestedScrollingChild3 { Adapter mAdapter; // ... public void setAdapter(@Nullable Adapter adapter) { // ... // 核心代碼 setAdapterInternal(adapter, false, true); // ... } private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious, boolean removeAndRecycleViews) { // 設(shè)置新的 adapter 之前做一些清理工作 if (mAdapter != null) { mAdapter.unregisterAdapterDataObserver(mObserver); mAdapter.onDetachedFromRecyclerView(this); // detach 回調(diào) } // 清理 item 緩存 if (!compatibleWithPrevious || removeAndRecycleViews) { removeAndRecycleViews(); } // 工具類重置 mAdapterHelper.reset(); final Adapter oldAdapter = mAdapter; mAdapter = adapter; // 賦值 if (adapter != null) { // 注冊 adapter.registerAdapterDataObserver(mObserver); // attach 回調(diào) adapter.onAttachedToRecyclerView(this); } if (mLayout != null) { // LayoutManager 中 adapter 改變回調(diào) mLayout.onAdapterChanged(oldAdapter, mAdapter); } // recycler adapter 改變回調(diào) mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious); mState.mStructureChanged = true; } }
可以看出上面源碼中有兩個重要的點(diǎn):mObserver,mAdapterHelper;
先看一下 adapter.registerAdapterDataObserver 源碼:
public void registerAdapterDataObserver(@NonNull AdapterDataObserver observer) { mObservable.registerObserver(observer); }
mObserver 和 mObservable 定義如下:
public class RecyclerView { private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver(); // ... public abstract static class Adapter<VH extends ViewHolder> { private final AdapterDataObservable mObservable = new AdapterDataObservable(); // ... } // ... }
RecyclerViewDataObserver
RecyclerViewDataObserver 繼承自 AdapterDataObserver 重寫了其全部方法,看一下其核心部分:
private class RecyclerViewDataObserver extends AdapterDataObserver { // ... @Override public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { assertNotInLayoutOrScroll(null); if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) { triggerUpdateProcessor(); } } @Override public void onItemRangeInserted(int positionStart, int itemCount) { assertNotInLayoutOrScroll(null); if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) { triggerUpdateProcessor(); } } @Override public void onItemRangeRemoved(int positionStart, int itemCount) { assertNotInLayoutOrScroll(null); if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) { triggerUpdateProcessor(); } } @Override public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { assertNotInLayoutOrScroll(null); if (mAdapterHelper.onItemRangeMoved(fromPosition, toPosition, itemCount)) { triggerUpdateProcessor(); } } // ... }
可以看出這幾個 onItemRangerXXX 方法都是調(diào)用 mAdapterHelper 的同名方法。
AdapterDataObservable
AdapterDataObservable 繼承自抽象類 Observable 并且泛型為 AdapterDataObserver (上一節(jié)提到的 RecyclerViewDataObserver 就是 AdapterDataObserver 子類),Observable 是 sdk 中給我們提供的一個觀察者模式基類 Observable 意為可觀察對象,其內(nèi)部維護(hù)一個 mObservers 容器(泛型 ArrayList)用于存放“觀察者”,并對外提供了注冊、解注冊方法;
Observable 源碼比較簡單就不貼了,來看一下 AdapterDataObservable 的核心源碼:
static class AdapterDataObservable extends Observable<AdapterDataObserver> { public boolean hasObservers() { // 判斷 mObservers 容器中是否有 “觀察者” return !mObservers.isEmpty(); } public void notifyChanged() { // 遍歷 mObservers 調(diào)用 onChanged for (int i = mObservers.size() - 1; i >= 0; i--) { mObservers.get(i).onChanged(); } } public void notifyStateRestorationPolicyChanged() { for (int i = mObservers.size() - 1; i >= 0; i--) { mObservers.get(i).onStateRestorationPolicyChanged(); } } public void notifyItemRangeChanged(int positionStart, int itemCount) { notifyItemRangeChanged(positionStart, itemCount, null); } public void notifyItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) { for (int i = mObservers.size() - 1; i >= 0; i--) { mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload); } } public void notifyItemRangeInserted(int positionStart, int itemCount) { for (int i = mObservers.size() - 1; i >= 0; i--) { mObservers.get(i).onItemRangeInserted(positionStart, itemCount); } } public void notifyItemRangeRemoved(int positionStart, int itemCount) { for (int i = mObservers.size() - 1; i >= 0; i--) { mObservers.get(i).onItemRangeRemoved(positionStart, itemCount); } } public void notifyItemMoved(int fromPosition, int toPosition) { for (int i = mObservers.size() - 1; i >= 0; i--) { mObservers.get(i).onItemRangeMoved(fromPosition, toPosition, 1); } } }
可以看出 notifyXXX 方法均為遍歷 mObservers 中對應(yīng)的方法,在這里也就是調(diào)用 RecyclerViewDataObserver 中的方法;
Adapter
到這里可以看出,setAdapter 中的 registerAdapterDataObserver 是將 RecyclerView 與 Adapter 用觀察者模式相關(guān)聯(lián),那么先來看一下 Adapter 的相關(guān)源碼:
public abstract static class Adapter<VH extends ViewHolder> { private final AdapterDataObservable mObservable = new AdapterDataObservable(); // ... // 注冊 public void registerAdapterDataObserver(@NonNull AdapterDataObserver observer) { mObservable.registerObserver(observer); } // 解注冊 public void unregisterAdapterDataObserver(@NonNull AdapterDataObserver observer) { mObservable.unregisterObserver(observer); } public final void notifyDataSetChanged() { mObservable.notifyChanged(); } public final void notifyItemChanged(int position) { mObservable.notifyItemRangeChanged(position, 1); } // 剩下的 notifyItemXXX 方法同上 都是調(diào)用 mObservable 同名方法 就不貼代碼了 // ... }
Adapter 中的 notifyXXX 都調(diào)用了 mObservable 的同名方法,那么經(jīng)過上面的分析這就相當(dāng)于調(diào)用到了 RecyclerViewDataObserver 中的方法,RecyclerViewDataObserver 的源碼上面的小節(jié)部分已經(jīng)提到,都是調(diào)用 mAdapterHelper 中的方法,接下來就來看看 AdapterHelper 的源碼;
AdapterHelper
先看一下其在 RecyclerView 中的初始化:
public class RecyclerView { AdapterHelper mAdapterHelper; // ... public RecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { // ... initAdapterManager(); // ... } void initAdapterManager() { mAdapterHelper = new AdapterHelper(new AdapterHelper.Callback() { // 篇幅原因 方法實(shí)現(xiàn)就省略了 }); } // ... }
在構(gòu)造方法中,對 mAdapterHelper 進(jìn)行了初始化,上述 RecyclerViewDataObserver 中調(diào)用的 onItemRangeXXX 方法很多這里就以 onItemRangeChanged 為例看下源碼:
boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) { if (itemCount < 1) { return false; } // 注意這里是兩步操作 // obtainUpdateOp 構(gòu)建 UpdateOp 對象 // 添加到 mPendingUpdates 容器 mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount, payload)); // 記錄操作類型 mExistingUpdateTypes |= UpdateOp.UPDATE; return mPendingUpdates.size() == 1; }
mPendingUpdates 存放 UpdateOp 對象,UpdateOp 中記錄 item 變化的相關(guān)信息;
到這里再回到 RecyclerViewDataObserver 中的 onItemRangeXXX 方法,如果返回 ture 還會調(diào)用 triggerUpdateProcessor(),看一下這個方法源碼:
RecyclerViewDataObserver.java
void triggerUpdateProcessor() { // mHasFixedSize 通過 setHasFixedSize 設(shè)置 默認(rèn)是 false if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) { // 執(zhí)行 mUpdateChildViewsRunnable // 這個 runable 相比于 else 中直接調(diào)用 requestLayout() 增加了一些判斷 算是性能上的一個優(yōu)化 ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable); } else { mAdapterUpdateDuringMeasure = true; // 調(diào)用 requestLayout 重新布局 requestLayout(); } }
看到這里基本可以了解到,當(dāng)我們調(diào)用 adapter.notifyItemXXX 后會觸發(fā) requestLayout() 重新調(diào)用布局流程 dispatchLayoutStep1、2、3 ,如果設(shè)置 mHasFixedSize 為 true 性能應(yīng)該會更佳;
notifyDataSetChanged
當(dāng)我們調(diào)用 notifyDataSetChanged 時編譯器會給出提示:
提示最好使用更具體的變更事件,也就是調(diào)用 notifyItemXXX 更好。那么我們來看一下 notifyDataSetChanged 為什么不如 notifyItemXXX。通過上面的源碼流程,直接看 RecyclerViewDataObserver 的 onChanged 方法源碼:
RecyclerViewDataObserver.java
public void onChanged() { assertNotInLayoutOrScroll(null); mState.mStructureChanged = true; // 注意這一行 processDataSetCompletelyChanged(true); if (!mAdapterHelper.hasPendingUpdates()) { requestLayout(); } }
onChanged 內(nèi)部直接調(diào)用了 requestLayout,和 onItemRangeXXX 類似(上面分析 onItemRangeXXX 內(nèi)部調(diào)用 triggerUpdateProcessor 最終也會調(diào)用 requestLayout),但是注意 processDataSetCompletelyChanged 這個方法:
void processDataSetCompletelyChanged(boolean dispatchItemsChanged) { mDispatchItemsChangedEvent |= dispatchItemsChanged; mDataSetHasChangedAfterLayout = true; // 方法名的大概意思:標(biāo)記已知view為無效 markKnownViewsInvalid(); } void markKnownViewsInvalid() { final int childCount = mChildHelper.getUnfilteredChildCount(); // 循環(huán)每個 viewhodler for (int i = 0; i < childCount; i++) { final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); if (holder != null && !holder.shouldIgnore()) { // 給 viewholder 添加了 FLAG_INVALID holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID); } } markItemDecorInsetsDirty(); mRecycler.markKnownViewsInvalid(); }
添加這個標(biāo)記有什么作用呢?這里就不賣關(guān)子了,回想一下之前博客講述的回收復(fù)用流程,Recycler 負(fù)責(zé)獲取 ViewHolder,通過 getViewForPosition 最終調(diào)用到 tryGetViewHolderForPositionByDeadline 方法從多級緩存中獲取 ViewHolder,獲取完了之后在綁定數(shù)據(jù)時有這么一個判斷:
Recycler.java
ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) { //... if (mState.isPreLayout() && holder.isBound()) { holder.mPreLayoutPosition = position; } // 注意這里的 else if 分支 else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { // 如果 viewholder 有 FLAG_INVALID 標(biāo)記會調(diào)用 tryBindViewHolderByDeadline final int offsetPosition = mAdapterHelper.findPositionOffset(position); bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); } //... }
而 tryBindViewHolderByDeadline 中又調(diào)用了 bindViewHolder,源碼如下:
RecyclerView.java
private boolean tryBindViewHolderByDeadline(@NonNull ViewHolder holder, int offsetPosition, int position, long deadlineNs) { // ... mAdapter.bindViewHolder(holder, offsetPosition); // ... }
bindViewHolder 中又調(diào)用了 onBindViewHolder 重新進(jìn)行了數(shù)據(jù)綁定設(shè)置;所以,使用 notifyDataSetChanged 會將所有的 itemView 進(jìn)行無效化標(biāo)記,布局時會全部走一次數(shù)據(jù)綁定,所以推薦使用 notifyItemXXX 來對 RecyclerView 進(jìn)行更新。
最后
本篇 Adapter 的分析略顯粗糙,僅對關(guān)鍵源碼進(jìn)行了分析,主要是覺得這部分內(nèi)容在日常開發(fā)或者面試中最常遇到的問題就是 notifyDataSetChanged 和 notifyItemXXX 的區(qū)別。本系列也是對源碼的淺析,點(diǎn)到為止。
以上就是Android 源碼淺析RecyclerView Adapter的詳細(xì)內(nèi)容,更多關(guān)于Android RecyclerView Adapter的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android編程實(shí)現(xiàn)開機(jī)自動運(yùn)行的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)開機(jī)自動運(yùn)行的方法,分析了Android開機(jī)運(yùn)行的原理并結(jié)合實(shí)例形式給出了Android基于廣播實(shí)現(xiàn)開機(jī)運(yùn)行的技巧,需要的朋友可以參考下2016-10-10Android MPAndroidChart開源圖表庫之餅狀圖的代碼
MPAndroidChart是一款基于Android的開源圖表庫,MPAndroidChart不僅可以在Android設(shè)備上繪制各種統(tǒng)計圖表,而且可以對圖表進(jìn)行拖動和縮放操作,應(yīng)用起來非常靈活2018-05-05AndroidStudio升級到3.0的新特性和注意事項(xiàng)小結(jié)
這篇文章主要介紹了AndroidStudio升級到3.0的新特性和注意事項(xiàng),非常不錯,具有參考借鑒價值,需要的朋友可以參考下2017-11-11Flutter實(shí)現(xiàn)牛頓擺動畫效果的示例代碼
牛頓擺大家應(yīng)該都不陌生,也叫碰碰球、永動球(理論情況下),那么今天我們用Flutter實(shí)現(xiàn)這么一個理論中的永動球,可以作為加載Loading使用,需要的可以參考一下2022-04-04android listview進(jìn)階實(shí)例分享
這篇文章主要介紹了android listview進(jìn)階實(shí)例分享,具有一定借鑒價值,需要的朋友可以參考下2018-01-01Android kotlin+協(xié)程+Room數(shù)據(jù)庫的簡單使用
這篇文章主要介紹了Android kotlin+協(xié)程+Room數(shù)據(jù)庫的簡單使用,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-01-01