Android Framework 文件应用移植到 AndroidStudio

DocumentsUIStudio ------ AOSP DocumentsUI 的 Android Studio 移植工程

将源码树 C:\ZQ\0629\packages\apps\DocumentsUI(Android 13 / TP1A,255 个 Java 文件) 移植为标准 Gradle 工程,可直接用 Android Studio 打开开发调试,改动可通过脚本同步回源码树。

📐 想看应用自身是怎么设计的?ARCHITECTURE.md ------ 分层架构、18 个包地图、运行时骨架、13 条核心数据流、组件清单、扩展点。 本文档只讲移植过程(隐藏 API 编译方案、编译报错、签名与装机)。

1. 工程信息

包名 com.android.documentsui
工具链 AGP 8.1.2 / Gradle 8.2 / JDK 17(与 CarScreensaverKotlin 一致)
compileSdk 33
minSdk / targetSdk 29 / 30(对齐 AOSP documentsui_defaults
语言 纯 Java,零 Kotlin,AndroidX
位置 C:\code\DocumentsUIStudio

2. 目录结构

bash 复制代码
DocumentsUIStudio/
├── settings.gradle.kts / build.gradle.kts / gradle.properties
├── gradle/wrapper/gradle-wrapper.properties
├── scripts/sync_back.sh              ← 改动同步回 0629 源码树
└── app/
    ├── build.gradle.kts              ← 依赖 + 隐藏 API 编译方案
    ├── proguard.flags                ← 原 AOSP 混淆规则
    ├── libs/framework-13-android-all.jar   ← 全量框架 jar(见 §3)
    └── src/main/
        ├── AndroidManifest.xml       ← 已删 package=/uses-sdk(AGP 8 要求)
        ├── java/com/android/documentsui/    ← 原 src/ 全量(256 个文件)
        │   ├── DocumentsStatsLog.java       ← 开发版桩(见 §4)
        │   └── ...(files/ picker/ dirlist/ roots/ ...)
        ├── java/com/android/modules/utils/build/SdkLevel.java  ← 并入的 modules-utils 源
        └── res/                      ← 原 res/ 全量

3. 隐藏 API 编译方案(核心机制)

AOSP 中 DocumentsUI 以 sdk_version: "system_current" 编译,源码使用了 @SystemApi/@hide 符号(UserHandle.ofUserManager.isManagedProfile(int)android.os.FileUtils 等), 标准 compileSdk 的 android.jar 里没有,直接编译会报"找不到符号"。

解法app/libs/framework-13-android-all.jar(Robolectric android-all 13, 178MB / 66510 条目,来自 Maven Central,含全部 框架类含隐藏 API) 被前置到 javac 的编译 classpath (见 app/build.gradle.kts):

  • 同名类按 classpath 顺序命中 → android.* 类型解析自全量 jar;
  • java.* 仍来自标准 android.jar(全量 jar 不含它们,正好互补);
  • 全量 jar 仅参与编译、不打入 APK(实测 APK 17.6MB / 框架 jar 178MB), 运行时用的仍是设备真实 framework。

⚠️ 三个必须遵守的点(否则编译必挂)

  1. ★ 必须前置到【classpath】,不是 bootclasspath。 实测 AGP 8.1.2 把 compileSdk 的 android.jar 放在常规编译 classpath 上 (JavaCompile.classpath 的第一项就是 platforms/android-33/android.jar), options.bootstrapClasspath 并不参与本次编译。 只设 bootclasspath 的现象就是"方案没生效"------满屏 cannot find symbol: UserHandle.of / myUserId / Uri.toSafeString / DocumentsContract.setManageMode / ContentResolver.getCache / Root.FLAG_HAS_SETTINGS / StatsLog.write(StatsEvent)

  2. 时序:在 doFirst {}(或 gradle.projectsEvaluated {})里改写。 脚本顶层 configureEach {} 会在 AGP 之后被覆盖。

  3. gradle.properties 必须设 android.nonFinalResIds=false AGP 8 起默认为 true → R 字段不再 final → AOSP 源码里 public static final int SORT_DIMENSION_ID_SIZE = R.id.size; 不再是常量表达式, 于是 @IntDef({...})element value must be a constant expression, 所有 case R.id.xxx:constant expression required(一次报 100+ 条,看着吓人, 实际就这一个开关)。

实测:修完 1+3 后 :app:compileDebugJavaWithJavac 一次通过, :app:assembleDebug 产出 app-debug.apk(17.6MB)。

⚠️ 运行时注意:debug 包直接安装时隐藏 API 可能被设备的 hidden-API 检测拦截 (logcat 有 warning,个别功能异常)。完整功能需按原 AOSP 方式预置 (platform 签名 + priv-app,签名平台应用自动豁免检查)。

4. 与 AOSP 的差异(同步回树时注意)

差异点 AS 工程侧 AOSP 侧
DocumentsStatsLog.java 手写桩:write() 不上报,atom ID 为占位值 stats-log-api-gen 生成,真实 atom ID
SdkLevel.java 并入 java/com/android/modules/utils/build/ 来自 frameworks/libs/modules-utils(构建依赖)
SelectionDemoActivity 最小桩类(0629 树裁了 demo 目录但 manifest 仍引用,补齐防运行时崩溃) 位于 src/.../selection/demo/(0629 已裁)
AndroidManifest.xml 已删 package=/uses-sdk(AGP 8 强制) 需保留这两个属性
ProvidersCache.updateAsync 的断言 已修复(见 §4.1,这是真 bug,回树时建议一并带过去) 上游仍是漏掉 FLAG_SUPPORTS_SEARCH 的过期断言

4.1 ★ debug 包启动闪退:上游过期断言(已修复)

现象adb install -r 装上后一启动就崩, java.lang.AssertionError at ProvidersCache.updateAsync(ProvidersCache.java:211), 调用链 DocumentsApplication.onCreateProvidersCache.updateAsync

根因(不是移植引入的,源码与 0629 树 1:1) :AOSP 上游 generateRecentsRoot() 给 Recents 根设了 3 个 flag,而 updateAsync() 里的断言只比对 2 个:

java 复制代码
// generateRecentsRoot()(第 138 行)
flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_SEARCH;
// updateAsync() 的断言(原第 211 行)------ 少了 FLAG_SUPPORTS_SEARCH
assert (recentRoot.flags == (Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_IS_CHILD));

为什么量产包从不崩、debug 包必崩 :AGP 8 对 debuggable 变体会保留并强制开启 Java 断言 ------ 实测反汇编已装 APK,ProvidersCachestatic final synthetic boolean $assertionsDisabled 在 DEX 里是常量 false (即断言恒执行);而 release/量产链路(D8 默认 + R8)会把整条 assert 剥掉 (用最小样例 + d8 实测验证过),所以上游这个 bug 在量产机上从来没暴露过。

修复 :把断言补齐成与实现一致的 3 个 flag(本工程已改)。 全工程共 58 处 assert,只有这一处是"常量与实现漂移",其余都是真正的空值/状态不变量检查, debug 下照常生效 ------ 以后若再爆 AssertionError,多半是挖到了真 bug,按业务判断处理即可。

排查时注意:adb shell dumpsys package com.android.documentsui 的 flags 里会有 DEBUGGABLE(debug 变体所致)+ UPDATED_SYSTEM_APP(覆盖安装系统应用所致),属正常。

5. 使用

bash 复制代码
# Android Studio:Open → 选 C:\code\DocumentsUIStudio → Sync → Run
#   ⚠️ Gradle JDK 必须选 17(不能是 21):AGP 8.1.2 的 JdkImageTransform(jlink)
#      在 JDK 21 上会失败(`androidJdkImage` 转换报错)。
#      命令行实编(本机验证用):
JAVA_HOME="C:/Users/ZQ/.jdks/jbr-17.0.14" gradle :app:assembleDebug

依赖版本速查:appcompat 1.6.1 / material 1.9.0 / recyclerview 1.3.0 / recyclerview-selection 1.1.0 / transition 1.4.1 / guava 31.1-android / commons-compress 1.21 / jsr305 3.0.2(对应 AOSP documentsui_defaults 的 static_libs)。

5.1 装机验证:已接入 platform 签名,可直接覆盖安装

设备(uis7870_3h10_car_native / userdebug / test-keys)上 com.android.documentsui 已作为 /system/priv-app/DocumentsUI 预置且为 platform 签名。工程已把平台密钥接进 Gradle(见 §5.2),debug / release 均用 platform 签名,且 versionCode 对齐到 设备上的 33,因此不必再改包名绕路:

路径 做法 说明
A. 覆盖安装(快,推荐迭代) adb install -r app/build/outputs/apk/debug/app-debug.apk 签名一致 + 版本号不回退即可覆盖;APK 落在 /data/app,privapp 白名单按包名仍生效
B. 推送替换 /system(等价预置形态) adb root && adb remountadb push app-debug.apk /system/priv-app/DocumentsUI/DocumentsUI.apk → 清 /data/app 残留 → adb reboot 最接近量产形态
C. AOSP 集成编译(最终验证) bash scripts/sync_back.sh C:/ZQ/0629m DocumentsUI → 刷机 最慢但最真实

5.2 平台签名配置

密钥文件 app/keys/platform.jks(PKCS12,alias = platform),SHA256 指纹 C8:A2:E9:BC:CF:59:7C:2F:B6:DC:66:BE:E2:93:FC:13:F2:FC:47:EC:77:BC:6B:2B:0D:52:C1:1F:51:19:2A:B8 ------ 已从设备拉取 /system/priv-app/DocumentsUI/DocumentsUI.apk 实测比对,与系统平台签名完全一致。

口令集中在 gradle.propertiesPLATFORM_KEYSTORE_* 四项(路径默认 app/keys/platform.jks, 相对工程根目录 )。密钥文件本身已进 .gitignorekeys/*.jks)。

⚠️ gradle.properties 是入库文件 (里面还有 android.nonFinalResIds=false 等必需开关), 所以口令也会一起提交。当前用的是 AOSP platform 测试密钥 ,私钥与口令本就是公开的, 可接受。若换成客户的量产平台密钥,请把口令挪到不入库的位置,构建脚本会按 -P 命令行参数 → ~/.gradle/gradle.properties → 工程 gradle.properties 的顺序取值:

bash 复制代码
# 例:口令放用户级配置,不随工程提交
# %USERPROFILE%\.gradle\gradle.properties
PLATFORM_KEYSTORE_PASSWORD=xxx
PLATFORM_KEY_PASSWORD=xxx

app/build.gradle.ktssigningConfigs.create("platform") 同时挂到 debugreleasedebugAndroidTest 也继承),开启 v1/v2/v3 签名、关闭 v4(.idsig 仅用于增量安装)。 密钥换位置只需改 PLATFORM_KEYSTORE_FILE;密钥缺失时构建不失败,但会回退 debug 签名并打印 警告 ------ 那种 APK 装不进设备。

验证签名是否真的生效(注意加 --min-sdk-version 23):

bash 复制代码
keytool -printcert -jarfile app/build/outputs/apk/debug/app-debug.apk | grep SHA256
# 期望:C8:A2:E9:BC:...:51:19:2A:B8(与设备平台证书一致)

"$ANDROID_HOME/build-tools/<ver>/apksigner" verify -v --min-sdk-version 23 \
    app/build/outputs/apk/debug/app-debug.apk

为什么必须加 --min-sdk-version 23:apksigner 只校验当前 minSdk 仍然需要的签名方案 。 本工程 minSdk = 29,直接 apksigner verify -v 会打印 v1: false / v2: false / v3: true,看起来像只签了 v3 ------ 其实 v1+v2+v3 三个块都在 , 只是 apksig 认为 minSdk≥24 时 v1 无需校验、minSdk≥28 时 v2 已被 v3 取代。 用 --min-sdk-version 23 复验即可看到 v1/v2/v3 全部 true(实测结论)。

6. 同步改动回源码树

bash 复制代码
bash scripts/sync_back.sh C:/ZQ/0629

脚本只同步 documentsui 包源码与 res/(增量、带 diff 提示),并自动跳过 DocumentsStatsLog 桩与 SdkLevel;Manifest 不自动同步 (属性差异见 §4,需手工处理)。 同步后在源码树里正常单编:m DocumentsUI

7. 已知限制 / 排坑

  1. 已实编验证通过:app:compileDebugJavaWithJavac + :app:assembleDebug 均 BUILD SUCCESSFUL,APK 17.6MB)。 命令行复现(必须 JDK 17,不能是 AS 自带的 JBR 21):

    bash 复制代码
    JAVA_HOME="C:/Users/ZQ/.jdks/jbr-17.0.14" \
      "C:/Users/ZQ/.gradle/wrapper/dists/gradle-8.2-bin/bbg7u40eoinfdyxsxr3z4i7ta/gradle-8.2/bin/gradle" \
      :app:assembleDebug
  2. 构建期 warning 可忽略processDebugResources 会打出一批 removing resource .../string/xxx_file_type without required default value ------ 这是 0629 树缺 res/values/mimes.xml(只剩各语言目录的陈旧翻译)导致的, 这些键在代码/res 里已无任何引用 (已核实),不影响功能。 (如要消除,可从上游 AOSP 补回 res/values/mimes.xml。)

  3. gradle-wrapper 只附 properties,无 gradlew/gradle-wrapper.jar------AS 打开自动补齐, 或用本地 Gradle 8.2。

  4. DocumentsStatsLog 桩使统计不上报(开发无影响);需要真实统计时从 AOSP 编译产物取回生成文件替换。

  5. 系统功能依赖:MANAGE_DOCUMENTS 等签名权限、IntentForwarderActivity 跨 profile 转发、privapp 白名单(privapp_whitelist_com.android.documentsui) ------ 已配置 platform 签名,覆盖安装即可拿到签名权限;priv-app 白名单在 /data/app 覆盖形态下同样按包名生效。

  6. 混淆:release 配置沿用原 proguard.flags,默认 isMinifyEnabled=false; 需要瘦身时自行打开。

相关推荐
传奇开心果编程2 小时前
【Jetpack Compose基础语法学与练】第8课 rememberSaveable,页面旋转/系统重建保留状态
android·学习·ui·kotlin·android jetpack
>Andre<3 小时前
UFS5.0标准中文全译·卷一:范围、术语与架构
android·linux·嵌入式硬件
事圆则缓3 小时前
Java 8 Lambda、Stream、Optional 与 Android 边界:从回调语法到运行时兼容
android·java
mmsx4 小时前
Android 地图十万要素不卡顿:空间网格 + 渐进式加载的移动端实践
android·大数据·opengl
Godikov5 小时前
Android 工业终端保活实战:前台服务 + 开机自启 + 更新自启的三重保障
android
字节暗面5 小时前
SO加固强度怎么量化?腾讯ACE与FairGuard静态分析实测
android·逆向
终端安全笔记7 小时前
描述文件、企业证书、MDM 不是三个名字,是三层
android·ios·智能手机
字节暗面7 小时前
一次游戏安全SO静态分析记录:腾讯ACE与FairGuard加固强度对比
android·逆向工程·游戏加固
传奇开心果编程7 小时前
【Jetpack Compose基础语法学与练】第9课 SideEffect副作用 + LaunchedEffect,处理异步任务和生命周期
android·学习·ui·kotlin·android jetpack