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.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 的区别。本系列也是对源码的浅析,点到为止。

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

(0)

相关推荐

  • Android 源码浅析RecyclerView ItemAnimator

    目录 前言 源码分析 前置基础 ItemHolderInfo InfoRecord ViewInfoStore ProcessCallback 动画处理 dispatchLayoutStep3 dispatchLayoutStep1 dispatchLayoutStep1 dispatchLayoutStep2 dispatchLayoutStep3 总结 动画执行 ItemAnimator SimpleItemAnimator DefaultItemAnimator 最后 前言 在这个系列博客

  • RecyclerView无限循环效果实现及示例解析

    目录 前言 有现成的轮子吗? 1.修改adpter和数据映射实现 2.自定义layoutManager 后记 前言 前两天在逛博客的时候发现了这样一张直播间的截图,其中这个商品列表的切换和循环播放效果感觉挺好: 熟悉android的同学应该很快能想到这是recyclerView实现的线性列表,其主要有两个效果: 1.顶部item切换后样式放大+转场动画.2.列表自动.无限循环播放. 第一个效果比较好实现,顶部item布局的变化可以通过对RecyclerView进行OnScroll监听,判断ite

  • RecyclerView 源码浅析测量 布局 绘制 预布局

    目录 前言 onMeasure onLayout onDraw dispatchLayoutStep1.2.3 dispatchLayoutStep1 dispatchLayoutStep2 dispatchLayoutStep3 mAttachedScrap 和 mChangedScrap 预布局 最后 前言 上一篇博客内容对 RecyclerView 回收复用机制相关源码进行了分析,本博客从自定义 View 三大流程 measure.layout.draw 的角度继续对 RecyclerVi

  • Android源码学习之工厂方法模式应用及优势介绍

    工厂方法模式定义: Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses. 定义一个用于创建对象的接口,让子类决定实例化哪一个类.工厂方法使一个类的实例化延迟到其子类. 常用的工厂方法模式结构: 如上图所示(截取自<Head Firs

  • 从Android源码剖析Intent查询匹配的实现

    前言     这篇文章主要是介绍一下Android Intent,并且从Android源码的角度对Intent查询匹配过程进行分析. Intent介绍     Intent的中文是"意图"的意思,而意图是一个非常抽象的概念,那么在Android的编码设计中,如何实例化意图呢?因此Android系统明确指定一个Intent可由两方面属性来衡量. 主要属性:包括Action和Data.其中Action用于表示该Intent所表达的动作意图,Data用于表示该Action所操作的数据.   

  • Android 源码如何编译调试

    android提供的工具链和开发工具比较完善,因此它的开发环境的搭建比较简单,相信许多朋友都已经搭建好环境,并编写了HelloActivity入门程序了.这里先看几个问题: 1.android的文件系统结构是怎样的,我们安装的程序放在那里? 编译android源码之后,在out/target/product/generic一些文件: ramdisk.img.system.img.userdata.img. system. data.root 其中, system.img是由 system打包压缩

  • Windows下获取Android 源码方法的详解

    前言:略!获取源码的原因千千万~~~ 1.安装GIT工具.GIT是林纳斯·托瓦兹大神为了管理器Linux内核开发而创立的分布式版本控制软件.下载地址:http://code.google.com/p/msysgit/一路next将安装进行到底. 2.在磁盘剩余空间较大的磁盘下新建一个文件夹,用于存放源码.我在F盘下:新建了androidsourcecode文件夹. 3.访问Android源码网站,获取你所需要的源码"下载链接".网站地址:http://android.git.kerne

  • Ubuntu Android源码以及内核下载与编译

    本教程是基于Ubuntu下Android6.0.1源码以及内核的下载和编译,记录一下,以后也就不用自己去找资料,一遍一遍的尝试了.可以翻墙的,英语好的,直接去AndroidSource. 系统环境:Ubuntu14.04LTS Android版本:6.0.1 重要网址 清华大学镜像 AndroidSource 下载前的准备 安装OpenJdk sudo add-apt-repository ppa:openjdk-r/ppa sudo apt-get update sudo apt-get in

  • Android源码学习之组合模式定义及应用

    组合模式定义: Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly. 将对象组合成树形结构以表示"部分-整体"的层次结构,使得用户对单个对象和组合对象的使用具有一致性. 如上图所示(截取自<Head First De

  • Android源码学习之观察者模式应用及优点介绍

    观察者模式定义: Define a one-to-many dependency between objects so that when one object changes state, all its dependents aer notified and updated automatically. 定义对象间一种一对多的依赖关系,使得当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新.  如上图所示(截取自<Head First Design Patterns>一书),

  • Android源码学习之单例模式应用及优点介绍

    单例模式定义: Ensure a class has only one instance, and provide a global point of access to it. 动态确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例. 如上图所示(截取自<Head First Design Patterns>一书). 通过使用private的构造函数确保了在一个应用中产生一个实例,并且是自行实例化(在Singleton中自己使用new Singleton()). 具体单例模式有

随机推荐