我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android 源码浅析RecyclerView Adapter

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Android 源码浅析RecyclerView Adapter

引言

在使用 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 时编译器会给出提示:

image.png

提示最好使用更具体的变更事件,也就是调用 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的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Android 源码浅析RecyclerView Adapter

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Android 源码浅析RecyclerView Adapter

这篇文章主要介绍了Android 源码浅析之RecyclerView Adapter示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-12-21

Android源码浅析RecyclerViewItemAnimator

这篇文章主要为大家介绍了Android源码浅析RecyclerViewItemAnimator,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-12-21

浅析Android之Adapter用法总结

1.概念 Adapter是连接后端数据和前端显示的适配器接口,是数据和UI(View)之间一个重要的纽带。在常见的View(ListView,GridView)等地方都需要用到Adapter。如下图直观的表达了Data、Adapter、Vi
2022-06-06

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

这篇文章主要介绍了RecyclerView源码浅析测量布局绘制预布局,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-12-21

Android RecyclerView的Item自定义动画及DefaultItemAnimator源码分析

这是关于RecyclerView的第二篇,说的是如何自定义Item动画,但是请注意,本文不包含动画的具体实现方法,只是告诉大家如何去自定义动画,如何去参考源代码。 我们知道,RecyclerView默认会使用DefaultItemAnima
2022-06-06

spring初始化源码代码浅析

Spring框架被广泛应用于我们的日常工作中,但是很长时间以来我们都是只会使用,不懂它的作用原理,下面这篇文章主要给大家介绍了关于spring初始化源码的相关资料,需要的朋友可以参考下
2023-05-18

Android AsyncTask源码分析

Android中只能在主线程中进行UI操作,如果是其它子线程,需要借助异步消息处理机制Handler。除此之外,还有个非常方便的AsyncTask类,这个类内部封装了Handler和线程池。本文先简要介绍AsyncTask的用法,然后分析具
2022-06-06

Android LayoutInflater.inflate源码分析

LayoutInflater.inflate源码详解 LayoutInflater的inflate方法相信大家都不陌生,在Fragment的onCreateView中或者在BaseAdapter的getView方法中我们都会经常用这个方法来
2022-06-06

Django的admin源码浅析和模仿

admin模块:admin提供了5种接口list_display,指定数据展示字段,不能放多对多字段list_display_link,哪个字段可以链接 search_fields,搜索框search_fiekds    search_fi
2023-01-31

Tomcat9源代码浅析-环境搭建

1.概要Apache Tomcat是一个非常受欢迎的开源Web容器,使用Java语言开发。近期公司开始推行开源技术的应用,目前正在将Weblogic替换为Tomcat,我们称之为W2T。本系列为项目过程中对Tomcat 9源代码分析的记录,
2023-06-02

React深入浅出分析Hooks源码

在react类组件(class)写法中,有setState和生命周期对状态进行管理,但是在函数组件中不存在这些,故引入hooks(版本:>=16.8),使开发者在非class的情况下使用更多react特性
2022-11-13

Android ArrayMap源代码分析

分析源码之前先来介绍一下ArrayMap的存储结构,ArrayMap数据的存储不同于HashMap和SparseArray。Java提供了HashMap,但是HashMap对于手机端而言,对空间的利用太大,所以Android提供
2022-06-06

Android 10 startActivity 源码分析

源码基于 Android 10此图着重提炼了生命周期的部分,Android 10 中 新增了 ActivityTaskManager ,专门用于管理 Activity,接替了 ActivityManager 的一部分工作 理解 Instru
2022-06-06

Android View源码解读 DecorView与ViewRootImpl浅谈

前言 对于Android开发者来说,View无疑是开发中经常接触的,包括它的事件分发机制、测量、布局、绘制流程等,如果要自定义一个View,那么应该对以上流程有所了解、研究。本系列文章将会为大家带来View的工作流程详细解析。在深入接触Vi
2022-06-06

源码浅析Android中内存泄漏检测工具Leakcanary的使用

大名鼎鼎的Leakcanary想必作为Android开发都多多少少接触过,新版本的Leakcanary也用Kotlin重写了一遍,最近详细查看了下源码,就来和大家简单分享一下
2023-05-17

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录