4、编写Baseline Profile规则文件(baseline-prof.txt)
说到写规则文件,我记得第一次接触baseline-prof.txt时,看着那些密密麻麻的AST(抽象语法树)符号,心里直犯嘀咕——这不就是个纯文本吗?怎么还搞出这么多讲究。后来踩了几次坑才明白,规则文件写得好不好,直接决定Profile能不能生效、生效的覆盖面有多大。
说白了,这个文件就是用来告诉编译器:“这些代码路径是我App启动和交互中最关键的,你优先给我编译成机器码。” 它不像ProGuard规则那样复杂,但格式有严格约定。不然编译时就各种报错,比如格式错误、类名找不到、通配符滥用等问题。
规则文件长什么样
一个标准的baseline-prof.txt,每个规则占一行,支持以下几种模式:
| 类型 | 语法格式 | 示例 | 说明 |
|---|---|---|---|
| 类初始化 | L<类路径>; | |
触发该类所有<clinit>和<init>方法 |
| 方法执行 | <方法签名> | |
指定方法被编译,包含调用链中的热路径 |
| 通配方法 | <签名> + ** 或 * |
|
匹配指定包下所有无参返回void的方法 |
| 构造函数 | <init> 或 <clinit> |
|
覆盖无参和有参构造函数 |
| 接口/注解标记 | 直接写入接口或注解类路径 | |
让编译器将含有该注解的类也一并编译 |
⚠️ 曾经踩过的坑: 规则必须以分号结尾,漏掉分号编译直接报“Missing semicolon”。另外签名中的箭头
->前后不能有空格,我用编辑器自动排版时经常中招。
编写规则的核心流程
我个人习惯按照“分析热点 → 确定目标类 → 书写签名 → 验证覆盖”这四步走。下面这张图直观展示了规则编写的决策路径:
规则编写的注意事项
嗯,这里要重点说几个我在项目中反复翻车的点:
- 规则数量限制: 单文件不要超过1500条规则,Android 12以上建议控制在1000条以内。我曾经一口气写了2000条,结果安装包体积膨胀了30%,启动速度反而变慢——因为AOT编译时间过长,系统直接放弃了部分规则。
- 通配符的使用边界:
**可以匹配任意多层级目录,*只匹配一级。我建议优先用具体签名,只有确定整个包都需要覆盖时才用通配符。有个真实教训:Lcom/example/**;->*()V会把所有无参void方法全扫进来,包括一些低频工具类,白白浪费编译空间。 - 构造函数不要漏: 很多老师傅只写方法签名,忘了初始化逻辑。启动时对象的
<init>如果不提前编译,第一个对象创建会触发解释执行。我一般会在规则里额外加一行:
Lcom/example/splash/SplashViewModel;-><init>()V - 避免冗余覆盖: 如果已经写了类规则
Lcom/example/MyClass;,系统会自动编译该类所有成员方法。不需要再单独写里面的方法签名,写了反而可能导致基线重复合并时冲突。
我的小习惯: 每次写完规则文件后,我会跑一遍
./gradlew :app:generateBaselineProfile,然后用 --info 日志查看 “Matched methods: X” 的数量。如果这个值远大于预期,就说明通配扩太广了,赶紧回去收窄。
一个完整规则的实战示例
假设我们有一个新闻类App,首页含网络请求、列表渲染、图片加载。我个人习惯这样组织规则:
# ===== 启动核心类 =====
Lcom/example/news/MainActivity;
Lcom/example/news/ui/NewsListFragment;
# ===== 网络库初始化 =====
Lcom/example/network/RetrofitClient;-><init>()V
Lcom/example/network/RetrofitClient;->createService(Ljava/lang/Class;)Ljava/lang/Object;
# ===== 图片加载路径 =====
Lcom/example/images/ImageLoader;->load(Ljava/lang/String;Landroid/widget/ImageView;)V
Lcom/example/images/ImageCache;->get(Ljava/lang/String;)Landroid/graphics/Bitmap;
# ===== 列表渲染高频方法 =====
Lcom/example/news/adapter/NewsAdapter;->onBindViewHolder(Lcom/example/news/adapter/NewsViewHolder;I)V
Lcom/example/news/adapter/NewsViewHolder;->bind(Lcom/example/news/model/NewsItem;)V
你看,我几乎没用一个通配符。每条规则都是我通过Macrobenchmark跑出来的热启动追踪路径。当然,如果你的应用有多个Module,别忘了在各自module的 src/main 下也放一份 baseline-prof.txt,否则子Module的代码不会被编译。
核心要义: 规则文件不是写越多越好,而是写准越好。精准定位到启动和交互中的热路径,AOT才能把力使在刀刃上。