RecyclerView源码分析(一):RecyclerView的三大流程

2024-08-25 13:08

本文主要是介绍RecyclerView源码分析(一):RecyclerView的三大流程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1、开篇

我们都知道RecyclerView是一个具有缓存机制的列表控件,它会在适当的时机对滑出屏幕的View进行回收和重用,避免创建大量的View,从而达到高性能的目的。作为Android中使用最高频的组件之一,我们非常有必要了解一下它背后的实现原理和使用相关注意事项。通过源码分析它的流程,是了解RecyclerView的重要手段。注意的是阅读源码一定要带着问题去阅读,只关心主流程,不要被淹没在源码的汪洋大海。因为仅仅RecyclerView.java这个文件就有一万三千多行代码,不要试图搞清楚每一样代码的意思和目的。事实上,阅读源码是一个效率相对低下的方式,只是没有更好的方式了,才显得它相尤为重要。
废话不多说,看一下本系列文章要解决的问题: ·

  1. 既然是个ViewGroup,那少不了要问上一句:它的measure、layout和draw是怎么样的?
  2. RecyclerView是怎么回收View的?什么时候回收?
  3. 怎么支持多类型Item的?怎么缓存和查找的呢?
  4. Item动画过程中notifyXXXChange会不会导致动画的错位?
  5. Adapter的onRecreateViewHolder和onBindViewHolder两大核心方法是什么时候调用的?

而本篇文章作为这个系列的第一篇,主要解决第一个问题。另外,开始源码分析之前先看一下RecyclerView的核心类:

在这里插入图片描述

2、onMeasure流程

先看onMeasure:

protected void onMeasure(int widthSpec, int heightSpec) {// mLayout就是LayoutManager对象if (mLayout == null) { // 如果没有设置LayoutManager,设定默认宽高defaultOnMeasure(widthSpec, heightSpec);return;}// 是否开启自动测量模式,Android提供的LinearLayoutManager、GridLayoutManager和StaggeredLayoutManager默认都开启了自动测量if (mLayout.isAutoMeasureEnabled()) {final int widthMode = MeasureSpec.getMode(widthSpec);final int heightMode = MeasureSpec.getMode(heightSpec);/*** 此调用应该被视为deprecated,而被defaultOnMeasure方法替代。但后者其实也不能完全替代前者,* 因为这回破坏现有的第三方代码。但是所有面向开发者的文档都引导开发者在LayoutManager#isAutoMeasureEnabled()方法返回true的时候不要重写LayoutManager#onMeasure方法。  * 所以这其实也是调用了defaultOnMeasure方法。*/mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);final boolean measureSpecModeIsExactly =widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;// 当我们已经明确给RecyclerView指定宽高的时候,无需测量,直接返回// 当Adapter没有设置的时候也无法测量,直接返回if (measureSpecModeIsExactly || mAdapter == null) {return;}// 开始测量第一步if (mState.mLayoutStep == State.STEP_START) {dispatchLayoutStep1();}// set dimensions in 2nd step. Pre-layout should happen with old dimensions for// consistency// 开始测量第二步,预布局mLayout.setMeasureSpecs(widthSpec, heightSpec);mState.mIsMeasuring = true;dispatchLayoutStep2();// 获取到了子View的宽高mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);// 如果RecyclerView没有指定确切的宽高且至少有一个子View也不确定宽高,则需要重新测量// 也就是再次执行dispatchLayoutStep2()if (mLayout.shouldMeasureTwice()) {mLayout.setMeasureSpecs(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));mState.mIsMeasuring = true;dispatchLayoutStep2();// 到这里已经可以获取子View的宽高了mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);}} else {if (mHasFixedSize) {mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);return;}// 自定义测量流程开始if (mAdapterUpdateDuringMeasure) {  // Adapter在测量过程中更新了startInterceptRequestLayout();onEnterLayoutOrScroll();processAdapterUpdatesAndSetAnimationFlags();onExitLayoutOrScroll();if (mState.mRunPredictiveAnimations) {mState.mInPreLayout = true;} else {// 完成剩余的更新以提供与布局传递一致的状态mAdapterHelper.consumeUpdatesInOnePass();mState.mInPreLayout = false;}mAdapterUpdateDuringMeasure = false;stopInterceptRequestLayout(false);} else if (mState.mRunPredictiveAnimations) {// 如果mAdapterUpdateDuringMeasure是false且mRunPredictiveAnimations是true// 这意味着已经调用onMeasure来处理Adapter的更新了// 当RecyclerView是LinearLayout的子View且layout_width=MATCH_PARENT,onMeasure会调用两次// RecyclerView不能多次调用LayoutManager.onMeasure,因为LayoutManager测量子View的时候getViewForPosition()会崩溃setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight());return;}if (mAdapter != null) {mState.mItemCount = mAdapter.getItemCount();} else {mState.mItemCount = 0;}startInterceptRequestLayout();mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);stopInterceptRequestLayout(false);mState.mInPreLayout = false; // 清除标记}
}

