Android Studio 使用 AOSP 私有 Stub JAR 的完整记录

2 阅读3分钟

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

github.com/Reginer/aos…

这个仓库按 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

github.com/Sable/andro…

同上

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

操作步骤:

  1. 关闭 Android Studio,避免 Gradle 正在占用旧 JAR。
  2. 备份原始 android.jar
  3. 下载 Reginer/aosp-android-jarandroid-28/android.jar,或者 fork 仓库对应目录中的 JAR。
  4. 用下载的 JAR 覆盖 SDK 的 platforms/android-28/android.jar
  5. 重新打开工程并执行 Gradle Sync。
  6. 执行一次 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”的问题,下面这些问题仍然存在:

  1. 系统 API 可能要求 signaturesignature|privileged 或其他系统权限。
  2. App 可能必须安装到 /system/priv-app/product/priv-app
  3. 某些权限还需要平台签名、权限白名单或 privapp-permissions 配置。
  4. 目标设备可能没有这个方法,或者方法签名和编译 JAR 不一致。
  5. Android 9 开始存在 non-SDK API 限制;替换 JAR 不会自动关闭运行时 hidden API 检查。
  6. 黑名单接口即使能编译,也可能在运行时抛出异常或被系统拒绝。

可以把问题拆成两层:

编译层:android.jar 是否包含类、方法和字段
运行层:framework 是否实现、App 是否有权限、hidden API 是否允许

参考资料