From View To Jetpack Compose 原生 View 在 Android APP 中,所有的用户界面元素都是由 View 和 ViewGroup 的对象构成的。View 是绘制在屏幕上的用户能与之交互的一个对象。而 ViewGroup 则是一个用于存放其他 View(和 ViewGroup)对象的布局容器。 Android 为我们提供了一个 View 和 ViewGroup 子类的集合,集合中提供了一些常用的输入控件 (比如按钮和文本域) 和各种各样的布局模式。
原生 View 显示到我们的手机上面主要通过 Measure、layout、Draw 过程。
Window、Activity、DecorView、ViewRoot Activity
Activity 并不负责视图控制,它只是控制生命周期和处理事件。真正控制视图的是 Window。一个 Activity 包含了一个 Window,Window 才是真正代表一个窗口。Activity 就像一个控制器,统筹视图的添加与显示,以及通过其他回调方法,来与 Window、以及 View 进行交互。
Window
Window 是视图的承载器,内部持有一个 DecorView ,而这个 DecorView 才是 view 的根布局。Window 是一个抽象类,实际在 Activity 中持有的是其子类 PhoneWindow。PhoneWindow 中有个内部类 DecorView,通过创建 DecorView 来加载 Activity 中设置的布局 R.layout.activity_main
。Window 通过 WindowManager 将 DecorView 加载其中,并将 DecorView 交给 ViewRoot,进行视图绘制以及其他交互。
DecorView
DecorView 是 FrameLayout 的子类,它可以被认为是 Android 视图树的根节点视图。DecorView 作为顶级 View,一般情况下它内部包含一个竖直方向的 LinearLayout,**在这个 LinearLayout 里面有上下三个部分,上面是个 ViewStub,延迟加载的视图(应该是设置 ActionBar,根据 Theme 设置),中间的是标题栏 (根据 Theme 设置,有的布局没有),下面的是内容栏 **
ViewRoot
ViewRoot 主要处理 View 的绘制以及事件分发等交互。
ViewRoot 对应 **ViewRootImpl 类,它是连接 WindowManagerService 和 DecorView 的纽带 **,View 的三大流程(测量(measure),布局(layout),绘制(draw))均通过 ViewRoot 来完成。
setContentView 在 View 系统中,我们所编写的 xml 文件通过 setContentView
方法装载到视图。
1 2 3 4 public void setContentView (@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); }
可以看到我们的 xml
文件是给到了 Window
去装载视图,即通过 Activity
持有的 PhoneWindow
中的 DecorView
装载
1 2 3 4 5 6 7 8 9 10 11 12 public void setContentView (int layoutResID) { if (mContentParent == null ) { installDecor(); } else { mContentParent.removeAllViews(); } mLayoutInflater.inflate(layoutResID, mContentParent); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } }
这个 Window
通过 installDecor
创建 DecorView
,并且把我们的 xml
文件装载到 mContentParent
,这个 mContentParen
t 就是 DecorView
中的 Content
那部分的 FrameLayout
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 private void installDecor () { if (mDecor == null ) { mDecor = generateDecor(); mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); mDecor.setIsRootNamespace(true ); if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0 ) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); } } if (mContentParent == null ) { mContentParent = generateLayout(mDecor); ... } } }
以上还只是加载了布局文件,DecorView
真正显示还要到 Activity
的 onResume
过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 final void handleResumeActivity (IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) { ActivityClientRecord r = performResumeActivity(token, clearHide); if (r != null ) { final Activity a = r.activity; if (r.window == null && !a.mFinished && willBeVisible) { r.window = r.activity.getWindow(); View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; if (a.mVisibleFromClient) { a.mWindowAdded = true ; wm.addView(decor, l); } if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) { if (r.activity.mVisibleFromClient) { r.activity.makeVisible(); } } } } }
可以看到这里在调用 makeVisible
方法之后才真正可见,在 makeVisible
方法中兜兜转转到最后 WindowManager
中的 ViewRoot
中去 measure
、layout
、draw
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public void setView (View view, WindowManager.LayoutParams attrs, View panelParentView) { ... requestLayout(); ... try { ... res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mInputChannel); } }
ViewRootImpl 调用到 requestLayout 来完成 View 的绘制操作
1 2 3 4 5 6 7 8 @Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); } }
View 绘制,先判断当前线程
1 2 3 4 5 6 void checkThread () { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException ( "Only the original thread that created a view hierarchy can touch its views." ); } }
最后兜兜转转到了 performTraversals 方法
1 2 3 4 5 6 7 8 9 10 private void performTraversals () { ...... performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ... performLayout(lp, desiredWindowWidth, desiredWindowHeight); ...... performDraw(); } ...... }
Measure Android View 的测量过程中使用到了 MeasureSpec(测量规格)。
其中,Mode 模式共分为三类:
UNSPECIFIED :不对 View 进行任何限制,要多大给多大,一般用于系统内部
EXACTLY :对应 LayoutParams 中的 match_parent 和具体数值这两种模式。检测到 View 所需要的精确大小,这时候 View 的最终大小就是 SpecSize 所指定的值,
AT_MOST :对应 LayoutParams 中的 wrap_content。View 的大小不能大于父容器的大小。
MeasureSpec 对于 View 的 MeasureSpec,其确定是通过父布局的 MeasureSpec 和自身的布局参数 LayoutParams
以上看来,View 的 MeasureSpec 要通过父容器 ViewGroup 的 getChildMeasureSpec 方法获取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 public static int getChildMeasureSpec (int spec, int padding, int childDimension) { int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); int size = Math.max(0 , specSize - padding); int resultSize = 0 ; int resultMode = 0 ; switch (specMode) { case MeasureSpec.EXACTLY: if (childDimension>= 0 ) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { resultSize = size; resultMode = MeasureSpec.AT_MOST; } break ; case MeasureSpec.AT_MOST: if (childDimension>= 0 ) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { resultSize = size; resultMode = MeasureSpec.AT_MOST;l } else if (childDimension == LayoutParams.WRAP_CONTENT) { resultSize = size; resultMode = MeasureSpec.AT_MOST; } break ; case MeasureSpec.UNSPECIFIED: if (childDimension>= 0 ) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { resultSize = 0 ; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { resultSize = 0 ; resultMode = MeasureSpec.UNSPECIFIED; } break ; } return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
首先我们来看 ViewGroup 的 measure
过程,对于 ViewGroup 没有 onMeasure
方法,只有 measureChildren
方法
1 2 3 4 5 6 7 8 9 10 protected void measureChildren (int widthMeasureSpec, int heightMeasureSpec) { final int size = mChildrenCount; final View[] children = mChildren; for (int i = 0 ; i < size; ++i) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } }
1 2 3 4 5 6 7 8 9 10 11 protected void measureChild (View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
对于 View 的 measure
过程主要在于 onMeasure
方法,通过 setMeasuredDimension
方法设置测量值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } public static int getDefaultSize (int size, int measureSpec) { int result = size; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) { case MeasureSpec.UNSPECIFIED: result = size; break ; case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = specSize; break ; } return result; }
以上 getDefaultSize
方法可以看到当 mode 为 AT_MOST 或者 EXACTLY 时,其实都是 specSize,但是结合上面的表我们可以知道,这时的 specSize 其实都是 parentSize,所以当我们自定义 View 的时候我们要重写 onMeasure
方法。
那么 ViewGroup 如何 Measure 自身呢?对于不同的 ViewGroup,Measure 方式都有所不同,具体我们等会自定义 View 的时候可以细看。
Layout 测量完 View 大小之后,需要将 View 布局在 Window 中,View 的布局主要通过上下左右四个点来确定。
其中布局也是自上而下,不同的是 ViewGroup 先在 layout() 中确定自己的布局,然后在 onLayout() 方法中再调用子 View 的 layout() 方法,让子 View 布局。在 Measure 过程中,ViewGroup 一般是先测量子 View 的大小,然后再确定自身的大小。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public void layout (int l, int t, int r, int b) { int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight; boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); ... }
onLayout
是一个可重写的方法,如果当前 View 就是一个单一的 View,那么没有子 View,就不需要实现该方法。
如果当前 View 是一个 ViewGroup,就需要实现 onLayout 方法,在自定义 ViewGroup 的时候就需要自行实现 onLayout 方法布局子 View
Draw View 的绘制过程遵循如下几步:
绘制背景 background.draw(canvas)
绘制自己(onDraw)
绘制 Children(dispatchDraw)
绘制装饰(onDrawScrollBars)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public void draw (Canvas canvas) { ... int saveCount; if (!dirtyOpaque) { drawBackground(canvas); } final int viewFlags = mViewFlags; if (!verticalEdges && !horizontalEdges) { if (!dirtyOpaque) onDraw(canvas); dispatchDraw(canvas); ........ onDrawScrollBars(canvas); .......... return ; } ... }
无论是 ViewGroup 还是单一的 View,都需要实现这套流程,不同的是,在 ViewGroup 中,实现了 dispatchDraw() 方法,而在单一子 View 中不需要实现该方法。自定义 View 一般要重写 onDraw() 方法,在其中绘制不同的样式。
自定义 View 有了前面的知识,我们可以着手实现一个自己 ViewGroup 和 View。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 public class MyViewGroup extends ViewGroup { @SuppressLint("DrawAllocation") @Override protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) { super .onMeasure(widthMeasureSpec, heightMeasureSpec); final int childrenCount = getChildCount(); final int paddingLeft = getPaddingLeft(); final int paddingRight = getPaddingRight(); final int paddingTop = getPaddingTop(); final int paddingBottom = getPaddingBottom(); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int marginLeft = 0 ; int marginTop = 0 ; int marginRight = 0 ; int marginBottom = 0 ; int allChildrenWidth = 0 ; int maxChildrenHeight = 0 ; for (int i = 0 ; i < childrenCount; i++) { View childView = getChildAt(i); ViewGroup.LayoutParams params = childView.getLayoutParams(); ViewGroup.MarginLayoutParams marginParams; if (params instanceof ViewGroup.MarginLayoutParams) { marginParams = (ViewGroup.MarginLayoutParams) params; } else { marginParams = new ViewGroup .MarginLayoutParams(params); } measureChild(childView, widthMeasureSpec, heightMeasureSpec); maxChildrenHeight = Math.max(maxChildrenHeight, childView.getMeasuredHeight()); allChildrenWidth += childView.getMeasuredWidth(); marginLeft += marginParams.leftMargin; marginRight += marginParams.rightMargin; marginBottom = Math.max(marginBottom, marginParams.bottomMargin); marginTop = Math.max(marginTop, marginParams.topMargin); } if (childrenCount == 0 ) { setMeasuredDimension(100 , 100 ); } else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) { setMeasuredDimension(allChildrenWidth + paddingLeft + paddingRight + marginRight + marginLeft, maxChildrenHeight + paddingTop + paddingBottom + marginTop + marginBottom); } else if (widthMode == MeasureSpec.AT_MOST) { setMeasuredDimension(allChildrenWidth + paddingLeft + paddingRight + marginRight + marginLeft, heightSize); } else if (heightMode == MeasureSpec.AT_MOST) { setMeasuredDimension(widthSize, maxChildrenHeight + paddingTop + paddingBottom + marginTop + marginBottom); } } @SuppressLint("DrawAllocation") @Override protected void onLayout (boolean b, int i, int i1, int i2, int i3) { final int paddingLeft = getPaddingLeft(); final int paddingTop = getPaddingTop(); final int childrenCount = getChildCount(); int childLeft = paddingLeft; for (int j = 0 ; j < childrenCount; j++) { View childView = getChildAt(j); ViewGroup.LayoutParams params = childView.getLayoutParams(); ViewGroup.MarginLayoutParams marginParams; if (params instanceof ViewGroup.MarginLayoutParams) { marginParams = (ViewGroup.MarginLayoutParams) params; } else { marginParams = new ViewGroup .MarginLayoutParams(params); } if (childView.getVisibility() != GONE) { childView.layout(childLeft + marginParams.leftMargin, marginParams.topMargin + paddingTop, childLeft + childView.getMeasuredWidth(), childView.getMeasuredHeight() + paddingTop + marginParams.topMargin); childLeft += (childView.getMeasuredWidth() + marginParams.rightMargin); } } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class MyView extends View { @Override protected void onDraw (Canvas canvas) { super .onDraw(canvas); int width = getWidth() - paddingLeft - paddingRight; int height = getHeight() - paddingBottom - paddingTop; @SuppressLint("DrawAllocation") Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.archlinux_logo_icon); @SuppressLint("DrawAllocation") Rect src = new Rect (paddingLeft, paddingTop, paddingLeft + width, paddingTop + height); canvas.drawBitmap(bitmap, src, src, mPaint); } @Override protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) { super .onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) { setMeasuredDimension(200 , 200 ); } else if (widthMode == MeasureSpec.AT_MOST) { setMeasuredDimension(200 , heightSize); } else if (heightMode == MeasureSpec.AT_MOST) { setMeasuredDimension(widthSize, 200 ); } } }
总结如下:
从 View 的测量、布局和绘制原理来看,要实现自定义 View,根据自定义 View 的种类不同,可能分别要自定义实现不同的方法。但是这些方法不外乎:onMeasure() 方法,onLayout() 方法,onDraw() 方法。
**onMeasure() 方法 **:单一 View,一般重写此方法,针对 wrap_content 情况,规定 View 默认的大小值,避免于 match_parent 情况一致。ViewGroup,若不重写,就会执行和单子 View 中相同逻辑,不会测量子 View。一般会重写 onMeasure() 方法,循环测量子 View。
onLayout() 方法: 单一 View,不需要实现该方法。ViewGroup 必须实现,该方法是个抽象方法,实现该方法,来对子 View 进行布局。
onDraw() 方法: 无论单一 View,或者 ViewGroup 都可以实现该方法。
Jetpack Compose Jetpack Compose 是用于构建原生 Android 界面的新工具包。它可简化并加快 Android 上的界面开发,使用更少的代码、强大的工具和直观的 Kotlin API,快速打造生动而精彩的应用。
更少的代码、直观、加速开发、功能强大
声明式 UI 在传统 View 中,我们通常需要使用 findViewById
方法拿到控件之后,再通过 setText、setImageBitmap
等方法更新界面的数据,这样手动操纵视图的方法会提高出错的可能性。如果一条数据在多个位置呈现,很容易忘记更新显示它的某个视图。此外,当两项更新以出人意料的方式发生冲突时,也很容易造成异常状态。
而 Jetpack Compose 作为声明式 UI,其技术的工作原理是在概念上从头开始重新生成整个屏幕,然后执行必要的更改,我们只需要声明每个控件需要的状态,在我们更新状态的时候,相应的控件会发生 重组 ,此方法可避免手动更新有状态视图层次结构的复杂性。
简单使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Composeable fun Content () { var state by remember {mutableStateOf(0 ) } StateContent(state){ state = state +1 } } @Composable fun StateContent (state:Int , add: () -> Unit ) { Column { Text(text = state.toString()) Button(onClick = { add() }) { Text("Add" ) } } }
配合 ViewModel 和 Flow 食用 上面的例子还是不够 MVVM,我们的 State 与事件应该保存在 ViewModel 中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class MainViewModel : ViewModel () { private val _state = MutableStateFlow(0 ) val state = _state fun addNum () { _state.value = _state.value + 1 } } @Composable fun Content (mainViewModel: MainViewModel ) { val state by mainViewModel.state.collectAsState(initial = 0 ) StateContent(state = state) { mainViewModel.addNum() } } @Composable fun StateContent (state: Int , addNum: () -> Unit ) { Column { Text(text = state.toString()) Button(onClick = { addNum() }) { Text("Add" ) } } }
以上的做法是 Google 官方推崇的最佳架构,即 数据单向流通,唯一可信源,状态向下传递,事件向上传递
重组原理以及性能优化 Composable 的本质 在 Compose 中,普通的函数通过 Composable 注解就变成了构建 UI 的可组合函数,具体其是通过 Compose Compiler Plugin 实现的。
1 2 3 4 @Composable fun Greeting (msg: String ) { Text(text = "Hello $msg !" ) }
通过 Compose Compiler Plugin 将 @Composable Greeting(msg:String) 编译成了 Greeting(final String msg, Composer $composer, final int $changed)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public static final void Greeting(final String msg, Composer $composer, final int $changed) { $composer = $composer.startRestartGroup(-1948405856 ); int $dirty = $changed; if (($changed & 14 ) == 0 ) { $dirty = $changed | ($composer.changed(msg) ? 4 : 2 ); } if (($dirty & 11 ) == 2 && $composer.getSkipping()) { $composer.skipToGroupEnd(); } else { TextKt.Text-fLXpl1I(msg, $composer, 0 , 0 , 65534 ); } ScopeUpdateScope var10000 = $composer.endRestartGroup(); if (var10000 != null ) { var10000.updateScope((Function2)(new Function2() { public final void invoke(@Nullable Composer $composer, int $force) { MainActivityKt.Greeting(msg, $composer, $changed | 1 ); } })); } }
以上表明每个 Composable 函数其实都对应了一个 ScopeUpdateScope,通过监听来实现重组。
其中 Compose 中 State 的读、写行为是通过 Snapshot(状态快照系统)监听。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 @Stable interface MutableState <T > : State <T > { override var value: T } inline operator fun <T> MutableState<T> .setValue (thisObj: Any ?, property: KProperty <*>, value: T ) { this .value = value } internal open class SnapshotMutableStateImpl <T >( value: T, override val policy: SnapshotMutationPolicy<T> ) : StateObject, SnapshotMutableState<T> { override var value: T get () = next.readable(this ).value set (value) = next.withCurrent { if (!policy.equivalent(it.value, value)) { next.overwritable(this , it) { this .value = value } } } private var next: StateStateRecord<T> = StateStateRecord(value) } internal inline fun <T : StateRecord, R> T.overwritable ( state: StateObject , candidate: T , block: T .() -> R ) : R { var snapshot: Snapshot = snapshotInitializer return sync { snapshot = Snapshot.current this .overwritableRecord(state, snapshot, candidate).block() }.also { notifyWrite(snapshot, state) } }
本质上,它其实就是通过自定义 Getter、Setter 来实现的。当我们定义的 state 变量变化的时候,Compose 可以通过自定义的 Setter 捕获到这一行为,从而调用 ScopeUpdateScope 当中的监听,触发重组。
然后我们看到上面的 notifyWrite 方法
1 2 3 4 @PublishedApi internal fun notifyWrite (snapshot: Snapshot , state: StateObject ) { snapshot.writeObserver?.invoke(state) }
这里可以看到快照系统拥有 writeObserver ,事实上他还有 readObserver
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 private inline fun <T> composing ( composition: ControlledComposition , modifiedValues: IdentityArraySet <Any >?, block: () -> T ) : T { val snapshot = Snapshot.takeMutableSnapshot( readObserverOf(composition), writeObserverOf(composition, modifiedValues) ) try { return snapshot.enter(block) } finally { applyAndCheck(snapshot) } } override fun recordReadOf (value: Any ) { if (!areChildrenComposing) { composer.currentRecomposeScope?.let { it.used = true observations.add(value, it) ... } } } override fun recordWriteOf (value: Any ) = synchronized(lock) { invalidateScopeOfLocked(value) derivedStates.forEachScopeOf(value) { invalidateScopeOfLocked(it) } } private fun invalidateScopeOfLocked (value: Any ) { observations.forEachScopeOf(value) { scope -> if (scope.invalidateForResult(value) == InvalidationResult.IMMINENT) { observationsProcessed.add(value, scope) } } }
readObserverOf
来记录哪些 scope
使用了此 state
,而 writeObserverOf
而写入时会找出对应使用此 state
的 scope
使其 invalidate
,然后对于这些 scope
执行重组
性能优化 有了以上知识,我们就可以理解官方文档性能优化中的尽可能延后读取 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Composable fun SnackDetail () { Box(Modifier.fillMaxSize()) { val scroll = rememberScrollState(0 ) Title(snack, scroll.value) } } @Composable private fun Title (snack: Snack , scroll: Int ) { val offset = with(LocalDensity.current) { scroll.toDp() } Column( modifier = Modifier .offset(y = offset) ) { } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Composable fun SnackDetail () { Box(Modifier.fillMaxSize()) { val scroll = rememberScrollState(0 ) Title(snack) { scroll.value } } } @Composable private fun Title (snack: Snack , scrollProvider: () -> Int ) { val offset = with(LocalDensity.current) { scrollProvider().toDp() } Column( modifier = Modifier .offset(y = offset) ) { } }
状态读取发生在哪个 Scope,状态更新的时候,哪个 Scope 就发生重组 。