我对这个方法里面的注释翻译了一些,在一些地方也加入了我自己的理解。这里面氛围了三种情况:

  1. 没有设置LayoutManager
  2. LayoutManager开启了自动测量
  3. LayoutManager没有开启自动测量

2.1 没有设置LayoutManager

没有设置LayoutManager的情况非常简单,就是调用了defaultOnMeasure:

/*** An implementation of {@link View#onMeasure(int, int)} to fall back to in various scenarios* where this RecyclerView is otherwise lacking better information.*/
void defaultOnMeasure(int widthSpec, int heightSpec) {// calling LayoutManager here is not pretty but that API is already public and it is better// than creating another method since this is internal.final int width = LayoutManager.chooseSize(widthSpec,getPaddingLeft() + getPaddingRight(),ViewCompat.getMinimumWidth(this));final int height = LayoutManager.chooseSize(heightSpec,getPaddingTop() + getPaddingBottom(),ViewCompat.getMinimumHeight(this));setMeasuredDimension(width, height);
}

这里是根据RecyclerView的padding和minHeight来计算一个默认的宽高。具体怎么计算这里不深究,因为没有设置LayoutManager的情况不是我们最关心的。

2.2 LayoutManager开启了自动测量

这种情况下,首先调用了mLayout.onMeasure,注释中也解释了,其实就是调用的defaultOnMeasure:

public void onMeasure(@NonNull Recycler recycler, @NonNull State state, int widthSpec,int heightSpec) {mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
}

紧接着做了一个判断,如果已经指定确定的宽高或者没有设置Adapter,直接返回。
接下来就是非常重要的步骤了,最主要的两个调用是

dispatchLayoutStep1();dispatchLayoutStep2();

先看dispatchLayoutStep1源码:

/*** 布局的第一个步骤,主要操作如下:* 处理Adapter的更新* 决定哪些动画需要执行* 保存当前View的信息 * 如有必要,进行预布局并保存其信息*/
private void dispatchLayoutStep1() {// 断言,执行此方法的时候必须是处于STEP_START阶段mState.assertLayoutStep(State.STEP_START);...// 执行完成后,更新当前状态为STEP_LAYOUTmState.mLayoutStep = State.STEP_LAYOUT;
}

这里只看注释就好了,关于这些动画啥的,后面专门研究RecyclerView动画的时候再来慢慢看。

再看dispatchLayoutStep2()

/*** 布局的第二个步骤,这个方法里我们会进行针对最终的View状态进行实际布局* 在必要的情况下,这个方法可能会被多次调用*/
private void dispatchLayoutStep2() {startInterceptRequestLayout();onEnterLayoutOrScroll();mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);mAdapterHelper.consumeUpdatesInOnePass();// 通过Adapter获取Item的数量并存到mState中mState.mItemCount = mAdapter.getItemCount();mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;// 布局子ViewmState.mInPreLayout = false;mLayout.onLayoutChildren(mRecycler, mState);mState.mStructureChanged = false;mPendingSavedState = null;// onLayoutChildren may have caused client code to disable item animations; re-checkmState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;// 更新布局的步骤mState.mLayoutStep = State.STEP_ANIMATIONS;onExitLayoutOrScroll();stopInterceptRequestLayout(false);
}

