2024年Android最全Android RecyclerView 绘制流程及Recycler缓存,2024年最新面试时答不出来怎么机智应答

结语

  • 现在随着短视频,抖音,快手的流行NDK模块开发也显得越发重要,需要这块人才的企业也越来越多,随之学习这块的人也变多了,音视频的开发,往往是比较难的,而这个比较难的技术就是NDK里面的技术。
  • 音视频/高清大图片/人工智能/直播/抖音等等这年与用户最紧密,与我们生活最相关的技术一直都在寻找最终的技术落地平台,以前是windows系统,而现在则是移动系统了,移动系统中又是以Android占比绝大部分为前提,所以AndroidNDK技术已经是我们必备技能了。
  • 要学习好NDK,其中的关于C/C++,jni,Linux基础都是需要学习的,除此之外,音视频的编解码技术,流媒体协议,ffmpeg这些都是音视频开发必备技能,而且
  • OpenCV/OpenGl/这些又是图像处理必备知识,下面这些我都是当年自己搜集的资料和做的一些图,因为当年我就感觉视频这块会是一个大的趋势。所以提前做了一些准备。现在拿出来分享给大家。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {

layoutChunkResult.resetInternal();

if (RecyclerView.VERBOSE_TRACING) {

TraceCompat.beginSection(“LLM LayoutChunk”);

}

//子 View 测量布局的真正实现,每次执行完之后需要重新计算 remainingSpace。

layoutChunk(recycler, state, layoutState, layoutChunkResult);

if (!layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList != null

|| !state.isPreLayout()) {

layoutState.mAvailable -= layoutChunkResult.mConsumed;

// 我们保留一个单独的剩余空间,因为Mavaailable对于回收很重要

//每次循环之后,都将remainingSpace减去已消费的size

remainingSpace -= layoutChunkResult.mConsumed;

}

return start - layoutState.mAvailable;

}

在 onLayoutChildren 中调用 fill 方法,完成子 View 的测量布局工作;

在 fill 方法中通过 while 循环判断是否还有剩余足够空间来绘制一个完整的子 View;

layoutChunk 方法中是子 View 测量布局的真正实现,每次执行完之后需要重新计算 remainingSpace。

layoutChunk

layoutChunk 是一个非常核心的方法,这个方法执行一次就填充一个 ItemView 到 RecyclerView,部分代码如下:

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,

LayoutState layoutState, LayoutChunkResult result) {

//从缓存(Recycler)中取出子 ItemView。

View view = layoutState.next(recycler);

RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();

//然后调用 addView 或者 addDisappearingView 将子 ItemView 添加到 RecyclerView 中。

if (layoutState.mScrapList == null) {

if (mShouldReverseLayout == (layoutState.mLayoutDirection

== LayoutState.LAYOUT_START)) {

addView(view);

} else {

addView(view, 0);

}

} else {

if (mShouldReverseLayout == (layoutState.mLayoutDirection

== LayoutState.LAYOUT_START)) {

addDisappearingView(view);

} else {

addDisappearingView(view, 0);

}

}

//测量被添加的 RecyclerView 中的子 ItemView 的宽高。

measureChildWithMargins(view, 0, 0);

//根据所设置的 Decoration、Margins 等所有选项确定子 ItemView 的显示位置。

layoutDecoratedWithMargins(view, left, top, right, bottom);

}

onDraw


测量和布局都完成之后,就剩下最后的绘制操作了。

@Override

public void onDraw(Canvas c) {

super.onDraw©;

final int count = mItemDecorations.size();

for (int i = 0; i < count; i++) {

mItemDecorations.get(i).onDraw(c, this, mState);

}

}

如果有添加 ItemDecoration,则循环调用所有的 Decoration 的 onDraw 方法,将其显示。至于所有的子 ItemView 则是通过 Android 渲染机制递归的调用子 ItemView 的 draw 方法显示到屏幕上。

绘制流程小结


