
本文基于 Android 12(API 32)平台,适用于有一定 AOSP 编译经验的 Framework 开发者。涉及 Android 11+ 的 APEX 模块化变化会特别标注。
做 Android 系统应用或 Framework 开发时,有两个高频痛点:
- Android Studio 调不到
@hideAPI ------ SDK 自带的android.jar只包含公开 API 的存根,系统应用开发却离不开隐藏接口。 - 改一行代码要全编半小时 ------ Framework 代码量巨大,每次都全量编译不现实,需要精准单编 + 快速 push 验证。
本文就围绕这两个痛点,分成两部分讲:android.jar 的生成 和 Framework 快速编译验证。
一、android.jar 的生成
1.1 为什么需要自定义 android.jar
Android Studio 默认使用的 android.jar 来自 SDK 目录(如 platforms/android-32/android.jar),它只包含公开 API 的签名存根 (stub),所有 @hide 标注的类和方法都被剔除了。可以看到具体大小为25MB左右,当然我们也可以用jadx工具查看里面的内容,我这里就先不演示了。

但系统应用开发场景下,你几乎绕不开隐藏 API:
- 调用
WifiManager的热点配置方法 - 访问
ActivityManagerService的内部接口 - 使用
XXXXManager等厂商自定义类
这时候有两种解决方案。
1.2 方案一:使用开源项目(省事,推荐快速上手)
社区已有成熟的开源项目,提供各版本编译好的 android.jar,直接下载替换即可:
| 项目 | 说明 |
|---|---|
| Reginer/aosp-android-jar | 提供多个 Android 版本的 AOSP android.jar,包含隐藏 API |
| anggrayudi/android-hidden-api | 提供基于各版本 SDK 的隐藏 API jar 包 |
优点 :开箱即用,无需编译环境。 缺点:版本不一定与你当前 AOSP 分支完全匹配;如果你修改了 Framework 代码新增了 API,开源 jar 不会包含。
适合:临时开发、API 未做修改的场景。 如果你在 Framework 中新增了自定义 API,请用方案二自行生成。
1.3 方案二:自行编译生成(精准,推荐系统开发使用)
核心思路:把 AOSP 编译产物中的完整 API 类合并到 SDK 的 android.jar 中。
步骤 1:准备 SDK 的 android.jar
找到 SDK 目录下的 android.jar,拷贝出来并解压:
bash
# SDK 路径示例,根据你的实际环境调整
cp ~/Android/Sdk/platforms/android-32/android.jar ./android.jar
# 解压到工作目录
mkdir new_android_jar
cd new_android_jar
jar -xf ../android.jar
cd ..
步骤 2:获取 Framework 编译产物中的 classes-header.jar
AOSP 编译后,framework 模块的中间产物路径为:
bash
out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes-header.jar
注意路径中的模块名是
framework_intermediates,不是framework-minus-apex! 原因见 [1.4 节](#1.4 节 "#14-%E5%85%B3%E9%94%AE%E5%8E%9F%E7%90%86framework-vs-framework-minus-apex")。并且这里的是common而不是product,是因为product目录下的 jar 包(如javalib.jar)是给真机运行的,里面已经被转换成了 Dalvik 虚拟机专用的 .dex 字节码 ,而 Android Studio(及底层的 javac 编译器)只认识标准的 .class 文件 。拿.dex的包去替换 SDK,编译器会直接报错。只有common目录下的classes-header.jar才是纯正的.class存根
将 classes-header.jar 也解压出来:
bash
mkdir header_content
cd header_content
jar -xf ../classes-header.jar
cd ..
步骤 3:合并并重新打包
这里其实我刚开始的时候有一个疑问,为什么不能直接把编译出的 classes.jar 改名拿去用呢。后面我通过jadx看了以后发现了一个问题,framework_intermediates 编译出的 jar 包虽然包含了 Android 的全量 API,
但它不包含 Java 的基础核心库(如 java.lang.*, java.util.* 等,这些属于 libcore 模块)。如果直接替换,Android Studio 会因为找不到基础 Java 类而报错,所以用原版 SDK 的 android.jar 作为底座(保留 Java 基础类),再将 classes-header.jar 追加进去,是完美通过编译的条件。
将 header 中的类文件合并到 SDK 的 android.jar 中,然后重新打包:
bash
# 将 header 内容合并进 new_android_jar
cp -r header_content/* new_android_jar/
# 重新打包为 android.jar
cd new_android_jar
jar cvf ../android_new.jar -C . .
cd ..
步骤 4:替换 SDK 的 android.jar
bash
# 备份原始文件
cp ~/Android/Sdk/platforms/android-32/android.jar ~/Android/Sdk/platforms/android-32/android.jar.bak
# 替换
cp android_new.jar ~/Android/Sdk/platforms/android-32/android.jar
替换完成后,重启 Android Studio,即可在代码中直接调用 @hide API。

验证:可以执行以下命令确认是否包含目标类:
bashjar tf classes-header.jar | grep {你的自定义类}
1.4 关键原理:framework_intermediates vs framework-minus-apex
这是最容易踩坑的地方。在 out/target/common/obj/JAVA_LIBRARIES/ 下你会看到两个目录:
framework_intermediates/ ← 完整 API,用这个
framework-minus-apex_intermediates/ ← 残缺 API,不要用这个
framework-minus-apex:扣除 APEX 的"残缺体"
在 Android 11 及之后,Google 将许多核心功能抽离成了 APEX 独立模块:
| 被 APEX 化的功能 | APEX 模块 |
|---|---|
Wi-Fi (android.net.wifi) |
com.android.wifi |
媒体 (android.media) |
com.android.media |
| 网络共享 (Tethering) | com.android.tethering |
| AppSearch | com.android.appsearch |
这些模块的代码和 API 不再包含在 framework-minus-apex 的编译产物中。
如果你用 framework-minus-apex_intermediates/classes.jar 作为 Android Studio 的依赖库,当你尝试调用 WifiManager 等被抽离的类的 API 时,编译器会直接报错,提示找不到这些类和方法------它是一个残缺的 API 集合。
framework_intermediates:编译期的"完全体"壳模块
在 frameworks/base/Android.bp 中,Google 有一段明确的注释:
"This 'framework' module is NOT installed to the device. It's 'framework-minus-apex' that gets installed to the device... The purpose of this module is to provide framework APIs (both public and private) for bundled apps."
(这个 framework 模块不会被安装到设备上。安装到设备上的是 framework-minus-apex......这个模块的目的是为了给系统应用提供包含公开和私有的完整 Framework API。)

也就是说:
framework是一个编译期专用(Build-only)的壳模块- 它在编译期间,将
framework-minus-apex的代码与各个 APEX 模块暴露出来的存根(Stubs)合并在一起 - 因此
framework_intermediates/下的产物是一个完全体 ,包含 Android 系统所有的公共 API 以及被@hide隐藏的私有 API
结论:生成 android.jar 时,务必使用 framework_intermediates,不要用 framework-minus-apex_intermediates。
1.5 集成与更新策略
当你修改了 Framework 代码后,是否需要重新生成 android.jar?
| 场景 | 是否需要更新 android.jar |
|---|---|
| 仅修改了 Framework 内部实现,未改 API 签名 | 不需要 |
新增了 @hide API 或自定义类 |
需要重新生成 |
| 修改了公开 API | 需要 ,且要先执行 make update-api |
如果只需要提取某个特定类(而非完整合并),可以直接从 classes.jar 中抽取:
bash
cd out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/
# 从 classes.jar 中提取指定类
jar -xvf classes.jar android/app/{你自定义的类}.class
# 打包成单独的 jar 供 AS 引用
jar -cvf custom.jar android
如果合并过程中遇到编译失败,可以尝试重新解压 header 再合并:
bash
mkdir header_content
cd header_content
jar -xf ../classes-header.jar
cd ..
jar uf android.jar -C header_content .
这里再提供一个一键脚本,放在我们的AOSP源码目录下,取名叫做 make_custom_sdk.sh
bash
#!/bin/bash
# 1. 配置路径
# 原生官方的 android.jar 路径 (请替换为你本地的真实路径)
BASE_SDK_JAR="android.jar"
# 你刚编译出来的 framework 头部 jar 路径
FRAMEWORK_HEADER="out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes-header.jar"
# 最终生成的定制版 jar 名字
OUTPUT_JAR="custom_android.jar"
echo "开始制作包含隐藏 API 的定制 SDK..."
# 2. 检查文件是否存在
if [ ! -f "$BASE_SDK_JAR" ] || [ ! -f "$FRAMEWORK_HEADER" ]; then
echo "错误:找不到底包或 classes-header.jar,请检查路径!"
exit 1
fi
# 3. 复制官方底包作为基础
cp "$BASE_SDK_JAR" "$OUTPUT_JAR"
# 4. 创建临时目录解压并混合
mkdir -p temp_header
cd temp_header
echo "解压 classes-header.jar..."
jar -xf ../$FRAMEWORK_HEADER
# 5. 将混合后的 class 塞回定制的 jar 中
echo "正在混合类库..."
cd ..
jar uf "$OUTPUT_JAR" -C temp_header .
# 6. 清理现场
rm -rf temp_header
echo "大功告成!生成的定制 SDK 位于:$OUTPUT_JAR"

二、Framework 快速编译与验证
2.1 常用编译产物速查
Framework 修改后,不需要全量编译,只需针对修改的模块单编。以下是常用产物速查表:
| 产物 | 单编命令 | 对应源码目录 | 说明 |
|---|---|---|---|
services.jar |
m services |
frameworks/base/services |
含 ActivityManagerService、PackageManagerService 等核心服务 |
framework.jar |
m framework-minus-apex |
frameworks/base |
含 Activity、View、Handler 等基础类 |
framework-graphics.jar |
m framework-graphics |
frameworks/base/graphics |
图形相关 API(Android 12+ 从 framework 中剥离的模块) |
framework-res.apk |
m framework-res |
frameworks/base/core/res |
系统资源(string、drawable、layout 等) |
2.2 编译命令对比
单模块编译:mmm(快)
bash
mmm frameworks/base/services -j32
编译范围较小,只编译指定目录下的模块,速度较快。适合只修改了 frameworks/base/services 目录下代码的场景。
模块编译:make / m(范围更广)
bash
# make 方式
make -j32 services
# 或使用 Soong 的 m 命令(推荐)
m services
编译范围更广,涉及更多模块和依赖项,时间相对较长。适合修改影响了多个目录下与 services 相关代码的场景,确保所有相关模块都被正确编译。
API 变更时:update-api
如果修改了公开 API(新增、删除或修改了方法签名),必须先更新 API 定义:
bash
make update-api -j32
然后再编译对应模块:
bash
make framework-minus-apex -j32
# 产物路径:out/target/product/xxx/system/framework/framework.jar
make -j32 services
# 产物路径:out/target/product/xxx/system/framework/services.jar
2.3 产物目录解析:out/soong vs out/target
编译完成后,你会看到两个重要的产物目录,不要搞混:
out/soong/.intermediates/ ------ 中间产物车间
这是 Soong 构建系统(Android 新一代基于 Ninja 的构建系统)的工作草稿本和中间缓存目录。
里面放了什么:
- 编译过程中产生的
.class文件 - 从
.aidl自动生成的 Java 文件 - 尚未打包、尚未优化的原始 Jar 包
- C/C++ 编译出的
.o目标文件
有什么用:用于增量编译(只编译修改过的部分)。下次编译时,Soong 会检查这里,没有改动就直接复用,大大加快编译速度。
不要直接 push 这个目录下的文件到手机,它们可能还没完成最终的打包、对齐和 Dex 转换。
out/target/product/<设备名>/system/ ------ 最终成品仓库
这是 Android 构建过程的最终装配线,目录结构 1:1 对应设备里的 /system 分区。
里面放了什么:
- 经过 Dex 转换(D8/R8)、对齐处理、甚至预编译(Odex/Vdex)优化过的
.jar和.apk - 最终的动态链接库
.so和可执行二进制文件
有什么用 :这里的产物是随时可以运行的成品。打包工具直接把这个目录原封不动压成镜像。你需要 adb push 的文件,一定是从这个目录下取的。
| 目录 | 角色 | 能否直接 push |
|---|---|---|
out/soong/.intermediates/ |
中间产物,用于增量编译 | 不能 |
out/target/product/<设备>/system/ |
最终成品,1:1 对应 /system 分区 | 可以 |
2.4 Push 与验证脚本
编译完成后,将产物 push 到设备并重启验证。以下是整合后的完整脚本:
完整 Push 脚本(services.jar 为例)
bash
#!/bin/bash
#=============================================
# Framework services.jar 快速验证脚本
# 使用前修改变量:PRODUCT_NAME 和 OUT_DIR
#=============================================
PRODUCT_NAME="xxxx"
OUT_DIR="target/product/${PRODUCT_NAME}/system/framework"
# 等待设备连接
adb wait-for-device
# 获取 root 权限并重新挂载系统分区为可写
adb root
sleep 2
adb wait-for-device
adb remount
# 推送 services.jar
adb push "${OUT_DIR}/services.jar" /system/framework/
# 同步文件系统并重启
adb shell sync
adb reboot
需要推送多个产物时
如果同时修改了 framework 和 services,按以下顺序推送:
bash
adb root
adb remount
# 按依赖顺序推送
adb push framework-res.apk /system/framework/ # 资源(如有修改)
adb push framework.jar /system/framework/ # Framework 核心
adb push secondary_framework.jar /system/framework/ # 如果存在 secondary_framework.jar,需要 push
adb push services.jar /system/framework/ # 如果 service 有修改,需要 push
adb shell sync
adb reboot
关于 secondary_framework.jar:部分设备/平台会生成此文件,如果编译产物中存在,务必一并推送,否则可能出现类加载异常。
遇到类加载异常时:清理 Dalvik 缓存
如果 push 后设备启动异常或修改未生效,可能是 Dalvik 缓存导致的。清理后重启:
bash
adb root
adb remount
# 删除 framework 的 arm 编译缓存(如有)
adb shell "rm -rf /system/framework/arm"
# 推送新的 services.jar
adb push services.jar /system/framework
# 清理 services.jar 的 dalvik 缓存
adb shell "rm -rf /data/dalvik-cache/arm/system@framework@services.jar@classes.*"
adb shell sync
adb reboot
2.5 常见踩坑速查
| 问题 | 原因 | 解决方案 |
|---|---|---|
AS 中调用 WifiManager 等 API 报红 |
使用了 framework-minus-apex 生成 android.jar |
改用 framework_intermediates 重新生成 |
| push 后修改未生效 | Dalvik 缓存未清理 | 执行 rm -rf /data/dalvik-cache/arm/system@framework@*.jar@classes.* |
| push 后设备无法开机 | 推送了 out/soong/.intermediates 下的中间产物 |
从 out/target/product/<设备>/system/framework/ 取最终产物 |
| 编译报 API 不一致 | 修改了公开 API 未执行 make update-api |
先执行 make update-api -j32,再编译模块 |
secondary_framework.jar 相关类找不到 |
未推送该文件 | 检查产物目录是否存在该文件,存在则一并 push |
framework-graphics.jar 相关类找不到 |
Android 12+ 图形模块已独立编译 | 单独执行 m framework-graphics 并 push |
小结
| 痛点 | 解决方案 | 关键点 |
|---|---|---|
AS 调不到 @hide API |
生成自定义 android.jar |
用 framework_intermediates(完全体),别用 framework-minus-apex_intermediates(残缺体) |
| 全编太慢 | 单编 + push 验证 | 产物从 out/target/product/<设备>/system/ 取,别从 out/soong/.intermediates/ 取 |
最后说个题外话,在早期的 Android 版本中,原本可以通过 make android_hidden_api_stubs 一键生成完整的包含隐藏 API 的 jar 包 。但在 Android 11/12 之后,由于 Project Mainline 的推进和对私有 API 调用的严格限制,Google 已经删除了该 Target。所以目前掌握这两点,日常 Framework 开发效率会有质的提升。如果你在 AOSP 13/14 上遇到差异,欢迎交流补充。