ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Litho ComponentTree 深度解析:线程安全地管理 Android 组件树生命周期

Litho ComponentTree 深度解析:线程安全地管理 Android 组件树生命周期 移动开发UI组件【免费下载链接】lithoA declarative framework for building efficient UIs on Android.项目地址https://gitcode.com/gh_mirrors/li/litho点击查看免费下载ComponentTree是 Litho 中代表一棵组件树、并负责其完整生命周期的核心对象它以单个根组件为输入递归调用组件的OnCreateLayout构建整棵组件树并在新 props 或 state 到来时刷新已挂载mounted的状态。本文将以官方文档 componenttree.md 为主线结合 Litho 源码ComponentTree.java、LithoView.java深入讲解何时需要手动创建ComponentTree、如何通过 Builder 精细配置它、如何将其挂载到LithoView以及它在线程安全与异步布局方面的底层机制。读完本文你将掌握ComponentTree.create(...)的完整用法与 Builder 全部配置项、setComponentTree/setRoot/setSizeSpec/updateState等关键 API 的线程语义以及 Litho 默认布局线程与异步布局的内部运行方式。一、什么是 ComponentTree从 LithoView 到组件树在 Using Components 指南中我们了解到只要把一个根组件交给LithoViewLithoView就会自动完成其余工作——它本质上是一个能够渲染 Litho 组件的 AndroidViewGroup。LithoView背后真正管理组件的是ComponentTree。查看 LithoView.java 的工厂方法可以看到LithoView.create(...)内部实际上就是在替你做这件事// LithoView.create(Context context, Component component) 的内部实现源码简化 lithoView.setComponentTree(ComponentTree.create(context, component).build());从源码类注释ComponentTree.java可以明确它的定位Represents a tree of components and controls their life cycle. ComponentTree takes in a single root component and recursively invokes its OnCreateLayout to create a tree of components. ComponentTree is responsible for refreshing the mounted state of a component with new props.即ComponentTree接收一个根组件递归调用其OnCreateLayout生成组件树当组件收到新的 props 或 state 更新时负责刷新已挂载的视图状态。ComponentTree的线程安全是其最核心的特性之一——源码中以ThreadSafe注解ComponentTree.java意味着你可以从任意线程创建它、并向它发起调用。这与传统 Android View 必须主线程操作的约束形成鲜明对比也是 Litho 能在后台线程完成 measure/layout 的基础。二、为什么通常不需要手动创建 ComponentTree按照官方文档的原话You shouldnt typically need to do this, as you usually provide a component to your LithoView instead as shown in Using Components. However, there are situations where you might want to create and manage your ownComponentTree.绝大多数场景下直接把组件交给LithoView就够了代码更简洁LithoView view LithoView.create(c, component); // 或 LithoView view new LithoView(this); view.setRoot(component);这两种写法都会由LithoView内部自动创建并管理ComponentTree你无需关心其生命周期细节。那么什么情况下值得绕过这层便利、亲手创建一个ComponentTree从源码暴露的 API 可以归纳出以下典型场景需要动态更换整棵树的根组件ComponentTree.setRoot(...)/setRootSync(...)/setRootAsync(...)ComponentTree.java允许你显式控制何时、以何种方式替换根组件而LithoView.setRoot的封装更隐式。需要精细控制布局线程通过 Builder 的layoutThreadLooper(...)/layoutThreadHandler(...)指定专用的 Looper 或 Handler改变默认的后台布局线程。需要注入初始 TreeState / StateUpdater / 监听器例如通过treeState(...)为组件树预置 state 初始值或通过measureListener(...)监听测量结果。需要让多个视图复用同一棵组件树手动创建的ComponentTree可以在合适的情况下被共享LithoView.java 注释提到当视图从窗口分离时会暂存 ComponentTree以备共享复用。深度集成需要需要读取/控制ComponentTree当前 root、尺寸规格size spec、是否已释放isReleased()等内部状态或订阅NewLayoutStateReadyListener感知新布局就绪的时刻。三、创建 ComponentTreecreate() 与 Builder创建ComponentTree的入口是静态方法ComponentTree.create(...)它返回一个 Builder。源码中共有四个重载ComponentTree.java重载说明create(ComponentContext context)仅传上下文root 稍后通过withRoot(...)设置若未设置build()会默认使用EmptyComponent见 build()create(ComponentContext context, Component.Builder? root)传入已生成的组件 Builder最常见用法create(ComponentContext context, Component root)直接传入已 build 的组件实例create(ComponentContext context, Component root, ...)内部使用源码中用于带额外参数创建官方文档给出了一个完整的、可直接运行的最小示例——在 Activity 中手动创建ComponentTree并交给LithoViewOverride public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final LithoView lithoView new LithoView(this); final ComponentContext c new ComponentContext(this); final Component text Text.create(c) .text(Hello World) .textSizeDip(50) .build(); final ComponentTree componentTree ComponentTree.create(c, text).build(); lithoView.setComponentTree(componentTree); setContentView(lithoView); }这个示例虽然只有几行但完整覆盖了三条关键链路值得逐行拆解new LithoView(this)new ComponentContext(this)LithoView 是承载渲染的ViewGroupComponentContext则封装了 Android Context 以及 Litho 的配置如ComponentsConfiguration见 Builder 构造函数 中对context.mLithoConfiguration.componentsConfig的读取。Text.create(c).text(...).textSizeDip(50).build()构建一个渲染 50dp Hello World 文本的组件实例。ComponentTree.create(c, text).build()lithoView.setComponentTree(componentTree)创建组件树并挂载到视图。setComponentTreeLithoView.java内部会完成新旧树切换、挂载与卸载unmount管理注意它不允许挂载一个已release()的ComponentTree。四、Builder 配置项全解源码级ComponentTree.Builder是官方文档指出的暴露配置方法的入口。结合 ComponentTree.java 的实现Builder 支持以下配置方法方法作用与注意事项withRoot(Component root)指定树的根组件。传null会抛出NullPointerException不调用该方法则build()默认以EmptyComponent作为根源码 L2911-L2918componentsConfiguration(ComponentsConfiguration config)以集中式配置对象设置整棵树的所有配置含增量挂载等是官方推荐的现代配置方式L2900-L2903incrementalMount(boolean isEnabled)启用/禁用增量挂载incremental mount。已标记Deprecated官方建议改用componentsConfiguration(...)因为树的配置现在应统一收敛到ComponentsConfiguration对象中L2934-L2938layoutThreadLooper(Looper looper)指定执行布局的后台 Looper。注意某些罕见场景如屏幕旋转必须回到主线程测量不指定时使用 Litho 默认 LooperL2945-L2951layoutThreadHandler(RunnableHandler handler)与上面等价但直接传入 Handler用于更底层的线程控制L2958-L2961treeState(TreeState treeState)指定初始 TreeState让组件树在创建时就能为 state 设置当前值L2967-L2970overrideComponentTreeId(int id)覆盖自动生成的 ComponentTree id。源码注释明确警告绝大多数情况下用不到除非你非常清楚自己在做什么L2977-L2980overrideRenderUnitIdMap(...)覆盖 renderUnitId 生成器与树 id注释同样标注多数情况下不应使用L2987-L2992measureListener(MeasureListener)注册测量监听可在测量完成时收到回调L2994-L2997stateUpdater(StateUpdater)注入自定义 StateUpdater用于接管树的状态更新逻辑L2999-L3002poolScope(PoolScope)实验性 APIExperimentalLithoApi仅在ComponentsConfiguration.customPoolScopesEnabled开启时生效用于控制对象池作用域L3004-L3011build()完成构建若未设置 root 则回退到EmptyComponent合并 incrementalMount 与全局调试开关LithoDebugConfigurations.isIncrementalMountGloballyDisabled最终new ComponentTree(this)L3014-L3045关于增量挂载incremental mount的底层细节build()中有一段值得注意的逻辑ComponentTree.java如果客户端通过 Builder 显式设置了incrementalMountEnabled则优先采用该值否则回落到config.incrementalMountEnabled最终还要与全局调试开关isIncrementalMountGloballyDisabled做与运算。也就是说即便你在 Builder 里开了增量挂载只要全局调试配置禁止构建结果仍会关闭它——这是排查为何增量挂载不生效时容易忽略的一点。五、挂载到 LithoViewsetComponentTree 的语义创建完成后必须将ComponentTree挂载到一个LithoView上才能真正渲染。核心方法是LithoView.setComponentTree(...)LithoView.java。从 LithoView.java 的实现可以看到它承担的职责释放旧树当旧树与新树不是同一对象时setComponentTree会走卸载流程第二个参数unmountAllWhenComponentTreeSetToNull控制传入null时是否卸载全部挂载内容。拒绝已释放的树不能把release()过的ComponentTree再挂回视图。记录新树标记mHasNewComponentTree用于区分新树与旧树配合mComponentTree.mId比较影响首次挂载动画等行为。绑定与解绑ComponentTree内部通过setLithoView(view)ComponentTree.java记录视图引用并确保新旧视图之间的引用清理与可见性状态如HINT_VISIBLE/HINT_INVISIBLE迁移。反过来ComponentTree的attach()/detach()ComponentTree.java也会在视图onAttachedToWindow/onDetachedFromWindow时被触发两者是一对互相配合的生命周期钩子。六、线程安全与生命周期关键 APIComponentTree是线程安全的ThreadSafe但内部字段的访问策略有精细划分理解这一点能避免踩坑。从源码字段声明可以看出两种访问模型标注ThreadConfined(ThreadConfined.UI)的字段如mIsMeasuring、mIsAttached只能在主线程访问其余状态通过synchronized (this)或GuardedBy(this)保护可跨线程安全访问。下面按用途梳理几个高频 API均可从 ComponentTree.java 源码中定位6.1 更换根组件setRoot / setRootSync / setRootAsyncpublic void setRoot(Nullable Component root); // 自动选择同步或异步 public void setRootSync(Nullable Component root); // 强制同步布局 public void setRootAsync(Nullable Component root); // 强制异步布局三个方法最终都汇聚到setRootAndSizeSpecAndWrapper(...)ComponentTree.java。日常开发优先使用setRoot让 Litho 根据当前是否已挂载、是否在测量中等状态自动决定走同步还是异步路径。6.2 更新尺寸约束setSizeSpec / setSizeSpecAsync / setSizeSpecSyncComponentTree的测量不是走 Android 常规的View.onMeasure而是由LithoView在onMeasure中调用树的measure(...)进而触发setSizeSpec(...)ComponentTree.java。同样提供Async/Sync变体用于控制测量发生在后台线程还是主线程。6.3 状态更新updateStateSync / updateStateAsyncpublic void updateStateSync(String componentKey, StateUpdate stateUpdate, String attribution); public void updateStateAsync(String componentKey, StateUpdate stateUpdate, String attribution);ComponentTree.java这两个方法用于向指定 key 的组件派发状态更新Sync版本会立即在调用线程执行 state 更新流程Async版本则排队到布局线程。这也解释了 Litho 中 state 更新未必发生在主线程的特性——配合ComponentTree的线程安全模型后台线程同样可以安全地触发 UI 状态刷新。6.4 释放release()public void release(); // 必须主线程调用assertMainThreadComponentTree.javarelease()会清理组件树占用的资源取消布局线程上排队中的 resolve/layout/updateState runnable、释放所有TreeFuture、通知释放监听器等。注意两点必须在主线程调用且不能在视图正在挂载mounting时释放否则抛出IllegalStateException释放后不能再次挂载到LithoView前面setComponentTree会拒绝。6.5 监听新布局就绪NewLayoutStateReadyListenerpublic void setNewLayoutStateReadyListener(Nullable NewLayoutStateReadyListener listener); public interface NewLayoutStateReadyListener { void onNewLayoutStateReady(ComponentTree componentTree); }ComponentTree.java该监听器在后台布局完成、新LayoutState就绪时被回调可用于在布局完成后执行自定义逻辑如统计耗时、触发后续操作是异步布局场景下与完成时刻挂钩的官方入口。七、底层原理异步布局如何在组件树中运转最后从源码结构看ComponentTree的异步布局由两段后台任务组成二者都以内部 Runnable 形式投递到布局线程DoResolveRunnableComponentTree.java携带 root、treeProps 与尺寸规格执行doResolve(...)负责解析resolve组件树。DoLayoutRunnableComponentTree.java基于ResolveResult执行doLayout(...)真正完成布局计算并产出LayoutState。UpdateStateSyncRunnableL3105-L3117用于在布局线程上同步执行 state 更新。这些任务默认投递到 Litho 的默认布局线程其线程名为ComponentLayoutThreadComponentTree.java通过懒加载的sDefaultLayoutThreadLooper提供L156-L158。这就是把 measure/layout 搬出主线程的机制核心主线程只负责把任务派发出去布局结果就绪后再切回主线程完成挂载。ComponentTree内部还会维护mMainThreadLayoutState与mCommittedLayoutState等LayoutStateL254、L841-L848通过hasCompatibleLayout(...)L743判断现有布局是否与新的尺寸规格兼容不兼容时才触发重新测量——这是 Litho 高效复用布局、减少重复计算的关键设计。八、小结与最佳实践回到 componenttree.md 的核心结论可以总结出以下实践准则默认交给 LithoView绝大多数 UI 场景直接用LithoView.create(c, component)或view.setRoot(component)无需手动管理ComponentTree需要精细控制时再手动创建动态换根setRoot系列、自定义布局线程layoutThreadLooper、注入初始状态treeState或监听布局完成measureListener/NewLayoutStateReadyListener时才使用ComponentTree.create(context, root).xxx().build()牢记线程语义ComponentTree整体线程安全可从任意线程调用但release()必须在主线程且不能在挂载中释放区分同步/异步 APISync变体会阻塞调用线程完成操作Async变体投递到后台布局线程选错会直接影响帧率或触发不必要的阻塞配置统一走 ComponentsConfigurationincrementalMount(boolean)已废弃新代码应使用componentsConfiguration(...)集中配置。借助源码ComponentTree.java、LithoView.java验证官方文档可以发现文档中thread-safe、Builder 暴露配置方法等论断都有对应的实现支撑。如果你想在真实工程里看它的运行效果可以参考 sample 目录下的各类示例组件或在 litho-it 中搜索ComponentTree相关的测试用例观察其在不同场景下的生命周期行为。赞分享移动开发UI组件【免费下载链接】lithoA declarative framework for building efficient UIs on Android.项目地址https://gitcode.com/gh_mirrors/li/litho点击查看免费下载相关推荐YCBlogs生命周期管理深入理解Android组件生命周期YCBlogs生命周期管理深入理解Android组件生命周期 在Android开发中Activity和Fragment的生命周期管理是构建稳定、高效应用的基教程技术博客文档如何用Akagi麻将AI辅助工具在5分钟内提升你的雀魂水平如何用Akagi麻将AI辅助工具在5分钟内提升你的雀魂水平 Akagi是一款革命性的开源麻将AI辅助工具能够实时分析雀魂、天鳳、麻雀一番街等主流麻将平台的游戏桌面应用人工智能Rin事件时间线功能详解追踪ASP.NET Core请求处理全过程Rin事件时间线功能详解追踪ASP.NET Core请求处理全过程 Rin是一款专为ASP.NET Core设计的请求/响应检查中间件它提供了强大的事件时间上一篇E7Helper终极指南第七史诗自动化脚本完整使用教程下一篇QMCDecode终极指南如何快速解密QQ音乐加密格式实现跨平台播放创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表