Android系统priv-app预置APK与so库配置指南

发布时间:2026/11/6 2:54:41
Android系统priv-app预置APK与so库配置指南 1. 先搞清楚 priv-app 这条路径到底特殊在哪把 apk 和所需库文件编译打包到 system/priv-app 路径下这件事在系统定制圈里几乎是绕不开的基本功。我在做定制系统这些年见过太多人第一次把 APK 直接丢进 system/app重启后发现权限申请全部返回失败debug 一整天最后才发现是路径放错了。priv-app 全称 privileged app字面意思是“特权应用”它不是一个普通的目录名而是 Android 权限模型里一道明确的分水岭放在这里的应用才有资格申请 signature|privileged 这一类权限。这类权限不开源给第三方只有预置在系统分区、且被标记为特权的应用才能拿到比如修改系统设置、静默安装、拦截通话、管理网络策略这些能力。所以只要你做的应用需要碰系统级能力priv-app 就是唯一的落脚点。这篇文章面向的是需要做系统预置的开发者、负责定制固件的同学以及想搞懂 AOSP 打包机制的人。我不会只给你一段可以复制的 Android.mk那样你换个项目还是不会。我想把这条链路上每一环为什么这么配、配错了会出什么症状讲透包括 so 库怎么跟着 APK 一起落到正确位置、白名单为什么必须写、签名不对会在哪一步炸、分区空间不够怎么算。这些内容在官方文档里往往分散在七八个页面且默认你已经懂了前后文。1.1 system/app 与 system/priv-app 的权限分水岭先把这个区别说死。Android 的权限保护级别里有一个组合叫signature|privileged系统在解析 APK 的 AndroidManifest 时会检查这个应用所在的扫描路径。在 PackageManagerService 的扫描逻辑里/system/priv-app、/system_ext/priv-app、/product/priv-app这几个目录下的应用会被打上PRIVILEGED标记/system/app下的不会。这个标记直接影响grantSignaturePermission的判断分支当应用申请的权限保护级别含 privileged 位时只有带标记的应用才会被授予否则就是一条Permission Denial的日志然后你的checkSelfPermission返回 DENIED。这个机制的设计意图很直白特权权限不能靠普通安装获取必须由固件制作者显式预置。你要绕开它只有两条路一是把应用做成平台签名并走 priv-app二是改框架代码把权限降级后者会破坏整个权限体系的兼容性除非你完全掌控这个固件且不打算过任何兼容性测试否则不要走。我见过有人图省事把权限保护级别从signature|privileged改成normal短期能跑后面系统升级一合并代码全废还把安全边界捅了个洞这个做法不值得学。顺带说一个容易混淆的点priv-app 本身不给你任何权限它只是给你“申请特权权限的资格”。权限还得在 Manifest 里声明还得被授予。很多人以为放进 priv-app 就万事大吉结果代码里调用还是抛 SecurityException原因是权限压根没在 Manifest 里写或者写了但被白名单拦住了这个下一节和第四章会细讲。1.2 哪些场景必须落到 priv-app判断标准其实很简单翻一遍你的 Manifest看有没有哪个uses-permission在 AOSP 的frameworks/base/core/res/AndroidManifest.xml里标着protectionLevelsignature|privileged。典型的有INSTALL_PACKAGES、DELETE_PACKAGES、WRITE_SECURE_SETTINGS、CAPTURE_AUDIO_OUTPUT、MODIFY_PHONE_STATE、REBOOT、DEVICE_POWER、NETWORK_SETTINGS、CHANGE_CONFIGURATION这些。只要有其中一个你就必须走 priv-app 加平台签名这条路没有折中方案。还有一类场景是隐性的应用需要sharedUserIdandroid.uid.system来跟系统进程共享数据目录或者需要访问某些只有 system uid 才能读的文件。这种也要求平台签名而平台签名的应用天然应该放在 priv-app因为你要用的能力大概率也在特权权限清单里。另外如果应用需要作为 DeviceOwner、需要注册为默认的某些系统组件默认 Launcher、默认输入法、默认短信虽然不一定要特权权限但放在 priv-app 更稳因为部分系统服务在做候选查询时会优先看 priv-app 里的应用避免被用户安装的同名应用顶掉。反过来如果你的应用只是普通业务应用权限全是 normal 或 dangerous 级别那放 system/app 甚至做成不可卸载的普通预置应用就够了没必要塞进 priv-app。priv-app 装多了会让系统启动扫描变慢也会扩大攻击面不是越多越好。1.3 手工拷贝为什么经常翻车很多人第一反应是adb root adb remount然后把 APK 推到/system/priv-app/。在 Android 9 之前这招勉强能用现在基本行不通或者行得通但一堆后遗症。原因有几个层面。第一层是权限白名单。从 Android 9 开始系统要求所有 priv-app 申请的signature|privileged权限必须在/system/etc/permissions/privapp-permissions-*.xml里有明确条目否则在ro.control_privapp_permissionsenforce模式下直接抛IllegalStateException让系统起不来在log模式下则是一条刺眼的警告。你在真机上手工拷一个 APK 进去白名单没配重启就卡开机动画。第二层是 odex。系统分区的应用在编译期通常已经做过 dex2oat生成.odex/.vdex。你手工拷一个未编译的 APK 进去系统在首次启动时会尝试在/data/dalvik-cache里现场编译这个过程可能超时也可能因为分区只读导致某些路径写不进去。虽然多数情况能兜住但启动时间会明显拉长还可能因为dm-verity校验不合规而触发异常。第三层是签名与 SELinux。手工 push 的文件 SELinux 上下文往往是默认的/system下的文件需要正确打标一般是system_file否则进程访问时被 avc denied。再加上如果 APK 不是用平台证书签名即使放进 priv-app 也拿不到特权权限等于白忙。所以正确做法是在编译期完成一切配置出镜像刷机而不是靠 adb push 打补丁。提示调试阶段确实需要快速验证时可以先adb disable-verity、adb remount把文件推进去之后记得restorecon一下目标路径并且把白名单 xml 一并 push 进去再重启。这套流程只适合临时验证不要写进交付流程。2. 方案选型预编译 APK 还是源码模块动手写配置之前先要决定一件更根本的事你要预置的是别人给你的成品 APK还是自己从源码编译的模块。这两条路在 AOSP 里的处理方式完全不同配置文件写法、so 库的落点、签名方式都不一样选错了后面全是返工。2.1 两条路线的适用边界预编译 APK 路线适用于第三方提供的成品包、需要用非公开证书签名的包、你只想把二进制塞进系统而不改源码的情况。它的标志性配置是BUILD_PREBUILT或android_app_import编译系统不解包你的 dex只做拷贝、重签名可选、JNI 库抽取这几件事。源码模块路线适用于你在 AOSP 源码树里有完整工程、可以用 Soong 或 Make 编译的情况。标志性配置是BUILD_PACKAGE或android_app加上LOCAL_PRIVILEGED_MODULE : true或privileged: true。这条路的好处是源码可维护、签名由LOCAL_CERTIFICATE统一控制、so 库走 ndk 编译后自动落到 APK 的 lib 目录缺点是你得把整个工程搬进源码树编译依赖要理顺。实际项目里经常是混合的你自己做的应用走源码模块外部合作的组件走预编译。两种方式可以并存PRODUCT_PACKAGES里同时列上就行不冲突。2.2 Android.mk 与 Android.bp 的取舍AOSP 从 Android 8 开始推 SoongAndroid.bp是主流但 Make 体系Android.mk到现在也没被完全移除两者在同一棵树里共存。选哪个不只看版本还要看你所处代码库的现状。如果整个设备的 product 配置还在用Android.mk风格团队里的人都熟 Kati 的变量体系那你用.mk更顺手尤其是LOCAL_PREBUILT_JNI_LIBS这个变量对预编译 so 的处理非常直接一行就能把 so 从 apk 旁边的目录搬进系统镜像的正确位置。而Android.bp在早期版本对预编译 so 的支持比较绕需要额外定义cc_prebuilt_library_shared模块再通过jni_libs引用配置量翻倍。但如果你在 Android 12 以上的树里工作强烈建议用Android.bp。原因很现实新版本的 Soong 对android_app_import的privileged、jni_libs、dex_preopt这些属性支持完善而且 Make 体系里某些变量在新版本已经被标记为废弃行为不稳定。我遇到过在较新的树上用LOCAL_PRIVILEGED_MODULE配预编译 APK产物路径正确但privapp-permissions的自动收集没触发最后换成 bp 才解决。所以选型原则是跟着你所在分支的主流走不跟版本对着干。2.3 单编、整编与 PRODUCT_PACKAGES 的关系这一步很多人搞不清。你在模块目录写好配置后只是让编译系统“认识”了这个模块它不一定会被打进镜像。真正决定它进不进system分区的是PRODUCT_PACKAGES。PRODUCT_PACKAGES是产品配置里的白名单写在device/vendor/device/device.mk或产品级*.mk里形如PRODUCT_PACKAGES MyApp。只有出现在这个列表里的模块才会被加入system镜像的安装集合。你make MyApp单编产物会出现在out/target/product/device/system/priv-app/MyApp/下面但那只是中间产物目录没有进最终的system.img不刷机的话对设备没有任何影响。要把改动落到设备上你得make systemimage重新生成镜像或者整编make -j之后刷入。调试阶段可以直接把out/.../system/priv-app/MyApp/整个目录推到设备对应位置配合 remount 验证。理解这一层你才不会出现“我明明编译成功了为什么设备上还是老版本”这类自我怀疑。3. 带 so 库的预编译 APK 完整配置现在进入正题假设我们手头有一个第三方给的 APK它依赖两个 native 库分别在lib/arm64-v8a/和lib/armeabi-v7a/下目标设备是 64 位系统但还要兼容 32 位。我们把它预置到 priv-app。3.1 目录结构与摆放规范先在源码树里建一个模块目录我一般放在vendor/vendor/apps/MyApp/这种位置避免和 AOSP 原生模块混在一起。目录结构建议这样组织vendor/vendor/apps/MyApp/ ├── Android.mk 或 Android.bp ├── MyApp.apk └── lib/ ├── arm64-v8a/ │ ├── libfoo.so │ └── libbar.so └── armeabi-v7a/ ├── libfoo.so └── libbar.so这里有个细节值得说APK 文件本身内部通常已经带了lib/目录用unzip -l MyApp.apk | grep lib/能看到。编译期把它再解出来放到模块目录旁边会造成镜像里同一份 so 存在两处——APK 内一份、priv-app/MyApp/lib/一份白占空间。我通常的做法是先把 APK 内的 lib 目录删掉再放到模块目录mkdir -p _work cd _work unzip ../MyApp.apk lib/* -d . cp -r lib ../lib cd .. rm -rf _work zip -d MyApp.apk lib/*zip -d会原地删除 APK 内的 lib 条目删完用unzip -l确认一下。这样最终镜像里只有priv-app/MyApp/lib/这一份节省的空间相当可观一个带完整 ffmpeg 的应用能省下十几兆。删完记得验证 APK 还能正常安装因为签名会变——如果 APK 是别人签好的且你要保持原签名删条目会破坏签名那就只能走重签名路线或者干脆保留两份把空间账算清楚再决定。3.2 Android.mk 逐行拆解这是最核心的一段配置。我把它拆开讲每一行都对应一个具体的编译行为LOCAL_PATH : $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE : MyApp LOCAL_MODULE_TAGS : optional LOCAL_SRC_FILES : MyApp.apk LOCAL_MODULE_CLASS : APPS LOCAL_MODULE_SUFFIX : $(COMMON_ANDROID_PACKAGE_SUFFIX) LOCAL_CERTIFICATE : platform LOCAL_PRIVILEGED_MODULE : true LOCAL_PRIVATE_PLATFORM_APIS : true LOCAL_MULTILIB : both LOCAL_PREBUILT_JNI_LIBS : \ lib/arm64-v8a/libfoo.so \ lib/arm64-v8a/libbar.so \ lib/armeabi-v7a/libfoo.so \ lib/armeabi-v7a/libbar.so LOCAL_DEX_PREOPT : false include $(BUILD_PREBUILT)LOCAL_MODULE是模块名必须唯一PRODUCT_PACKAGES里引用的就是这个名字。LOCAL_MODULE_TAGS : optional表示不参与eng/userdebug的强制构建集合靠PRODUCT_PACKAGES显式拉入这是预置应用的常规写法。LOCAL_MODULE_CLASS : APPS告诉编译系统这是个应用类模块配合LOCAL_MODULE_SUFFIX : $(COMMON_ANDROID_PACKAGE_SUFFIX)展开就是.apk编译系统才知道怎么处理产物命名。LOCAL_CERTIFICATE : platform是重点。这表示用build/target/product/security/下的 platform 证书对 APK 重签名。如果你的 APK 需要sharedUserIdandroid.uid.system或者要拿签名级权限这一行必须有。如果原 APK 是自签名的且你不打算改签名写成LOCAL_CERTIFICATE : PRESIGNED但那样就拿不到平台权限了要自己权衡。LOCAL_PRIVILEGED_MODULE : true就是标题里那句话的核心开关。它的作用是在安装规则里把目标路径从$(TARGET_OUT_APPS)也就是system/app改成$(TARGET_OUT_PRIVILEGED_APPS)也就是system/priv-app。等价的手动写法是LOCAL_MODULE_PATH : $(TARGET_OUT_PRIVILEGED_APPS)但用LOCAL_PRIVILEGED_MODULE更语义化而且某些版本里还会触发额外的特权应用相关处理建议用前者。LOCAL_PRIVATE_PLATFORM_APIS : true用于应用引用了hide的隐藏 API 的情况。不加这行编译期可能报cannot find symbol或者运行时抛NoSuchMethodError。这行只在预编译不重新编译源码时影响有限但对于源码模块路线是必需的。LOCAL_MULTILIB : both表示同时支持 32 位和 64 位。这一行直接决定 so 装到lib/arm64/还是同时装到lib/arm/。设备是 64 位 only 的话写64能省一半空间。LOCAL_PREBUILT_JNI_LIBS是 so 库搬运的核心。这里用前缀的写法含义是“按 APK 内的相对路径安装”。具体来说lib/arm64-v8a/libfoo.so会被安装到priv-app/MyApp/lib/arm64/libfoo.so也就是 APK 运行时通过System.loadLibrary会去找的位置。冒号分隔的写法src:dest可以自定义目标路径但对齐 APK 的 ABI 目录结构时没必要容易搞错。补一句前缀并非所有历史版本都支持较老的树里要用不带的裸路径写法编译时会自动按 ABI 归位具体以你分支的build/make/core/prebuilt_internal.mk为准。LOCAL_DEX_PREOPT : false关掉 dex 预编译。这一行的取舍后面第五章细讲简单说就是省空间换首次启动时间。3.3 Android.bp 等价写法如果你的树是 Soong 主导等价的配置长这样android_app_import { name: MyApp, apk: MyApp.apk, certificate: platform, privileged: true, dex_preopt: { enabled: false, }, jni_libs: [ libfoo, libbar, ], compress_dex: true, } cc_prebuilt_library_shared { name: libfoo, arch: { arm64: { srcs: [lib/arm64-v8a/libfoo.so], }, arm: { srcs: [lib/armeabi-v7a/libfoo.so], }, }, strip: { none: true, }, check_elf_files: false, compile_multilib: both, }privileged: true对应 mk 里的LOCAL_PRIVILEGED_MODULE。jni_libs引用的是模块名不是文件路径所以每个 so 都要单独定义cc_prebuilt_library_sharedsrcs按 arch 分别指定。strip: { none: true }非常关键——预编译 so 往往已经被第三方 strip 过编译系统再 strip 一次可能报错或者破坏符号加上这个属性跳过。check_elf_files: false用于跳过 ELF 校验某些加固过的 so 过不了校验不关掉会编译失败。compress_dex: true会把 dex 压缩存储减小镜像体积代价是启动时解压。这个属性和dex_preopt是两回事别搞混。我个人的经验是第一次配 bp 时先把所有属性写全跑一遍编译看 Soong 报什么再逐项调整。bp 的错误提示比 mk 友好得多会明确告诉你哪个属性不认识。3.4 源码模块路线下的 jni 产物落点如果你的应用是自己编译的so 库来自jniLibs或 ndk 工程配置思路完全不同。这时你不需要LOCAL_PREBUILT_JNI_LIBS编译系统会自动把LOCAL_JNI_SHARED_LIBRARIES列出的库装进 APK 的 lib 目录。Android.mk 侧大致是LOCAL_JNI_SHARED_LIBRARIES : libfoo libbar LOCAL_MULTILIB : both include $(BUILD_PACKAGE)而libfoo和libbar自己在各自的目录里用BUILD_SHARED_LIBRARY定义编译完自动被引用。产物最终落在out/.../system/priv-app/MyApp/lib/arm64/。Soong 侧对应的是android_app { jni_libs: [libfoo] }同时libfoo用cc_library_shared定义。这条路线的优点是 so 跟着编译产物走不需要手工搬运架构一致性由编译系统保证缺点是必须把 native 工程的构建脚本接进 AOSP 的 Soong 体系如果原来是用 CMake 或 ndk-build 独立构建的得改成cc_library_shared描述工作量不小。折中方案是保留独立构建把产出的 so 当成预编译文件处理也就是回到 3.2 的写法这在工程上更现实。4. 白名单、签名与 sepolicy三处最容易漏的收尾APK 和 so 都放对位置了编译也过了刷机后发现应用起不来或者权限申请失败九成问题出在这三块。它们不在主流程上但缺一个就前功尽弃。4.1 privapp-permissions 白名单从 Android 9 起Google 收紧了对 priv-app 的权限管控要求所有被授予的signature|privileged权限必须显式记录在许可名单里。文件位置是/system/etc/permissions/privapp-permissions-package-name.xml或者系统级的privapp-permissions-platform.xml格式如下?xml version1.0 encodingutf-8? permissions privapp-permissions packagecom.example.myapp permission nameandroid.permission.INSTALL_PACKAGES/ permission nameandroid.permission.DELETE_PACKAGES/ permission nameandroid.permission.WRITE_SECURE_SETTINGS/ /privapp-permissions /permissions这个文件怎么进镜像最直接的写法是在设备配置里加一条PRODUCT_COPY_FILESPRODUCT_COPY_FILES \ $(LOCAL_PATH)/privapp-permissions-com.example.myapp.xml:$(TARGET_COPY_OUT_SYSTEM)/etc/permissions/privapp-permissions-com.example.myapp.xml注意$(TARGET_COPY_OUT_SYSTEM)这个变量在分区化的构建里指向system写死/system/etc/permissions/在老版本可行新版本建议用变量。怎么知道要写哪些权限两个办法。一是翻 Manifest 里所有uses-permission对照 AOSP 的AndroidManifest.xml看哪些是signature|privileged。二是更省事的办法先把ro.control_privapp_permissions设成log模式刷机开机后抓 logcat搜PrivappPermissions系统会明确告诉你缺哪条adb logcat -d | grep -i privapp日志里会出现类似PrivappPermissions violation: com.example.myapp: android.permission.INSTALL_PACKAGES的记录照抄进 xml 就行。这个方法我用了很多次比手工比对 Manifest 快得多而且不会漏。收集齐全后把模式改回enforce再验证一遍开机不崩就说明配完了。注意白名单是 package 级别的改包名就要新建一个文件。另外同一个权限被多个应用申请时每个应用各自的 xml 里都要写不能共用。4.2 签名体系与 sharedUserId预编译 APK 走LOCAL_CERTIFICATE : platform时用的证书来自build/target/product/security/platform.pk8和platform.x509.pem。这个证书必须和你设备上其他系统组件用的是同一套否则签名级权限互相授不了sharedUserId也共享不了。实践中最常见的坑是第三方给的 APK 已经用他们自己的证书签过你在配置里加LOCAL_CERTIFICATE : platform编译系统会重签但重签后如果 APK 内部有校验自身签名的逻辑很多加固包和支付 SDK 会做这件事运行时会直接检测到签名变化然后退出。这时候你要么让第三方用你们的平台证书签好再给要么改走PRESIGNED并放弃平台权限没有第三条路。关于sharedUserId从 Android 9 开始系统对它的使用限制变多新应用不建议再用。如果确实需要Manifest 里写android:sharedUserIdandroid.uid.system同时签名必须是平台证书两个条件缺一不可。我见过有人只加了 sharedUserId 没换签名装上去直接INSTALL_FAILED_SHARED_USER_INCOMPATIBLE卡在安装阶段。4.3 SELinux 标签与策略补充/system分区的文件在file_contexts里通常已经有通配规则system/priv-app(/.*)?会映射到system_file所以你新加的文件大概率自动打标正确。但有两类情况需要额外处理。一是如果你的应用在自己目录下创建了子目录或特殊文件比如机制文件、socket访问时可能触发 avc denied。抓dmesg | grep avc或者adb shell dmesg看 denied 的类型和上下文然后在设备 sepolicy 目录下补规则形如allow priv_app myapp_data_file:file { read write create open };二是应用如果以platform_app的 seinfo 运行并需要访问某些vendor侧资源策略要单独开。补规则的原则是最小权限只开实际报错的那几项不要图省事写allow priv_app *:* *;那种写法过不了任何安全审查也会把设备的安全边界打开。编译策略时记得make selinux_policy或者整编单改.te文件不重新生成策略是不会生效的。这个点我踩过改了策略刷机没变化排查半天发现是产物没重新生成。5. 分区空间的实测账本与产物裁剪前面所有配置都对最后卡在system.img超出分区大小这个情况太常见了。系统分区大小是产品配置里写死的BOARD_SYSTEMIMAGE_PARTITION_SIZE定义了字节数编译时如果超了会直接报system image size exceeds partition size。5.1 分区容量估算先学会算账。一个带 so 库的 APK 装进 priv-app 后占用空间是三部分之和APK 本身去掉了 lib 之后的大小、解出来的 so 库、以及可选的 odex/vdex。假设 APK 原始 30MB其中 lib 占 20MB剩下的 dex 和资源 10MB。删掉 lib 后 APK 约 10MBso 库按 64 位和 32 位各一份算 20MB合计 30MB看起来和原来差不多但如果你的设备只需要 64 位LOCAL_MULTILIB : 64之后 so 只留一份总共 20MB直接省下三分之一。如果开了 dex 预编译还会多出.odex和.vdex体积大约是 dex 部分的 1.2 到 1.8 倍。一个有 5MB dex 的应用预编译后可能多占 8MB。这个比例不是固定的跟 dex 里的方法数、字符串池大小都有关系我的经验是留 1.5 倍余量做估算。改分区大小要动BoardConfig.mk里的BOARD_SYSTEMIMAGE_PARTITION_SIZE这个值必须是块大小的整数倍一般 4096 对齐。改大了还要确认分区表GPT里有足够空间否则刷机时写不进去。现在多数设备用动态分区super 分区改法变成调整BOARD_SUPER_PARTITION_SIZE和各个子分区的大小改完要重新生成 super 镜像流程更复杂动之前务必备份原厂分区表。5.2 odex/vdex 的取舍LOCAL_DEX_PREOPT这个开关值得单独说。设成false时APK 里的 dex 保持原样系统首次启动时会用 dex2oat 在/data/dalvik-cache里生成优化产物占用 data 分区而不是 system 分区。设成true时编译期就生成好.odex/.vdex放进 system首次启动更快但 system 分区多占空间。怎么选看你的约束。system 分区紧张、data 分区宽裕的设备关掉预编译。追求开机速度、data 分区也很紧张的设备打开预编译。还有一个折中LOCAL_DEX_PREOPT : true配合LOCAL_STRIP_DEX之类的裁剪或者只预编译主 dex。我自己的项目里通常关掉预编译理由是这类定制应用大多在开机后才会被拉起不在启动关键路径上省下的 system 空间更值钱。但如果是默认 Launcher 或默认输入法它们参与开机流程预编译的收益就很明显这时候我会打开。5.3 多架构 so 的瘦身计算LOCAL_MULTILIB有三个取值both、32、64、first还有prefer32。both是两份都装64只装 64 位32只装 32 位first装主架构那一份。如果你的设备是纯 64 位系统TARGET_SUPPORTS_32_BIT_APPS : false那必须用64因为 32 位 so 根本加载不了白占空间。如果设备支持 32 位应用但你的应用没有 32 位需求也可以设64代价是 32 位应用调用它时找不到库——但这个应用本身是预置的不会被 32 位进程调用问题不大。具体能省多少拿一个典型的多媒体库举例arm64 版本 8MBarmeabi-v7a 版本 6MB两份都装 14MB只装 64 位 8MB省 6MB。如果你的系统里预置了五六个这类应用轻松省出三四十兆够放好几个新应用了。提示改LOCAL_MULTILIB之后一定要在真机上跑一遍核心功能特别是涉及 native 调用的部分。有些 so 的 64 位版本存在符号缺失或者行为差异只装 64 位会在运行时报UnsatisfiedLinkError这种问题编译期看不出来。6. 排查实录从编译报错到运行崩溃这部分是我这些年攒下来的问题清单按阶段分类每个都给出定位方法和解决思路。6.1 编译打包阶段报错LOCAL_SRC_FILES指向的文件不存在。八成是路径写错或者文件名大小写不一致。Linux 文件系统区分大小写myapp.apk和MyApp.apk是两个文件。用ls -l确认一下。报错找不到BUILD_PREBUILT。说明你的Android.mk里漏了include $(CLEAR_VARS)或者模块目录没被build/make/core/Makefile的subdir_makefiles扫到。检查上层的Android.mk有没有include $(call all-subdir-makefiles)或者LOCAL_PATH的递归包含。报错system image size exceeds partition size。参考第五章的瘦身方案优先考虑关预编译、裁多架构 so、删 APK 内冗余 lib。现象编译通过但out目录下没有产物。检查模块名有没有加进PRODUCT_PACKAGES。单编make MyApp会在out/target/product/device/system/priv-app/MyApp/生成但system.img里没有这是两码事。6.2 安装与权限阶段现象开机卡 logologcat 刷IllegalStateException: Signature|privileged permissions not in privapp-permissions。白名单没配或者配漏了。按 4.1 的方法抓日志补齐。现象应用装上了但权限申请返回 DENIED。先adb shell dumpsys package com.example.myapp | grep -A 20 requested permissions看权限有没有被授予再看grantedPermissions列表。没授予的话确认三件事权限在 Manifest 里声明了、签名是平台证书、白名单里有条目。现象INSTALL_FAILED_SHARED_USER_INCOMPATIBLE。用了sharedUserId但签名不匹配。换平台证书重签或者去掉 sharedUserId。6.3 运行与 JNI 加载阶段现象java.lang.UnsatisfiedLinkError: dlopen failed: library libfoo.so not found。这是最高频的问题排查顺序是先adb shell ls /system/priv-app/MyApp/lib/arm64/确认 so 在不在正确位置再看设备的 ABI 是arm64-v8a还是armeabi-v7a用adb shell getprop ro.product.cpu.abi确认然后检查 APK 内声明的android:extractNativeLibs如果设成false系统会直接从 APK 内映射 so这时候你放在priv-app/MyApp/lib/的 so 反而不被使用需要在编译期把 so 打进 APK。这个属性值配错导致的问题非常隐蔽我调过整整一天。现象so 加载了但调用崩溃。可能是符号缺失用readelf -d libfoo.so看NEEDED项确认依赖的库都在。也可能是 so 被 strip 过头LOCAL_PREBUILT_JNI_LIBS路线下编译系统不会再 strip但cc_prebuilt_library_shared路线下要注意strip: { none: true }。现象应用启动即闪退无 Java 堆栈。多半是 native 层崩溃抓 tombstoneadb shell ls /data/tombstones/把最新那个 pull 出来用ndk-stack解析。6.4 常见问题速查表症状高概率原因定位命令处理方式开机卡 logoprivapp 白名单缺失adb logcat -d | grep -i privapp补 xml 并重新出镜像权限申请 DENIED非平台签名或未加白名单dumpsys package pkg改LOCAL_CERTIFICATE并补白名单so 找不到安装路径不对或 ABI 不匹配ls /system/priv-app/*/lib/核对LOCAL_MULTILIB与路径镜像超限多架构 so 预编译产物查看 out 目录各模块体积裁架构、关预编译安装报共享用户不兼容sharedUserId 与签名不符adb install报错码统一平台证书启动慢未预编译 dexdumpsys package dexopt打开LOCAL_DEX_PREOPTavc deniedSELinux 策略缺失dmesg | grep avc补最小权限规则签名校验失败应用内部自校验签名logcat 搜包名用原签名或联系提供方重签7. 几条来自实践的经验配置写到这基本完整了但真正让我少走弯路的是几个不太容易从文档里看到的东西顺手一起说了。第一个是目录规划。我习惯给每个预置应用单独一个模块目录配一个Android.mk或Android.bp而不是把所有 APK 堆在一个大目录里用循环批量处理。批量处理看着省事但一旦某个应用需要特殊配置比如单独设 multilib、单独关预编译你就得在大循环里加一堆 if维护成本比一开始就分开高得多。而且分开之后每个模块的LOCAL_MODULE清晰PRODUCT_PACKAGES里按需勾选做产品差异化也方便。第二个是版本管理。预编译的 APK 是二进制扔进 Git 会让仓库迅速膨胀。我的做法是单独用一个制品目录管理这些二进制通过脚本在构建前同步到源码树源码树里只提交配置文件和校验值比如 sha256。这样既能追溯每个版本用的是哪个包又不会把仓库搞成几百兆。第三个是验证清单。每加一个 priv-app我都会固定跑一遍这套检查pm path确认安装路径、dumpsys package确认权限授予、ls lib/确认 so 落位、dmesg确认无 avc、然后跑核心功能。这套动作五分钟能做完但能挡掉九成的低级问题。之前有次赶进度跳过验证直接交付结果白名单漏了一条权限客户那边开机就崩返工的代价远大于五分钟。第四个是可维护性。如果你预置的应用超过十个建议在device.mk里用独立的.mk文件按业务分组管理PRODUCT_PACKAGES比如apps-media.mk、apps-security.mk主配置里$(call inherit-product, ...)引入。产品变体多的时候这种拆法能让你快速组合出不同配置不用在几百行列表里翻找。最后一个体会是priv-app 这个机制本身并不复杂复杂的是它牵动的周边签名、白名单、SELinux、分区、dex 优化每一块都有自己的坑而且报错信息往往指向不了根因。真正高效的做法不是背配置而是建立一套从现象到根因的排查路径知道每个报错该去看哪里。上面那张速查表就是我这几年不断往里补条目的结果遇到新问题先查表查不到就补进去慢慢就形成了自己的知识库。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询