網站首頁 編程語言 正文
引言
在使用 RecyclerView 時 Adapter 也是必備的,在對其進行增刪改操作時會用到以下方法:
recyclerView.setAdapter(adapter)
adapter.notifyItemInserted(index)
adapter.notifyItemChanged(index)
adapter.notifyItemRemoved(index)
adapter.notifyItemMoved(fromIndex, toIndex)
adapter.notifyDataSetChanged()
本篇博客就以此為切入點,分析這些方法的調用流程,以及 notifyDataSetChanged 和 notifyItemXXX 的區別
源碼分析
先從最先調用的 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) {
// 設置新的 adapter 之前做一些清理工作
if (mAdapter != null) {
mAdapter.unregisterAdapterDataObserver(mObserver);
mAdapter.onDetachedFromRecyclerView(this); // detach 回調
}
// 清理 item 緩存
if (!compatibleWithPrevious || removeAndRecycleViews) {
removeAndRecycleViews();
}
// 工具類重置
mAdapterHelper.reset();
final Adapter oldAdapter = mAdapter;
mAdapter = adapter; // 賦值
if (adapter != null) {
// 注冊
adapter.registerAdapterDataObserver(mObserver);
// attach 回調
adapter.onAttachedToRecyclerView(this);
}
if (mLayout != null) {
// LayoutManager 中 adapter 改變回調
mLayout.onAdapterChanged(oldAdapter, mAdapter);
}
// recycler adapter 改變回調
mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
mState.mStructureChanged = true;
}
}
可以看出上面源碼中有兩個重要的點: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 方法都是調用 mAdapterHelper 的同名方法。
AdapterDataObservable
AdapterDataObservable 繼承自抽象類 Observable 并且泛型為 AdapterDataObserver (上一節提到的 RecyclerViewDataObserver 就是 AdapterDataObserver 子類),Observable 是 sdk 中給我們提供的一個觀察者模式基類 Observable 意為可觀察對象,其內部維護一個 mObservers 容器(泛型 ArrayList)用于存放“觀察者”,并對外提供了注冊、解注冊方法;
Observable 源碼比較簡單就不貼了,來看一下 AdapterDataObservable 的核心源碼:
static class AdapterDataObservable extends Observable<AdapterDataObserver> {
public boolean hasObservers() {
// 判斷 mObservers 容器中是否有 “觀察者”
return !mObservers.isEmpty();
}
public void notifyChanged() {
// 遍歷 mObservers 調用 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 中對應的方法,在這里也就是調用 RecyclerViewDataObserver 中的方法;
Adapter
到這里可以看出,setAdapter 中的 registerAdapterDataObserver 是將 RecyclerView 與 Adapter 用觀察者模式相關聯,那么先來看一下 Adapter 的相關源碼:
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 方法同上 都是調用 mObservable 同名方法 就不貼代碼了
// ...
}
Adapter 中的 notifyXXX 都調用了 mObservable 的同名方法,那么經過上面的分析這就相當于調用到了 RecyclerViewDataObserver 中的方法,RecyclerViewDataObserver 的源碼上面的小節部分已經提到,都是調用 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() {
// 篇幅原因 方法實現就省略了
});
}
// ...
}
在構造方法中,對 mAdapterHelper 進行了初始化,上述 RecyclerViewDataObserver 中調用的 onItemRangeXXX 方法很多這里就以 onItemRangeChanged 為例看下源碼:
boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) {
if (itemCount < 1) {
return false;
}
// 注意這里是兩步操作
// obtainUpdateOp 構建 UpdateOp 對象
// 添加到 mPendingUpdates 容器
mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount, payload));
// 記錄操作類型
mExistingUpdateTypes |= UpdateOp.UPDATE;
return mPendingUpdates.size() == 1;
}
mPendingUpdates 存放 UpdateOp 對象,UpdateOp 中記錄 item 變化的相關信息;
到這里再回到 RecyclerViewDataObserver 中的 onItemRangeXXX 方法,如果返回 ture 還會調用 triggerUpdateProcessor(),看一下這個方法源碼:
RecyclerViewDataObserver.java
void triggerUpdateProcessor() {
// mHasFixedSize 通過 setHasFixedSize 設置 默認是 false
if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
// 執行 mUpdateChildViewsRunnable
// 這個 runable 相比于 else 中直接調用 requestLayout() 增加了一些判斷 算是性能上的一個優化
ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
} else {
mAdapterUpdateDuringMeasure = true;
// 調用 requestLayout 重新布局
requestLayout();
}
}
看到這里基本可以了解到,當我們調用 adapter.notifyItemXXX 后會觸發 requestLayout() 重新調用布局流程 dispatchLayoutStep1、2、3 ,如果設置 mHasFixedSize 為 true 性能應該會更佳;
notifyDataSetChanged
當我們調用 notifyDataSetChanged 時編譯器會給出提示:
提示最好使用更具體的變更事件,也就是調用 notifyItemXXX 更好。那么我們來看一下 notifyDataSetChanged 為什么不如 notifyItemXXX。通過上面的源碼流程,直接看 RecyclerViewDataObserver 的 onChanged 方法源碼:
RecyclerViewDataObserver.java
public void onChanged() {
assertNotInLayoutOrScroll(null);
mState.mStructureChanged = true;
// 注意這一行
processDataSetCompletelyChanged(true);
if (!mAdapterHelper.hasPendingUpdates()) {
requestLayout();
}
}
onChanged 內部直接調用了 requestLayout,和 onItemRangeXXX 類似(上面分析 onItemRangeXXX 內部調用 triggerUpdateProcessor 最終也會調用 requestLayout),但是注意 processDataSetCompletelyChanged 這個方法:
void processDataSetCompletelyChanged(boolean dispatchItemsChanged) {
mDispatchItemsChangedEvent |= dispatchItemsChanged;
mDataSetHasChangedAfterLayout = true;
// 方法名的大概意思:標記已知view為無效
markKnownViewsInvalid();
}
void markKnownViewsInvalid() {
final int childCount = mChildHelper.getUnfilteredChildCount();
// 循環每個 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();
}
添加這個標記有什么作用呢?這里就不賣關子了,回想一下之前博客講述的回收復用流程,Recycler 負責獲取 ViewHolder,通過 getViewForPosition 最終調用到 tryGetViewHolderForPositionByDeadline 方法從多級緩存中獲取 ViewHolder,獲取完了之后在綁定數據時有這么一個判斷:
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 標記會調用 tryBindViewHolderByDeadline
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
//...
}
而 tryBindViewHolderByDeadline 中又調用了 bindViewHolder,源碼如下:
RecyclerView.java
private boolean tryBindViewHolderByDeadline(@NonNull ViewHolder holder, int offsetPosition,
int position, long deadlineNs) {
// ...
mAdapter.bindViewHolder(holder, offsetPosition);
// ...
}
bindViewHolder 中又調用了 onBindViewHolder 重新進行了數據綁定設置;所以,使用 notifyDataSetChanged 會將所有的 itemView 進行無效化標記,布局時會全部走一次數據綁定,所以推薦使用 notifyItemXXX 來對 RecyclerView 進行更新。
最后
本篇 Adapter 的分析略顯粗糙,僅對關鍵源碼進行了分析,主要是覺得這部分內容在日常開發或者面試中最常遇到的問題就是 notifyDataSetChanged 和 notifyItemXXX 的區別。本系列也是對源碼的淺析,點到為止。
原文鏈接:https://juejin.cn/post/7179171313943543845
相關推薦
- 2022-05-04 使用Spring.Net框架實現多數據庫_實用技巧
- 2022-01-08 使用grid布局解決flex布局最后一行不足的問題
- 2022-09-09 Nginx配置解決NetCore的跨域問題_nginx
- 2022-04-25 利用Python寫個摸魚監控進程_python
- 2022-03-21 C語言動態內存管理介紹_C 語言
- 2022-06-11 C#把EXCEL數據轉換成DataTable_C#教程
- 2022-06-21 .net中常用的正則表達式_C#教程
- 2022-08-22 GoFrame實現順序性校驗示例詳解_Golang
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支