这是实际布局的地方,关键调用是LayoutManager的onLayoutChildren方法。我们知道,Android给我们提供了三个默认的LayoutManager,这里以最简单的LinearLayoutManager为例,看一下它是怎么布局子View的

public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {// 布局算法:// 1) 通过遍历子View和其他变量,找一个锚点Item及其坐标// 2) 往开始方向从底部到顶部填充Item// 3) 往结束方向从顶部到底部填充Item// 4) 滚动以满足要求,例如从底部堆叠// 其中3和4的顺序可能是相反的// 确定布局状态,这部分代码省略...final View focused = getFocusedChild();// 锚点信息无效了、发生滚动了或者恢复View状态(onRestoreInstanceState被调用)后第一次布局// 如果是第一次布局,那么应该会进入这个if分支if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION|| mPendingSavedState != null) {mAnchorInfo.reset();mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;// 计算锚点的位置和坐标updateAnchorInfoForLayout(recycler, state, mAnchorInfo);mAnchorInfo.mValid = true;} else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)>= mOrientationHelper.getEndAfterPadding()|| mOrientationHelper.getDecoratedEnd(focused)<= mOrientationHelper.getStartAfterPadding())) {// 这种情况发生在锚点是当前获取焦点的View,但是由于某种原因布局变小而被挤出屏幕可视范围外了// 比如点击了EditText后软键盘弹起了,而这个EditText被挤出去了// 这种情况将会更新锚点坐标,保证这个获取焦点的View的可见性// 否则layoutState中的可用空间将会被计算成负数,导致获取焦点的View不能展示mAnchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));}if (DEBUG) {Log.d(TAG, "Anchor info:" + mAnchorInfo);}// LinearLayoutManager可能会为“额外”的像素布局Item以考虑滚动目标、缓存或预测动画。// 这部分代码省略...// 先回收Attached状态下的ViewdetachAndScrapAttachedViews(recycler);...// 是否从结束方向往开始方向布局// 两种情况看起来差不多,所以我们主要看最常用的顺着正方向布局的情况if (mAnchorInfo.mLayoutFromEnd) { ...} else {// fill towards endupdateLayoutStateToFillEnd(mAnchorInfo);mLayoutState.mExtraFillSpace = extraForEnd;fill(recycler, mLayoutState, state, false);endOffset = mLayoutState.mOffset;final int lastElement = mLayoutState.mCurrentPosition;if (mLayoutState.mAvailable > 0) {extraForStart += mLayoutState.mAvailable;}// fill towards startupdateLayoutStateToFillStart(mAnchorInfo);mLayoutState.mExtraFillSpace = extraForStart;mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;fill(recycler, mLayoutState, state, false);startOffset = mLayoutState.mOffset;if (mLayoutState.mAvailable > 0) {extraForEnd = mLayoutState.mAvailable;// start could not consume all it should. add more items towards endupdateLayoutStateToFillEnd(lastElement, endOffset);mLayoutState.mExtraFillSpace = extraForEnd;fill(recycler, mLayoutState, state, false);endOffset = mLayoutState.mOffset;}}...
}

这里面有两个关键调用,分别是detachAndScrapAttachedViews(recycler)和fill(recycler, mLayoutState, state, false)。先来看detachAndScrapAttachedViews

/*** 临时回收所有的Attached状态下的子View。这些View会被回收到给定的Recycler对象中。* Recycler会优先复用最后回收的View*/ 
public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {final int childCount = getChildCount();for (int i = childCount - 1; i >= 0; i--) {final View v = getChildAt(i);scrapOrRecycleView(recycler, i, v);}
}

关于detachAndScrapAttachedViews方法我们先只看到这里,了解它的作用,后续分析Recycler类以及RecyclerView的回收机制时再展开分析。

接下来看一下fill方法

