编译 framework.jar(frameworks/base boot framework)
bash
# 进入 AOSP 根目录,先 envsetup + lunch(不 lunch 会报错)
cd /home/maqi/android13-r44
source build/envsetup.sh
lunch sdk_car_x86_64-userdebug
# 编译整个 boot framework(make 和 m 等价,m 就是 make 的封装)
make framework -j12
# 或
m framework -j12
产出位置:
- 原始 classes.jar:
out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar(~38M,25879 个类) - 装进系统的 bootclasspath jar:
out/target/product/emulator_car_x86_64/system/framework/framework.jar(~36M,含 dex)
注意:framework.jar 是系统开机就加载的基础框架(含大量 @hide 私有 API),不是给 app 集成用的 jar。app 编译用 SDK 的 android.jar(公开接口桩),不要 implementation 整个 framework.jar,会跟系统已加载的那份类重复直接崩。
framework 与 framework-minus-apex 的区别(frameworks/base/Android.bp L340~393)
| 模块 | 装设备? | 内容 | 用途 |
|---|---|---|---|
| framework-minus-apex | 是(stem: "framework" 装成 framework.jar) |
framework 减掉 APEX 模块(wifi/tethering/permission/media 等)的类 | 设备上真正跑的那个框架 |
| framework | 否(installable: false) |
minus-apex + framework-updatable-stubs-module_libs_api + 注解 |
纯编译用,给内置 app 当完整 API 类路径 |
- minus-apex 名字由来:Android 10 起把一批类从 framework 拆成可独立升级的 Mainline APEX 模块,minus-apex = 减掉这些。
make framework→framework_intermediates/classes.jar(38M);make framework-minus-apex→framework-minus-apex_intermediates/classes.jar(37M)。- 两个都是 3 万多类的大 jar,本质都是「系统框架」档,不是给第三方 app 打进 apk 用的。
app 引用 framework 隐藏 API 的两种做法(编译期)
目标:让 app 在编译期能引用 @hide / @SystemApi 隐藏 API。区别 = 改哪一层。
做法 A:替换 SDK 下的 android.jar(全局,改 SDK 层)
bash
# full jar(含 @hide)来源:
# out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar
# 覆盖到 SDK 的 android.jar(路径按 compileSdk 版本,如 android-33)
cp out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar \
"$ANDROID_SDK/platforms/android-33/android.jar"
做法 B:compileOnly framework.jar(局部,改 Gradle 单模块层)
gradle
// app/build.gradle
dependencies {
compileOnly files('libs/framework.jar') // 只编译、不打进 apk
}
framework 相关 jar 横向对比表
| 对比项 | 编译路径/来源 | 编译方法 | 产物路径 | 作用 | 使用方法(app 侧) |
|---|---|---|---|---|---|
| framework.jar | frameworks/base/Android.bp,name:"framework" |
make framework -j12 |
out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar(38M) |
build-only 合成 jar(minus-apex + 可更新模块 API 桩),给内置 app 完整 API | 不装设备;可当做法 A/B 的完整桩来源 |
| framework-minus-apex | frameworks/base/Android.bp,name:"framework-minus-apex" |
make framework-minus-apex -j12 |
framework-minus-apex_intermediates/classes.jar(37M)→ stem:"framework" 装成 out/target/product/<target>/system/framework/framework.jar(36M) |
设备上真正运行的框架(去掉 APEX 模块的类) | 随系统镜像烧录,bootclasspath 加载,不碰 app |
| android.jar(public) | SDK 快照 prebuilts/sdk/current/public/android.jar;源码树里由 android_stubs_current 从 framework 源码抽桩生成 |
平时 app 开发不编译(用 check-in 快照);改公开 API 后需 make update-api + 重编才更新 |
prebuilts/sdk/current/public/android.jar(快照)/ out/.../android_stubs_current_intermediates/classes.jar(构建产物) |
公开 API 桩(方法体是 Stub) | compileSdk 33 自动引用,什么都不用做 |
| android.jar(system) | prebuilts/sdk/current/system/android.jar |
无需编译 | 同上 system/ 目录 |
@SystemApi 桩 | 系统/特权 app(Soong 里 sdk_version:"system_current" 对应它) |
| 做法 A:替换 android.jar | 用 framework_intermediates/classes.jar |
make framework 后 cp 覆盖 |
覆盖 $ANDROID_SDK/platforms/android-33/android.jar |
全局(整个 SDK)暴露 @hide/@SystemApi | 覆盖后正常写代码即可 |
| 做法 B:compileOnly framework.jar | framework_intermediates/classes.jar 拷成 app/libs/framework.jar |
make framework 后 cp 到 libs/ |
app/libs/framework.jar |
单模块暴露隐藏 API(仅能补「整类被藏」) | dependencies { compileOnly files('libs/framework.jar') } |
android.jar 是什么:
一句话:android.jar 是 app「编译时」用的 API 桩(stub),只有公开接口的签名,没有真实实
现。
你这棵树上 prebuilts/sdk/current/ 下就有 6 个 android.jar,按「谁能用」分层:
- public/android.jar ------ 公开 API,所有 app 都能用(就是 Gradle compileSdk 33
对应那个) - system/android.jar ------ @SystemApi,系统/特权 app 才能用
- test/android.jar ------ @TestApi,测试用
- module-lib/android.jar ------ Mainline 模块用
- system-server/android.jar ------ system_server 用
- core/android.jar ------ core platform
SDK 里则是 platforms/android-33/android.jar(对应 prebuilts/sdk/current/public/andro
id.jar)。
update-api 具体操作(改了公开 API 后)
改了公开 API(新增/删除/改 public 方法或类)后编译,会报:
You have tried to change the API from what has been previously approved.
错误信息里会直接打印要执行的命令(m <模块名>-update-current-api),照抄即可。
两个选择(错误里写明的):
- 给新方法加
@hide(保持私有,不改公共 API) - 跑
m <模块名>-update-current-api更新签名文件
frameworks/base 主框架公开 API 的命令(模块名 api-stubs-docs-non-updatable):
bash
m api-stubs-docs-non-updatable-update-current-api
它做的事:把新生成的签名覆盖回 checked-in 签名文件:
frameworks/base/core/api/current.txt(公开 API)frameworks/base/core/api/removed.txt(删除的 API)
@SystemApi 同理(对应 core/api/system-current.txt):
bash
m api-stubs-docs-non-updatable-system-update-current-api
旧叫法
make update-api是 Android 10 之前;A13 用m <模块>-update-current-api。更新后 current.txt 要过 review 才能提主干(Google 管控),自己本地刷机无所谓,直接跑。
疑问
你提到的 update-api 和 stub 产物,核心作用是维护 Android 系统的 API 契约(Contract)与兼容性防线,而非直接用于 App 开发。
简单来说:
framework.jar(全量):是**"原材料"**,包含所有代码(公开 + 隐藏),用于系统运行或你手动替换 SDK 来"作弊"编译。android.jar(Stub):是**"官方考纲"**,只包含允许公开的 API,用于约束普通 App 只能调用规定接口。update-api:是**"修订考纲的工具"**,当系统开发者新增了公开 API 时,必须运行此命令更新"考纲",否则编译会报错。
以下是详细的用途解析与对比:
1. 核心概念辨析:为什么需要这么多产物?
| 产物/命令 | 本质定义 | 核心用途 | 谁在用? | 为什么不能混用? |
|---|---|---|---|---|
framework.jar (全量实现) |
系统真实代码 。包含 public, @hide, @SystemApi 所有类和方法的真实逻辑 。 |
系统运行。手机开机加载到 BootClassPath;或作为 AOSP 内置 App 的编译依赖 。 | 系统本身、内置 App、想破解隐藏 API 的开发者。 | 若普通 App 直接依赖它,会因包含大量非公开 API 而破坏谷歌的兼容性承诺,且体积过大。 |
android.jar (Public Stub) |
公开 API 桩 。仅含 public 接口签名,方法体为 throw new Stub() 。 |
编译约束 。确保第三方 App 只能 调用谷歌承诺稳定的公开接口,防止滥用内部 API 。 | 普通第三方 App 开发者 (Gradle 默认引用)。 | 它是"过滤器"。如果直接用 framework.jar 替代,就失去了"禁止调用隐藏 API"的编译期检查机制。 |
update-api (构建命令) |
签名同步工具 。将编译生成的最新 API 签名,覆盖回源码树中的 .txt 记录文件 。 |
API 管理 。当系统开发者新增/修改了 public 方法后,必须运行此命令更新"考纲",否则 CI 编译会报错 。 |
AOSP 系统开发者 (Framework 团队)。 | 普通 App 开发者无需运行。这是系统源码维护流程的一部分,用于保证 API 演进的可追溯性。 |
2. update-api 的具体工作流与意义
当你修改了 AOSP 源码中的 Java 文件(例如在 Activity.java 中新增了一个 public void myNewMethod()),编译系统会检测到当前的 API 签名记录(current.txt)与实际代码不符。
为什么会报错?
Android 有一套严格的 API Check 机制。编译时会比对:
- 实际代码 :当前源码中所有的
public接口。 - 记录文件 :
frameworks/base/core/api/current.txt(即"考纲")。
如果两者不一致,构建系统会抛出You have tried to change the API from what has been previously approved错误,阻止编译 。
update-api 做了什么?
运行该命令(如 m api-stubs-docs-non-updatable-update-current-api)后,系统会执行以下操作:
- 提取签名 :从最新编译的 Stub 中提取所有
public类和方法的签名。 - 覆盖记录 :将提取出的签名写入
current.txt和removed.txt文件 。 - 通过检查:下次编译时,实际代码与新的记录文件一致,报错消失。
bash
# 典型操作场景:系统开发者新增了公开 API
# 1. 修改源码 frameworks/base/core/java/android/app/Activity.java (新增 public 方法)
# 2. 尝试编译,通常会报错提示 API 变更
m framework -j12
# Error: You have tried to change the API from what has been previously approved.
# 3. 运行 update-api 命令 (根据错误提示的具体模块名)
# 这将更新 frameworks/base/core/api/current.txt
m api-stubs-docs-non-updatable-update-current-api
# 4. 重新编译,此时通过
m framework -j12
意义 :这确保了 Android 版本的 API 变更是有迹可循的。Google 在合入代码时,会审查
current.txt的变动,防止意外暴露内部接口或误删旧接口 。
3. 三种 Jar 包的层级关系
- Full (framework.jar):源头,什么都有。
- Stub (android.jar):经过过滤的"子集",只给外人看。
- Txt (current.txt):对"子集"的文字记录,用于版本控制。
- update-api:当源头变了,用来同步更新"文字记录"的工具。
4. 对你的开发意味着什么?
-
如果你是普通 App 开发者:
- 不需要关心
update-api:那是 Google 和系统ROM开发者做的事。 - 不需要关心 Stub 生成 :你只需要知道
android.jar是限制你的"墙"。 - 只需关注
framework.jar:如果你想翻墙(调用隐藏 API),你就拿framework.jar的编译产物去替换android.jar,或者在 Gradle 里依赖它。这正是你之前操作的本质------用"全量原材料"替换了"过滤后的考纲" 。
- 不需要关心
-
如果你是 ROM 定制/系统开发者:
- 每当你修改了 Framework 层的
public接口,必须运行update-api,否则无法构建系统镜像。 - 你需要理解
framework-minus-apex和framework的区别,以便正确打包系统分区 。
- 每当你修改了 Framework 层的
总结
搞那么多产物是为了分工:
framework.jar负责跑(系统运行时用)。android.jar负责管(编译期约束 App 行为)。update-api负责记(维护 API 演进的档案)。
你之前的操作(替换 android.jar),实际上是绕过了"管"的环节,直接让编译器看到了"跑"的内容,从而实现了调用隐藏 API 的目的。