Android Studio 使用 AOSP 私有 Stub JAR 的完整记录
Android SDK 里的
android.jar只有公开 SDK 接口。遇到系统应用、Launcher、系统服务配套功能时,很多源码里存在的方法在 Android Studio 里根本没有代码提示,只能靠反射调用。这篇记录的思路是:把编译期使用的
android.jar换成包含更多 AOSP 接口的版本,让代码可以直接调用系统 API,不再手写反射。
1. 替换 JAR 解决的是编译问题
android.jar 主要用于编译和代码提示,它不是设备运行时的 framework.jar:
Android Studio 编译期
→ 使用自定义 android.jar
→ 可以直接写出 AOSP 中的隐藏/系统 API
设备运行期
→ 仍然使用系统镜像中的 framework 实现
→ 仍然受 API 版本、权限和 hidden API 策略限制
所以替换 JAR 之后:
能直接编译 ≠ 设备一定允许调用
能看到方法 ≠ 设备一定有对应实现
目标设备最好与 JAR 来自同一 Android API 版本和相近的 AOSP 分支。比如 Android 9 设备优先使用 android-28/android.jar,不要直接拿 Android 35 的 JAR 去编译面向 Android 9 的系统功能。
现成的 jar 包仓库
Reginer/aosp-android-jar
这个仓库按 API 版本提供 AOSP 编译出来的 JAR:
android-28/android.jar
android-29/android.jar
android-30/android.jar
...
android-37/android.jar
README 给出的使用方式是替换 Android SDK 对应平台目录里的 android.jar,然后重新 Sync。
Sable/android-platforms
同上
3. Android Studio 方案一:替换 SDK 平台目录中的 android.jar
这是最直接的方案,适合内部开发机或专用编译环境。
假设项目使用:
android {
compileSdk = 28
}
那么要替换的文件就是:
${ANDROID_SDK_ROOT}/platforms/android-28/android.jar
Windows 默认一般类似:
%LOCALAPPDATA%\Android\Sdk\platforms\android-28\android.jar
操作步骤:
- 关闭 Android Studio,避免 Gradle 正在占用旧 JAR。
- 备份原始
android.jar。 - 下载
Reginer/aosp-android-jar的android-28/android.jar,或者 fork 仓库对应目录中的 JAR。 - 用下载的 JAR 覆盖 SDK 的
platforms/android-28/android.jar。 - 重新打开工程并执行 Gradle Sync。
- 执行一次 clean build,确认编译器已经读取新 JAR。
Linux/macOS 示例:
cp "$ANDROID_SDK_ROOT/platforms/android-28/android.jar" "$ANDROID_SDK_ROOT/platforms/android-28/android.jar.bak"
cp android-28/android.jar "$ANDROID_SDK_ROOT/platforms/android-28/android.jar"
./gradlew --stop
./gradlew clean assembleDebug
Windows PowerShell 示例:
$SdkRoot = $env:ANDROID_SDK_ROOT
Copy-Item "$SdkRoot\platforms\android-28\android.jar" "$SdkRoot\platforms\android-28\android.jar.bak"
Copy-Item ".\android-28\android.jar" "$SdkRoot\platforms\android-28\android.jar" -Force
不要修改 build-tools 目录,也不要把这个 android.jar 打包进 APK。它只应该作为编译期平台类库存在。
4. 方案一的验证方式
替换完成后,直接写一个原本需要反射的系统 API。如果能导包、能自动补全、能通过编译,说明 Android Studio 的编译类路径已经生效:
import android.os.SystemProperties
val model = SystemProperties.get("ro.product.model", "unknown")
再例如某些 AOSP 内部接口:
import android.app.AppGlobals
val packageManager = AppGlobals.getPackageManager()
这里没有反射,调用的就是 JAR 中暴露出来的类和方法。
如果 Android Studio 仍然提示找不到类,按顺序检查:
compileSdk 是否和被替换的 android-XX 一致
替换的是否真的是 platforms/android-XX/android.jar
Gradle 是否仍在使用旧 daemon
是否执行过 Sync/Clean/Rebuild
项目是否另行配置了 compileSdk 或自定义 SDK 路径
5. Android Studio 方案二:在 AOSP 源码中编译 JAR
如果设备系统也是自己编译的,最稳妥的方式不是下载别人的 JAR,而是使用同一份 AOSP 源码编译。这样编译出来的接口、分支和设备 framework 更容易保持一致。
AOSP 使用 Soong 构建系统,框架 API Stub 在 frameworks/base 的 StubLibraries 配置中定义。不同 Android 分支的模块名可能略有变化,常见的私有 Stub 模块包括:
android_stubs_private
android_stubs_private_jar
编译示例:
source build/envsetup.sh
lunch aosp_arm64-userdebug
m android_stubs_private
构建完成后,在 out/soong/.intermediates 下查找对应 JAR:
find out/soong/.intermediates -type f \( -name '*android_stubs_private*.jar' -o -path '*android_stubs_private*' -name 'classes.jar' \)
不同 AOSP 分支的中间产物目录会变化,不要把某一台机器上的绝对路径写死。找到最终的私有 Stub JAR 后,将它复制为目标 SDK 平台目录下的 android.jar:
cp <AOSP输出的私有StubJAR> "$ANDROID_SDK_ROOT/platforms/android-28/android.jar"
如果只执行 m sdk,得到的可能是标准公开 SDK;公开 SDK 不一定包含你需要的隐藏接口。需要内部/私有接口时,应优先确认当前 AOSP 分支的 android_stubs_private 目标,而不是只拿公开 android_stubs_current。
6. AOSP 编译 JAR 的选择原则
设备 Android 9 → 选择 API 28 的 Stub JAR
设备 Android 10 → 选择 API 29 的 Stub JAR
设备 Android 11 → 选择 API 30 的 Stub JAR
更重要的是源码分支一致:
设备 framework.jar 来自哪份 AOSP
↓
优先使用同一分支编译出来的 android.jar
如果设备厂商对 framework 做过定制,官方 AOSP JAR 也可能缺少厂商新增接口。这种情况下要从厂商对应的 AOSP/framework 源码编译,或者把厂商接口补进自己的 Stub 工程。
8. 直接调用不等于绕过权限
这类 JAR 只解决“编译器看不到 API”的问题,下面这些问题仍然存在:
- 系统 API 可能要求
signature、signature|privileged或其他系统权限。 - App 可能必须安装到
/system/priv-app或/product/priv-app。 - 某些权限还需要平台签名、权限白名单或
privapp-permissions配置。 - 目标设备可能没有这个方法,或者方法签名和编译 JAR 不一致。
- Android 9 开始存在 non-SDK API 限制;替换 JAR 不会自动关闭运行时 hidden API 检查。
- 黑名单接口即使能编译,也可能在运行时抛出异常或被系统拒绝。
可以把问题拆成两层:
编译层:android.jar 是否包含类、方法和字段
运行层:framework 是否实现、App 是否有权限、hidden API 是否允许