RecyclerView 会将测量 onMeasure布局 onLayout 的工作委托给 LayoutManager 来执行,不同的 LayoutManager 会有不同风格的布局显示,这是一种策略模式。如下图:

缓存复用原理 Recycler

===============

缓存复用是 RecyclerView 中另一个非常重要的机制,这套机制主要实现了 ViewHolder 的缓存以及复用。

核心代码是在 Recycler 中完成的,它是 RecyclerView 中的一个内部类,主要用来缓存屏幕内 ViewHolder 以及部分屏幕外 ViewHolder,部分代码如下:

public final class Recycler {

//未与RecyclerView分离的ViewHolder列表

final ArrayList mAttachedScrap = new ArrayList<>();

//与RecyclerView分离的ViewHolder列表

ArrayList mChangedScrap = null;

//ViewHolder缓存列表

final ArrayList mCachedViews = new ArrayList();

//ViewHolder缓存池

RecycledViewPool mRecyclerPool;

//开发者可以控制的ViewHolder缓存的帮助类

private ViewCacheExtension mViewCacheExtension;

//默认情况下缓存个数是 2

static final int DEFAULT_CACHE_SIZE = 2;

}

Recycler 的缓存机制就是通过上图中的这些数据容器来实现的,实际上 Recycler 的缓存也是分级处理的,根据访问优先级从上到下可以分为 4 级,如下:

  • 第一级缓存 mAttachedScrap&mChangedScrap

  • 第二级缓存 mCachedViews

  • 第三级缓存 ViewCacheExtension

  • 第四级缓存 RecycledViewPool

各级缓存功能


RecyclerView 之所以要将缓存分成这么多块,是为了在功能上进行一些区分,并分别对应不同的使用场景。

第一级缓存 mAttachedScrap&mChangedScrap

是两个名为 Scrap 的 ArrayList,这两者主要用来缓存屏幕内的 ViewHolder。为什么屏幕内的 ViewHolder 需要缓存呢?

做过 App 开发的应该都熟悉通过下拉刷新列表中的内容,当刷新被触发时,只需要在原有的 ViewHolder 基础上进行重新绑定新的数据 data 即可,而这些旧的 ViewHolder 就是被保存在 mAttachedScrap 和 mChangedScrap 中。实际上当我们调用 RV 的 notifyXXX 方法时,就会向这两个列表进行填充,将旧 ViewHolder 缓存起来。

第二级缓存 mCachedViews

它用来缓存移除屏幕之外的 ViewHolder,默认情况下缓存个数是 2,不过可以通过 setViewCacheSize 方法来改变缓存的容量大小。如果 mCachedViews 的容量已满,则会根据 FIFO(先进先出) 的规则将旧 ViewHolder 抛弃,然后添加新的 ViewHolder。

通常情况下刚被移出屏幕的 ViewHolder 有可能接下来马上就会使用到,所以 RecyclerView 不会立即将其设置为无效 ViewHolder,而是会将它们保存到 cache 中,但又不能将所有移除屏幕的 ViewHolder 都视为有效 ViewHolder,所以它的默认容量只有 2 个。

第三级缓存 ViewCacheExtension

这是 RecyclerView 预留给开发人员的一个抽象类,在这个类中只有一个抽象方法。

public abstract static class ViewCacheExtension {

@Nullable

public abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position,

int type);

}

开发人员可以通过继承 ViewCacheExtension,并复写抽象方法 getViewForPositionAndType 来实现自己的缓存机制。一般情况下我们不会自己实现也不建议自己去添加缓存逻辑,因为这个类的使用门槛较高(牛掰的人请忽略)。

第四级缓存 RecycledViewPool