/*** 填充给定的布局。这个方法相对于LinearLayoutManager的其他部分来说逻辑是独立的,稍微改一下的话可以作为公共的帮助类* @return 返回填充的像素*/ 
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,RecyclerView.State state, boolean stopOnFocusable) {// max offset we should set is mFastScroll + availablefinal int start = layoutState.mAvailable;if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {// TODO ugly bug fix. should not happenif (layoutState.mAvailable < 0) {layoutState.mScrollingOffset += layoutState.mAvailable;}recycleByLayoutState(recycler, layoutState);}int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;LayoutChunkResult layoutChunkResult = mLayoutChunkResult;while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {layoutChunkResult.resetInternal();if (RecyclerView.VERBOSE_TRACING) {TraceCompat.beginSection("LLM LayoutChunk");}layoutChunk(recycler, state, layoutState, layoutChunkResult);if (RecyclerView.VERBOSE_TRACING) {TraceCompat.endSection();}if (layoutChunkResult.mFinished) {break;}layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;/*** Consume the available space if:* * layoutChunk did not request to be ignored* * OR we are laying out scrap children* * OR we are not doing pre-layout*/if (!layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList != null|| !state.isPreLayout()) {layoutState.mAvailable -= layoutChunkResult.mConsumed;// we keep a separate remaining space because mAvailable is important for recyclingremainingSpace -= layoutChunkResult.mConsumed;}if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {layoutState.mScrollingOffset += layoutChunkResult.mConsumed;if (layoutState.mAvailable < 0) {layoutState.mScrollingOffset += layoutState.mAvailable;}recycleByLayoutState(recycler, layoutState);}if (stopOnFocusable && layoutChunkResult.mFocusable) {break;}}if (DEBUG) {validateChildOrder();}return start - layoutState.mAvailable;
}void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,LayoutState layoutState, LayoutChunkResult result) {// 获取子ViewView view = layoutState.next(recycler);if (view == null) {if (DEBUG && layoutState.mScrapList == null) {throw new RuntimeException("received null view when unexpected");}// if we are laying out views in scrap, this may return null which means there is// no more items to layout.result.mFinished = true;return;}RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();// 添加了子Viewif (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);}}// 测量子ViewmeasureChildWithMargins(view, 0, 0);...// 布局子ViewlayoutDecoratedWithMargins(view, left, top, right, bottom);...
}

可以看到,子View的创建、添加、测量和布局都是在layoutChunk中完成的!

2.3 LayoutManager没有开启自动测量

如下代码

protected void onMeasure(int widthSpec, int heightSpec) {...if (mLayout.isAutoMeasureEnabled()) {...} else {if (mHasFixedSize) {mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);return;}// 自定义测量流程开始if (mAdapterUpdateDuringMeasure) {  // Adapter在测量过程中更新了startInterceptRequestLayout();onEnterLayoutOrScroll();processAdapterUpdatesAndSetAnimationFlags();onExitLayoutOrScroll();if (mState.mRunPredictiveAnimations) {mState.mInPreLayout = true;} else {// 完成剩余的更新以提供与布局传递一致的状态mAdapterHelper.consumeUpdatesInOnePass();mState.mInPreLayout = false;}mAdapterUpdateDuringMeasure = false;stopInterceptRequestLayout(false);} else if (mState.mRunPredictiveAnimations) {// 如果mAdapterUpdateDuringMeasure是false且mRunPredictiveAnimations是true// 这意味着已经调用onMeasure来处理Adapter的更新了// 当RecyclerView是LinearLayout的子View且layout_width=MATCH_PARENT,onMeasure会调用两次// RecyclerView不能多次调用LayoutManager.onMeasure,因为LayoutManager测量子View的时候getViewForPosition()会崩溃setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight());return;}if (mAdapter != null) {mState.mItemCount = mAdapter.getItemCount();} else {mState.mItemCount = 0;}startInterceptRequestLayout();mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);stopInterceptRequestLayout(false);mState.mInPreLayout = false; // 清除标记}
}

LayoutManager没有开启自动测量又分为两种情况,第一种是RecyclerView指定了确切的宽高,那这就不用说了,直接使用指定的宽高就可以了;第二种是没有指定确切的宽高,做了两个判断,分别进行了一些处理,然后关键是调用如下:

startInterceptRequestLayout();
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
stopInterceptRequestLayout(false);

Android提供的三种现有的LayoutManager默认都是开启了自动布局的,所以这种情况了解一下就可以啦。

3、onLayout流程

看看onLayout方法

protected void onLayout(boolean changed, int l, int t, int r, int b) {TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);dispatchLayout();TraceCompat.endSection();mFirstLayoutComplete = true;
}

这里调用了dispatchLayout(),然后把mFirstLayoutComplete置为true。那我们再来看看dispatchLayout()

void dispatchLayout() {// 著名的log打印...if (mAdapter == null) {Log.e(TAG, "No adapter attached; skipping layout");// leave the state in STARTreturn;}if (mLayout == null) {Log.e(TAG, "No layout manager attached; skipping layout");// leave the state in STARTreturn;}mState.mIsMeasuring = false;// 根据前面的分析,如果调用过dispatchLayoutStep1(),mState.mLayoutStep == State.STEP_START就不会成立// 所以只有之前没有调用过,才会进入if分支if (mState.mLayoutStep == State.STEP_START) {dispatchLayoutStep1();mLayout.setExactMeasureSpecsFrom(this);dispatchLayoutStep2();} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()|| mLayout.getHeight() != getHeight()) {// 如果有更新过,那就要重新进行第二步mLayout.setExactMeasureSpecsFrom(this);dispatchLayoutStep2();} else {// always make sure we sync them (to ensure mode is exact)mLayout.setExactMeasureSpecsFrom(this);}dispatchLayoutStep3();
}

这里根据具体情况判断是否需要调用dispatchLayoutStep1和dispatchLayoutStep2,最后调用了dispatchLayoutStep3。前两个当法我们已经分析过了,看一下dispatchLayoutStep3

/*** 布局的最后一个步骤,在这个方法里会保存执行动画的View的信息、触发动画以及执行必要的清理操作*/
private void dispatchLayoutStep3() {...mState.mLayoutStep = State.STEP_START;...
}

这里也只看注释就好了,后续分析动画的时候再具体分析。

4、draw和onDraw

RecyclerView不仅重写了onDraw方法,也重写了draw方法,一起来看看吧

public void draw(Canvas c) {super.draw(c);final int count = mItemDecorations.size();for (int i = 0; i < count; i++) {mItemDecorations.get(i).onDrawOver(c, this, mState);}// TODO If padding is not 0 and clipChildrenToPadding is false, to draw glows properly, we// need find children closest to edges. Not sure if it is worth the effort.boolean needsInvalidate = false;if (mLeftGlow != null && !mLeftGlow.isFinished()) {final int restore = c.save();final int padding = mClipToPadding ? getPaddingBottom() : 0;c.rotate(270);c.translate(-getHeight() + padding, 0);needsInvalidate = mLeftGlow != null && mLeftGlow.draw(c);c.restoreToCount(restore);}if (mTopGlow != null && !mTopGlow.isFinished()) {final int restore = c.save();if (mClipToPadding) {c.translate(getPaddingLeft(), getPaddingTop());}needsInvalidate |= mTopGlow != null && mTopGlow.draw(c);c.restoreToCount(restore);}if (mRightGlow != null && !mRightGlow.isFinished()) {final int restore = c.save();final int width = getWidth();final int padding = mClipToPadding ? getPaddingTop() : 0;c.rotate(90);c.translate(-padding, -width);needsInvalidate |= mRightGlow != null && mRightGlow.draw(c);c.restoreToCount(restore);}if (mBottomGlow != null && !mBottomGlow.isFinished()) {final int restore = c.save();c.rotate(180);if (mClipToPadding) {c.translate(-getWidth() + getPaddingRight(), -getHeight() + getPaddingBottom());} else {c.translate(-getWidth(), -getHeight());}needsInvalidate |= mBottomGlow != null && mBottomGlow.draw(c);c.restoreToCount(restore);}// If some views are animating, ItemDecorators are likely to move/change with them.// Invalidate RecyclerView to re-draw decorators. This is still efficient because children's// display lists are not invalidated.if (!needsInvalidate && mItemAnimator != null && mItemDecorations.size() > 0&& mItemAnimator.isRunning()) {needsInvalidate = true;}if (needsInvalidate) {ViewCompat.postInvalidateOnAnimation(this);}
}public void onDraw(Canvas c) {super.onDraw(c);final int count = mItemDecorations.size();for (int i = 0; i < count; i++) {mItemDecorations.get(i).onDraw(c, this, mState);}
}

