Skip to content

Commit 37b2edc

Browse files
committed
docs: 重写 README,重新定位为性能诊断+轻量优化工具
- 定位从'解决 LayoutInflater 瓶颈'改为'XML 布局性能诊断与优化' - 移除 Phase 2/3 Roadmap 和 compiler 模块说明 - 诊断能力(InflateTracker/PoolStats)放在优化前面 - 补充 markAsMainThreadOnly 用法 - 版本号 0.3.0
1 parent a802af5 commit 37b2edc

2 files changed

Lines changed: 130 additions & 203 deletions

File tree

README.md

Lines changed: 48 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@
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
1513
dependencyResolutionManagement {
@@ -20,82 +18,32 @@ dependencyResolutionManagement {
2018

2119
// build.gradle.kts
2220
dependencies {
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+
// 池命中时零耗时
11166
val view = FastInflater.get().inflate(context, R.layout.item_feed, parent)
11267

113-
// 回收(View 不再使用时)
68+
// View 不再使用时回收进池
11469
FastInflater.get().recycle(R.layout.item_feed, view)
11570

116-
// 异步 inflate(池优先,未命中时后台线程 inflate)
71+
// 异步 inflate(池优先,未命中时后台 inflate,主线程回调
11772
FastInflater.get().inflateAsync(context, R.layout.item_feed, parent) { view ->
11873
parent.addView(view)
11974
}
12075
```
12176

12277
### RecyclerView 集成
12378

124-
预热 + 一行代码安装:
125-
12679
```kotlin
80+
// 预热 + 安装
12781
FastRecycledViewPool.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
13686
override fun getItemViewType(position: Int) = R.layout.item_feed
13787

13888
override 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
160107
FastInflater.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-
// 全局命中率
209139
Log.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
238163
FastInflater.get().markAsMainThreadOnly(R.layout.fragment_compose_view)
239-
```
240-
241-
**监听降级事件**
242164

243-
```kotlin
165+
// 监听降级事件
244166
FastInflater.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

Comments
 (0)