RecycledViewPool 同样是用来缓存屏幕外的 ViewHolder,当 mCachedViews 中的个数已满(默认为 2),则从 mCachedViews 中淘汰出来的 ViewHolder 会先缓存到 RecycledViewPool 中。ViewHolder 在被缓存到 RecycledViewPool 时,会将内部的数据清理,因此从 RecycledViewPool 中取出来的 ViewHolder 需要重新调用 onBindViewHolder 绑定数据。这就同最早的 ListView 中的使用 ViewHolder 复用 convertView 的道理是一致的,因此 RV 也算是将 ListView 的优点完美的继承过来。

RecycledViewPool 还有一个重要功能,官方对其有如下解释:

RecycledViewPool 允许您在多个 RecyclerView 之间共享视图。

如果要跨 RecyclerViews 回收视图,请创建 RecycledViewPool 的实例并使用RecyclerView.setRecycledViewPool(RecycledViewPool)。

如果您不提供,RecyclerView 会自动为自己创建一个池。

可以看出,多个 RecycledView之间可以共享一个 RecycledViewPool,这对于多 tab 界面的优化效果会很显著。需要注意的是,RecycledViewPool 是根据 type 来获取 ViewHolder,每个 type 默认最大缓存 5 个。因此多个 RecyclerView 共享 RecycledViewPool 时,必须确保共享的 RecyclerView 使用的 Adapter 是同一个,或 view type 是不会冲突的。

RecyclerView 是如何从缓存中获取 ViewHolder 的


在上面介绍 onLayout 阶段时,有介绍在 layoutChunk 方法中通过调用 layoutState.next 方法拿到某个子 ItemView,然后添加到 RecyclerView 中。

LinearLayoutManager.LayoutState.next

View next(RecyclerView.Recycler recycler) {

if (mScrapList != null) {

return nextViewFromScrapList();

}

final View view = recycler.getViewForPosition(mCurrentPosition);

mCurrentPosition += mItemDirection;

return view;

}

//继续跟getViewForPosition(mCurrentPosition);

@NonNull

public View getViewForPosition(int position) {

return getViewForPosition(position, false);

}

//继续跟getViewForPosition

View getViewForPosition(int position, boolean dryRun) {

return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;

}

可以看出最终调用 tryGetViewHolderForPositionByDeadline 方法来查找相应位置上的ViewHolder。

tryGetViewHolderForPositionByDeadline


在这个方法中会从上面介绍的 4 级缓存中依次查找:

@Nullable

ViewHolder tryGetViewHolderForPositionByDeadline(int position,

boolean dryRun, long deadlineNs) {

ViewHolder holder = null;

// 1)根据位置从scrap和mCachedViews返回位置的视图。

if (holder == null) {

//根据位置从scrap和mCachedViews返回位置的视图。

holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);

}

if (holder == null) {

//获取这个位置的数据的类型(子View复写的方法)

final int type = mAdapter.getItemViewType(offsetPosition);

// 2)根据ID在scrap和cache 中查找

if (mAdapter.hasStableIds()) {

holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),type, dryRun);

}

//从ViewCacheExtension获取ViewHolder

if (holder == null && mViewCacheExtension != null) {

//返回 绑定到给定位置的视图,如果没有可重用的视图,则为NULL

final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);

}

//3)从RecycledViewPool获取ViewHolder

if (holder == null) {

//4)从RecycledViewPool获取ViewHolder

holder = getRecycledViewPool().getRecycledView(type);

}

最后

想要了解更多关于大厂面试的同学可以点赞支持一下,除此之外,我也分享一些优质资源,包括:Android学习PDF+架构视频+源码笔记高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 这几块的内容。非常适合近期有面试和想在技术道路上继续精进的朋友。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

ull) {

//4)从RecycledViewPool获取ViewHolder

holder = getRecycledViewPool().getRecycledView(type);

}

最后

想要了解更多关于大厂面试的同学可以点赞支持一下,除此之外,我也分享一些优质资源,包括:Android学习PDF+架构视频+源码笔记高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 这几块的内容。非常适合近期有面试和想在技术道路上继续精进的朋友。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 17
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值