开发者

Android 源码浅析RecyclerView Adapter

目录
  • 引言
  • 源码分析
    • RecyclerViewDataObserver
    • AdapterDataObservable
    • Adapter
    • AdapterHelper
    • notifyDataSetChanged
  • 最后

    引言

    在使用 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.mStruct开发者_Python培训ureChanged = 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
        p编程客栈ublic 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 itTtIfNttHemCount, @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).onItempythonRangeRemoved(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 时编译器会给出提示:

    Android 源码浅析RecyclerView Adapter

    提示最好使用更具体的变更事件,也就是调用 notifyItemXXX 更好。那么我们来看一下 notifyDataSetChanged 为什么不如 notifyItemXXX。通过上面的源码流程,直接看 RecyclerViewDataObserver 的 onChanged 方法源码:

    RecyclerViewDataObserver.java

    public void onChanged() {
        assertNotInLayoutphpOrScroll(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();
        mRecyclandroider.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 的区别。本系列也是对源码的浅析,点到为止。

    以上就是android 源码浅析RecyclerView Adapter的详细内容,更多关于Android RecyclerView Adapter的资料请关注我们其它相关文章!

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