手动编写 baseline-prof 文件:格式、注释与基本规则
说实话,刚开始接触 Baseline Profile 的时候,我总觉得用 Android Studio 自带的插件生成就完事了。直到有一次线上应用启动慢了一截,我打开生成的 prof 文件,发现里面居然有几十个根本不会被调到的冷门类——嗯,从那以后,我就开始老老实实手写 prof 了。你想想看,编译器不会替你思考哪些路径是用户真正关心的,只有你最清楚自己 App 的热点。
手动写 baseline-prof,说白了就是在 /app/src/main/ 下放一个纯文本文件,取名叫 baseline-prof(不带任何扩展名)。它的格式极其简单,但规则细节特别容易踩坑。我来拆开讲。
文件格式:一个纯文本的「规则清单」
每一行就是一条规则。规则由操作码 + 目标描述组成。空行会被忽略,注释以 # 开头。文件第一行必须是包名声明,就像给它一个身份证。
# 文件第一行:声明包名
com.example.myapp
# 下面都是规则行
HSPLcom/example/myapp/MainActivity;->onCreate(Landroid/os/Bundle;)V
HSPLcom/example/myapp/data/Repository;->fetchData()Ljava/util/List;
WSLcom/example/myapp/ui/HomeFragment;
注意那个 #HSPL ?不对,我写错了——操作码和描述之间没有空格,但注释符号 # 是独立于规则行的。上面示例里第二行其实应该是 HSPL 开头,但我故意用了个错误写法来提醒你:每一行规则前面千万不要加 # 号,否则整行都会被当成注释。
adb shell profman --dump 看一遍实际生效的规则。
规则结构:五个操作码,三种核心类型
每个规则由三个字母的操作码开头,后面紧跟类的描述符。操作码决定了这条规则在编译和运行时的作用。我把常用的几类整理成了一个表格,方便你对照。
| 操作码 | 含义 | 示例 |
|---|---|---|
HSPL |
启动时预加载(Hot Start Path Load) | HSPLcom/example/myapp/MainActivity;->onCreate(Landroid/os/Bundle;)V |
WSL |
类全量预加载(Warm Start Load) | WSLcom/example/myapp/data/DataSource; |
WBL |
引用预热(Warm Boot Load) | WBLandroidx/recyclerview/widget/RecyclerView |
SPL |
路径预编译(Startup Path Load) | SPLcom/example/myapp/di/AppModule; |
R |
纯引用(Reference) | Randroid/content/Context; |
你可能会问:「到底用哪个?」我的经验是:HSPL 只放用户冷启动时真正调用的方法和类;WSL 适合那些虽然不立刻用到,但后续大概率会触发的类(比如 ViewPager 的 Fragment);WBL 和 SPL 主要用于加速系统组件的编译,一般不用手写,除非你明确知道某个库的初始化链很长。R 码则是最轻量的,只加入依赖引用,不会触发生成。
HSPL 和 WSL 混合方案。核心原则是——宁少勿多,每条规则都应该是经过验证的热点。
注释:你的「设计文档」
纯文本文件虽然简单,但一不留神就会变成一堆天书。我强烈建议在 prof 文件里加满注释。注释以 # 开头,可以单独占一行,也可以写在规则行后面(不过我个人觉得写在前面更清晰)。
# ---- 首屏启动链 ----
HSPLcom/example/myapp/MainActivity;->onCreate(Landroid/os/Bundle;)V
HSPLcom/example/myapp/di/AppComponent;->initialize()V
# ---- 懒加载但高频使用 ----
WSLcom/example/myapp/data/UserRepository;
WSLcom/example/myapp/network/ApiService;
# ---- 第三方库预热 ----
WBLandroidx/recyclerview/widget/RecyclerView
WBLcom/google/android/material/tabs/TabLayout
你看,加注释后,别人(甚至一个月后的你自己)拿到文件就能一眼看出哪部分是首屏、哪部分是懒加载。这比任何文档都实在。
# 这条规则是HSPL... 这种废话,而是写业务意图:# 首页Feed流核心类。这样当规则需要调整时,你能快速判断是否该删掉它。
基本规则结构:描述符写法详解
规则行中的目标描述符(也就是类或方法)有一套严格的格式。我见过最多的问题就是把类路径写成带点号的形式(比如 com.example.MyClass),但 profile 要求用 JVM 内部描述符 —— 就是用 / 代替 .,并且类名后面加 ;。方法还要加上括号里的参数类型和返回值。
| 类型 | 正确的 prof 描述符 | 错误的写法 |
|---|---|---|
| 类 | Lcom/example/myapp/HomeActivity; |
com.example.myapp.HomeActivity |
| 方法 | Lcom/example/myapp/util/FormatUtil;->formatDate(Ljava/util/Date;)Ljava/lang/String; |
FormatUtil.formatDate(Date) |
| 内部类 | Lcom/example/myapp/MainActivity$InnerFragment; |
com.example.myapp.MainActivity.InnerFragment |
为什么会这样?因为 ART 编译器的内部解析器直接采用 JVM 格式。如果你写错了,这条规则会被静默忽略。嗯,我之前排查问题时,发现 prof 文件里有一半规则都不生效,原因就是少写了分号。
javap 或者 Android Studio 的「Copy Reference」功能(右键方法 → Copy Reference),可以直接得到准确的描述符字符串。省得你手敲。
避坑指南:写 prof 最常见的三个坑
- 包名声明必须写在第一行,且不能加操作码。 我见过有人写
HSPLcom.example.myapp当包名声明——编译直接报错。包名行只写包名字符串,前面不能有L也不能有;。 - 规则顺序不重要。 ART 会重新排序,所以你可以按业务模块组织,不用按字母序。
- 不要写太多规则。 我见过一个项目写了 3000 条规则,结果文件体积超过 100KB,编译时间和运行时内存都受影响。控制在 500 条以内比较安全。
最后再啰嗦一句:写完后别忘了在 build.gradle 里开启 profile 集成。不然你写得再漂亮,也进不了 APK。