Android Framework 开发技巧:android.jar 生成与系统快速编译验证

本文基于 Android 12(API 32)平台,适用于有一定 AOSP 编译经验的 Framework 开发者。涉及 Android 11+ 的 APEX 模块化变化会特别标注。

做 Android 系统应用或 Framework 开发时,有两个高频痛点:

  1. Android Studio 调不到 @hide API ------ SDK 自带的 android.jar 只包含公开 API 的存根,系统应用开发却离不开隐藏接口。
  2. 改一行代码要全编半小时 ------ 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。

验证:可以执行以下命令确认是否包含目标类:

bash 复制代码
jar 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 ActivityManagerServicePackageManagerService 等核心服务
framework.jar m framework-minus-apex frameworks/base ActivityViewHandler 等基础类
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 上遇到差异,欢迎交流补充。

相关推荐
如此风景2 小时前
Kotlin Flow操作符学习
android·kotlin
plainGeekDev3 小时前
GreenDAO → Room
android·java·kotlin
weiggle3 小时前
第八篇:ViewModel + Compose——生产级状态管理实践
android
恋猫de小郭8 小时前
Amper 正式转正 Kotlin Toolchain ,Gradle 未来何去何从
android·前端·flutter
plainGeekDev9 小时前
ButterKnife → ViewBinding
android·java·kotlin
成都大菠萝1 天前
Android Car CarProperty 车辆信号链路
android
敲代码的鱼1 天前
PDF 预览与签名批注写回 支持安卓 iOS 鸿蒙 UTS插件
android·前端·ios
时光足迹1 天前
uni-app 视频通话实战:康复师与患者视频问诊的 6 个致命 Bug 与解决方案
android·ios·uni-app
Coffeeee1 天前
闲聊几句,Android老哥们,你们多久没做技改需求了
android·程序员·代码规范