22
33[ ![ CI] ( https://github.com/donglua/FastInflater/actions/workflows/ci.yml/badge.svg )] ( https://github.com/donglua/FastInflater/actions/workflows/ci.yml )
44
5- 高性能 Android 布局加载框架 ,通过 View 池化复用 + 智能预热 + 异步 inflate,从根本上解决 ` LayoutInflater ` 的性能瓶颈 。
5+ Android XML 布局性能诊断与优化工具。通过 inflate 耗时追踪定位热点布局 ,通过 View 池化复用降低重复 inflate 开销 。
66
7- ** 适用场景:** 仍在维护大量 XML 布局的 Android 项目( 尤其是 RecyclerView 密集列表场景) 。如果你的项目已全面迁移到 Jetpack Compose,则不需要这个库。
7+ ** 适用场景:** 仍在维护 XML 布局的 Android 项目, 尤其是 RecyclerView 密集列表、Tab 切换、Dialog 反复弹出等高频 inflate 场景 。如果你的项目已全面迁移到 Jetpack Compose,则不需要这个库。
88
99## 引入
1010
11- ### 方式 1: JitPack(推荐,无需认证)
12-
1311``` kotlin
1412// settings.gradle.kts
1513dependencyResolutionManagement {
@@ -20,82 +18,32 @@ dependencyResolutionManagement {
2018
2119// build.gradle.kts
2220dependencies {
23- implementation(" com.github.donglua:FastInflater:0.2 .0" )
21+ implementation(" com.github.donglua:FastInflater:0.3 .0" )
2422}
2523```
2624
27- ### 方式 2: GitHub Packages(备选)
28-
29- 需要配置 GitHub Token([ 创建 Token] ( https://github.com/settings/tokens/new?scopes=read:packages ) ,勾选 ` read:packages ` ):
30-
31- ``` kotlin
32- // settings.gradle.kts
33- dependencyResolutionManagement {
34- repositories {
35- maven {
36- url = uri(" https://maven.pkg.github.com/donglua/FastInflater" )
37- credentials {
38- username = providers.gradleProperty(" gpr.user" ).orNull ? : System .getenv(" GITHUB_ACTOR" )
39- password = providers.gradleProperty(" gpr.token" ).orNull ? : System .getenv(" GITHUB_TOKEN" )
40- }
41- }
42- }
43- }
44-
45- // build.gradle.kts
46- dependencies {
47- implementation(" com.github.donglua:fast-inflater:0.2.0" )
48- }
49- ```
50-
51- ## 为什么需要 FastInflater
52-
53- ` LayoutInflater ` 是将 XML 布局转换为 View 树的核心组件,也是应用启动和页面跳转时主线程卡顿的常见元凶。
25+ ## 它能做什么
5426
55- 瓶颈集中在四个方面:
27+ ** 诊断: ** 告诉你哪个布局慢、慢多少、被 inflate 了多少次、池命中率是多少。这些信息在 Android 现有工具链里没有现成替代品。
5628
57- 1 . ** 反射创建对象** — ` Class.forName().getConstructor().newInstance() ` 的开销远大于直接 ` new ` ,复杂页面几十上百个 View 的反射累加非常可观。
58- 2 . ** IO + XML 解析** — 从 APK 读取二进制 XML 并逐节点深度优先遍历,低端机型上耗时明显。
59- 3 . ** TypedArray 跨层解析** — 属性需结合 Theme/Style 计算,大量 Java-Native 跨层通信。
60- 4 . ** GC 抖动** — 短时间内创建大量 LayoutParams、TypedArray 等临时对象,触发 GC 停顿。
61-
62- ## FastInflater 的解决策略
63-
64- | 瓶颈 | 对策 |
65- | ---| ---|
66- | 反射 + XML 解析 | Phase 2 编译期 codegen,直接 ` new View() ` + ` setXx() ` |
67- | IO 读取 | Phase 3 native mmap 二进制属性表,零拷贝 |
68- | TypedArray 跨层 | codegen 预解析静态属性,运行时只处理动态值 |
69- | GC 抖动 | Phase 1 View 池化复用 + 自适应池大小 |
29+ ** 优化:** 对高频重复 inflate 的布局,池命中时耗时从几十 ms 降到 0。不是所有场景都有用,但有用的场景收益是 100%。
7030
7131## 核心特性
7232
33+ - ** 纳秒级耗时追踪** — 精确记录每次 inflate 耗时,按布局拆分,识别热点
34+ - ** 池命中率监控** — 全局和 per-layout 的 hit/miss 统计,用数据指导调优
7335- ** View 池化复用** — 池命中时 inflate 耗时降至 0,避免重复创建和 GC
7436- ** IdleHandler 预热** — 利用主线程空闲时间预创建 View,不抢占用户交互帧
75- - ** 异步 inflate** — 单线程后台 inflate,线程安全, 池优先
37+ - ** 异步 inflate** — 后台线程 inflate,池优先;含 ComposeView 等主线程依赖的布局自动降级
7638- ** 自适应池大小** — 根据运行时统计数据自动调整各布局池容量
77- - ** 池命中率监控** — 全局和 per-layout 的 hit/miss 统计,指导调优
78- - ** 纳秒级耗时追踪** — 精确记录每次 inflate 耗时,识别热点布局
79- - ** RecyclerView 无感集成** — 一行代码对接,无需修改 Adapter
39+ - ** RecyclerView 集成** — 预热加速 ViewHolder 首次创建
8040- ** DataBinding 支持** — ` FastDataBinding ` 兼容池化复用
8141- ** 自定义回收策略** — ` ViewRecyclePolicy ` 接口,精确控制 View 状态清理
82- - ** 内存压力响应** — 监听 trimMemory,分级清理池;Configuration 变化时自动清池
83-
84- ## Roadmap
85-
86- - ** Phase 1(当前)** — 运行时 View 池 + 智能预热 + 异步 inflate + 热点追踪 + 自适应调优
87- - ** Phase 2** — Gradle Plugin 编译期代码生成,覆盖 Top-N 热点布局
88- - ** Phase 3** — Native 属性表(mmap 二进制 blob)+ JNI 共享资源缓存
89-
90- ## Modules
91-
92- - ` lib/ ` — FastInflater 核心库
93- - ` compiler/ ` — Gradle Plugin,编译期 codegen(Phase 2,实验性)
94- - ` demo/ ` — 示例应用,包含 benchmark 和热点布局报告
42+ - ** 内存压力响应** — 监听 trimMemory 分级清理;Configuration 变化自动清池
9543
9644## Quick Start
9745
98- ### 基础用法
46+ ### 初始化 + 预热
9947
10048``` kotlin
10149// Application.onCreate
@@ -107,42 +55,43 @@ FastInflater.get().warmUp(this, listOf(
10755 ViewPool .WarmUpEntry (R .layout.item_comment, count = 3 ),
10856))
10957
110- // inflate(池命中时零耗时)
58+ // 包含 ComposeView/WebView 的布局,标记为主线程预热
59+ FastInflater .get().markAsMainThreadOnly(R .layout.fragment_compose)
60+ ```
61+
62+ ### inflate + 回收
63+
64+ ``` kotlin
65+ // 池命中时零耗时
11166val view = FastInflater .get().inflate(context, R .layout.item_feed, parent)
11267
113- // 回收( View 不再使用时)
68+ // View 不再使用时回收进池
11469FastInflater .get().recycle(R .layout.item_feed, view)
11570
116- // 异步 inflate(池优先,未命中时后台线程 inflate)
71+ // 异步 inflate(池优先,未命中时后台 inflate,主线程回调 )
11772FastInflater .get().inflateAsync(context, R .layout.item_feed, parent) { view ->
11873 parent.addView(view)
11974}
12075```
12176
12277### RecyclerView 集成
12378
124- 预热 + 一行代码安装:
125-
12679``` kotlin
80+ // 预热 + 安装
12781FastRecycledViewPool .install(recyclerView, warmUpLayouts = listOf (
12882 ViewPool .WarmUpEntry (R .layout.item_feed, 4 )
12983))
130- ```
13184
132- Adapter 中让 ` viewType == layoutId ` ,` onCreateViewHolder ` 通过 FastInflater 创建 View。
133- 预热池有缓存时直接返回,无需 inflate:
134-
135- ``` kotlin
85+ // Adapter 中让 viewType == layoutId
13686override fun getItemViewType (position : Int ) = R .layout.item_feed
13787
13888override fun onCreateViewHolder (parent : ViewGroup , viewType : Int ): MyViewHolder {
139- // 池命中时零耗时,未命中时正常 inflate
14089 val view = FastInflater .get().inflate(parent, viewType)
14190 return MyViewHolder (view)
14291}
14392```
14493
145- 设计原则:RecyclerView 自身的回收机制不变,FastInflater 只在"创建侧"加速——不会出现同一个 View 被两个池同时持有的问题 。
94+ FastInflater 只在创建侧加速,不参与 RecyclerView 的回收流程,不会出现双池冲突 。
14695
14796### DataBinding
14897
@@ -154,8 +103,6 @@ val binding = FastDataBinding.inflate<ItemFeedBinding>(
154103
155104### 自定义回收策略
156105
157- 对于有复杂内部状态的布局,提供自定义清理逻辑:
158-
159106``` kotlin
160107FastInflater .get().registerPolicy(R .layout.item_feed, object : ViewRecyclePolicy {
161108 override fun onRecycle (view : View ) {
@@ -168,22 +115,6 @@ FastInflater.get().registerPolicy(R.layout.item_feed, object : ViewRecyclePolicy
168115})
169116```
170117
171- ### 自适应池大小
172-
173- 运行一段时间后,根据实际数据自动调优:
174-
175- ``` kotlin
176- // 建议在 app 运行 3~5 分钟后调用
177- FastInflater .get().autoTune()
178- ```
179-
180- 也可以手动设置单个布局的池大小:
181-
182- ``` kotlin
183- FastInflater .get().setMaxPoolSize(R .layout.item_feed, 8 )
184- FastInflater .get().setMaxPoolSize(R .layout.activity_detail, 2 )
185- ```
186-
187118## 监控与调优
188119
189120### 热点布局追踪
@@ -195,7 +126,7 @@ InflateTracker.setReporter { stats ->
195126 .take(20 )
196127 .forEach { (id, stat) ->
197128 val name = resources.getResourceEntryName(id)
198- Log .d(" Inflate" , " $name count=${stat.count.get()} avg=${stat.avgMs} ms avgUs= ${stat.avgUs} us " )
129+ Log .d(" Inflate" , " $name count=${stat.count.get()} avg=${stat.avgMs} ms" )
199130 }
200131}
201132
@@ -205,49 +136,45 @@ InflateTracker.report()
205136### 池命中率
206137
207138``` kotlin
208- // 全局命中率
209139Log .d(" Pool" , " hit rate: ${(PoolStats .hitRate * 100 ).toInt()} %" )
210-
211- // 单个布局
212- Log .d(" Pool" , " item_feed hit rate: ${(PoolStats .hitRateFor(R .layout.item_feed) * 100 ).toInt()} %" )
213-
214- // 详细数据
215- PoolStats .snapshot().forEach { (layoutId, stat) ->
216- Log .d(" Pool" , " layout=$layoutId hits=${stat.hits.get()} misses=${stat.misses.get()} " )
217- }
140+ Log .d(" Pool" , " item_feed: ${(PoolStats .hitRateFor(R .layout.item_feed) * 100 ).toInt()} %" )
218141```
219142
220143** 调优参考:**
221- - 命中率 < 50%:预热数量不足或池太小,增加 warmUp count 或调大 maxPoolSize
144+ - 命中率 < 50%:预热数量不足或池太小
222145- 命中率 > 90%:池化充分发挥作用
223- - 命中率 100% 且池经常满:可以适当减小池大小,节省内存
146+ - 命中率 100% 且池经常满:可以适当减小池大小节省内存
224147
225- ### 主线程依赖的布局
148+ ### 自适应池大小
226149
227- 部分 View 不能在后台线程 inflate(构造或 attach 流程会访问 LiveData/Lifecycle):
150+ ``` kotlin
151+ // 运行 3~5 分钟后,根据实际数据自动调优
152+ FastInflater .get().autoTune()
228153
229- - ` androidx.compose.ui.platform.ComposeView `
230- - ` WebView ` / ` SurfaceView ` / ` TextureView `
231- - 自定义 View 在初始化时调用 ` liveData.observe() ` / ` liveData.removeObserver() `
154+ // 或手动设置
155+ FastInflater .get().setMaxPoolSize( R .layout.item_feed, 8 )
156+ `` `
232157
233- ** 自动检测 ** :FastInflater 后台 inflate 失败时会自动捕获异常、标记该布局,剩余预热数量降级到主线程 IdleHandler,后续 ` warmUp ` 和 ` inflateAsync ` 也会直接走主线程。
158+ ### 主线程依赖的布局
234159
235- ** 显式标记 ** (推荐,避免一次后台失败的开销) :
160+ 部分 View 不能后台 inflate(ComposeView、WebView、含 LiveData 的自定义 View)。FastInflater 会自动检测并降级到主线程 IdleHandler,也可以预先标记 :
236161
237162``` kotlin
238163FastInflater .get().markAsMainThreadOnly(R .layout.fragment_compose_view)
239- ```
240-
241- ** 监听降级事件** :
242164
243- ``` kotlin
165+ // 监听降级事件
244166FastInflater .get().setWarmUpListener(object : ViewPool .WarmUpListener {
245167 override fun onBackgroundInflateFailed (layoutId : Int , error : Throwable ) {
246- Log .w(" FastInflater" , " fallback to main thread: ${resources.getResourceEntryName(layoutId)} " , error)
247- }
248- override fun onMarkedAsMainThreadOnly (layoutId : Int ) {
249- Log .i(" FastInflater" , " marked main-thread-only: ${resources.getResourceEntryName(layoutId)} " )
168+ Log .w(" FastInflater" , " fallback: ${resources.getResourceEntryName(layoutId)} " )
250169 }
251170})
252171```
253172
173+ ## Modules
174+
175+ - ` lib/ ` — FastInflater 核心库
176+ - ` demo/ ` — 示例应用,包含 benchmark 和热点布局报告
177+
178+ ## License
179+
180+ Apache 2.0
0 commit comments