由于super.draw©里面调用了onDraw方法,所以onDraw里的自定义逻辑会先于draw里面的自定义逻辑执行。我们看到,onDraw里面主要是遍历了mItemDecorations,挨个调用它们的onDraw方法;在draw里面又遍历了mItemDecorations,挨个调用它们的onDrawOver方法,然后根据一系列的条件得出一个布尔值needsInvalidate,标记下次动画的时候是否刷新

5、总结

RecyclerView的三大流程概括如下:

  1. measure:

    • 如果没有设置自动LayoutManager,计算默认宽高即返回
    • 如果指定了确切的宽高,使用指定的宽高
    • 如果LayoutManager开启了自动布局,执行dispatchLayoutStep1()和dispatchLayoutStep2(),其中dispatchLayoutStep2()可能会重复执行
    • 如果LayoutManager没有开启自动布局,那么委托LayoutManager的onMeasure来进行
  2. layout:

    • 根据布局阶段决定是否需要执行dispatchLayoutStep1和dispatchLayoutStep2
    • 执行dispatchLayoutStep3
  3. draw:

    • 除了正常ViewGroup的draw流程,还先后遍历了mItemDecorations并执行了它们的onDraw和onDrawOver方法

其中测量和布局有三大步骤,分别是dispatchLayoutStep1、dispatchLayoutStep2、dispatchLayoutStep3,其中1和3更多的是与动画或者预布局相关的事情,而子View的创建、测量、布局和添加都在2中。
我们还可以得出结论,RecyclerView的子View的布局不一定是在RecyclerView的onLayout方法中执行。
OK,本文分析到这,下篇继续…

这篇关于RecyclerView源码分析(一):RecyclerView的三大流程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1105637

相关文章

Security OAuth2 单点登录流程

单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自独立的软件系统,提供访问控制的属性。当拥有这项属性时,当用户登录时,就可以获取所有系统的访问权限,不用对每个单一系统都逐一登录。这项功能通常是以轻型目录访问协议(LDAP)来实现,在服务器上会将用户信息存储到LDAP数据库中。相同的,单一注销(single sign-off)就是指

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

SWAP作物生长模型安装教程、数据制备、敏感性分析、气候变化影响、R模型敏感性分析与贝叶斯优化、Fortran源代码分析、气候数据降尺度与变化影响分析

查看原文>>>全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用 SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了WOFOST作物模型使作物的生长描述更为科学。 本文让更多的科研人员和农业工作者

MOLE 2.5 分析分子通道和孔隙

软件介绍 生物大分子通道和孔隙在生物学中发挥着重要作用,例如在分子识别和酶底物特异性方面。 我们介绍了一种名为 MOLE 2.5 的高级软件工具,该工具旨在分析分子通道和孔隙。 与其他可用软件工具的基准测试表明,MOLE 2.5 相比更快、更强大、功能更丰富。作为一项新功能,MOLE 2.5 可以估算已识别通道的物理化学性质。 软件下载 https://pan.quark.cn/s/57

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

衡石分析平台使用手册-单机安装及启动

单机安装及启动​ 本文讲述如何在单机环境下进行 HENGSHI SENSE 安装的操作过程。 在安装前请确认网络环境,如果是隔离环境,无法连接互联网时,请先按照 离线环境安装依赖的指导进行依赖包的安装,然后按照本文的指导继续操作。如果网络环境可以连接互联网,请直接按照本文的指导进行安装。 准备工作​ 请参考安装环境文档准备安装环境。 配置用户与安装目录。 在操作前请检查您是否有 sud