
做RK3568 Android12方案的工程师应该都经历过这个场景客户拿来一个APK说要预置进系统开机就能用、不能卸载。一开始我直接把APK丢进vendor/app或者system/app重新编译打包确实很快见效。可后来需求开始分化有的客户要能卸载有的要恢复出厂后就消失还有的客户反过来要求把系统预置的某个应用彻底删掉。这一套“丢进去”的流程就不够用了。所以我把RK3568 Android12平台上预置APK的三种模式专门整理了一遍不可卸载、可卸载、永久删除。这三种模式虽然都叫“预置APK”但底层逻辑完全不同。这篇内容会把每种模式的原理、mk写法、验证方法、常见坑都讲清楚适合正在做RK3568/3588等瑞芯微平台方案定制、系统集成或者维护Android源码的工程师参考。看完之后至少能解决一个很实际的问题客户再提类似需求时你可以直接告诉他怎么做而不是先试错一轮。1. 整体思路先搞清楚预置APK的本质在动手改代码之前我建议先把Android的多分区模型想明白。因为“能不能卸载”并不是APK自己决定的而是由它所处的分区决定的。1.1 为什么同一件事会有三种模式Android系统里system分区和vendor分区默认以只读方式挂载PackageManagerService启动时扫描这些只读目录下的APK会把它们标记为系统应用界面上不提供卸载按钮。data分区是可读写的这个分区下的APK对用户来说就是普通应用可以随意卸载。这种分区归属差异天然把预置APK分成了“不可卸载”和“可卸载”两大类。而“永久删除”则是指从编译产物里彻底移除某个应用连system/vendor/product镜像里都不再包含它本质上是删干净而不是隐藏或停用。这个机制看似简单实际项目里却很容易绕晕。比如客户要求“APK可以卸载但恢复出厂设置后希望它还在”有人直接把APK放在/system/app里结果用户根本卸载不掉还有人放在/data/app里恢复出厂后又发现APK没了原因就是这两类目录在出厂镜像里的存在形式完全不同。所以我接到需求时一定会先确认两个问题这个APK允许用户卸载吗恢复出厂设置后它要不要重新出现这两个答案的组合基本就确定了技术路线。1.2 RK3568 Android12的预置目录与分区分布以RK3568官方SDK为例Android12源码中和产品相关的目录集中在device/rockchip/rk356x/以及vendor/rockchip/。真正声明预置APK的文件通常是device/rockchip/rk356x/rk3568_r.mk或rk3568.mk还有vendor/rockchip/common/device.mk编译时通过PRODUCT_PACKAGES变量把模块打包进对应的分区镜像。分区方面RK3568 Android12出厂镜像常见的有几个system分区放系统框架和系统应用vendor分区放硬件相关服务和vendor应用product分区是专门给产品定制内容用的data分区则是用户数据区。这四个分区的读写性质决定了APK能还是不能卸载。system、vendor、product里放的APK默认是只读的可以停用但不能卸载data里放的APK就是用户可以直接卸载的正常应用。这里有个常见误区很多人默认把prebuilt APK放到system/app下觉得最保险。但从分区空间管理和OTA升级角度来看产品定制应用放到product/app更合适因为product分区本来就是为产品差异化定制的升级system分区时可以保持不变避免每次系统升级都要把客户应用重新打入system分区。不过需要注意RK3568平台上vendor和product分区大小受parameter.txt限制如果APK比较大要提前核对分区剩余空间免得后期因为空间不足反复调整。2. 模式一不可卸载APK不可卸载是行业定制里最普遍的需求。广告机、收银机、工控面板这类设备客户往往要求应用常驻用户不能乱动。2.1 不可卸载的本质只读分区天然屏蔽卸载按钮系统起来之后PackageManagerService会扫描/system/app、/system/priv-app、/product/app、/vendor/app这些只读目录。扫描到的APK会被打上FLAG_SYSTEM标志设置界面里的“卸载”按钮就是根据这个标志控制的。所以不可卸载并不是厂商做了限制而是这些目录本身不允许写入用户层面根本不提供卸载入口。不过要提醒一点虽然不能卸载但用户可以到“设置 - 应用 - 已安装应用”里点击“停用”停用后应用图标从桌面消失相关服务不启动对普通用户来说体验很像卸载。如果客户要求连停用都不行那就得从Launcher和权限管理角度做更深层限制这属于另一个话题。大部分情况下客户能接受“不能卸载”就够用了不必过度开发。2.2 prebuilt方式把现成APK打入只读分区当你拿到一个已经编译好的APK文件最常见做法是在device/rockchip/rk356x/下建一个apps子目录把APK放进去然后写一个mk文件include $(CLEAR_VARS) LOCAL_MODULE : MyDemoApp LOCAL_MODULE_CLASS : APPS LOCAL_MODULE_SUFFIX : $(COMMON_ANDROID_PACKAGE_SUFFIX) LOCAL_SRC_FILES : MyDemoApp.apk LOCAL_CERTIFICATE : PRESIGNED LOCAL_MODULE_TAGS : optional include $(BUILD_PREBUILT)然后在产品mk文件里添加一行PRODUCT_PACKAGES MyDemoApp这样就完成了一次最基本的不可卸载预置。关键参数有几个直接影响后果LOCAL_CERTIFICATE : PRESIGNED表示沿用APK自带签名适合普通第三方应用。LOCAL_CERTIFICATE : platform表示用系统的platform证书重新签名适用于需要申请系统权限的应用比如需要WRITE_SECURE_SETTINGS、READ_LOGS这类权限。LOCAL_CERTIFICATE : testkey这个要慎用。很多项目里预置后出现“签名不一致”或权限异常就是因为重签时用了不合适的key导致APK的系统权限校验对不上。如果想放到/system/priv-app需要追加LOCAL_PRIVILEGED_MODULE : true这个目录下的应用才有资格申请signature|privileged级别的权限。如果APK是源码形式放在packages/apps/里那就不需要prebuilt mk源码目录自带的Android.mk或Android.bp会负责编译只要把模块名加到PRODUCT_PACKAGES即可。源码编译的优势是资源、so库可以统一处理但客户交付的大多是apk文件所以prebuilt方式在实际项目中更常见。2.3 放到哪个分区有讲究前文提过system、product、vendor都算只读目录但选哪个分区要考虑权限和更新策略。priv-app位置比较特殊只有/system/priv-app以及部分平台上的/vendor/priv-app具备signature|privileged权限product/app和vendor/app虽然也是系统应用但不等同于priv-app。如果APK里没有申请系统级权限放哪里都行如果申请了系统权限除了放对目录还要检查privapp-permissions白名单否则系统启动时可能拒绝授予权限应用会崩溃或功能异常。另外要考虑OTA升级的影响。APK放在/system/app升级system分区时可以直接更新放在/vendor/app一般跟OEM固件一起升级放在/product/app也是跟随product分区升级。放在data分区则OTA一般不动data数据升级后旧版本可能残留。RK3568方案公司做行业定制时客户经常要求“系统升级后应用不能丢”这类需求优先选择只读分区。2.4 编译后如何验证不可卸载预置成功编译指令一般是source build/envsetup.sh lunch rk3568_r-userdebug make -j$(nproc)不同SDK版本的lunch名称可能有差异以当前工程实际支持为准。编译完成后可以检查out/target/product/rk3568/下的镜像文件也可以直接刷机验证adb shell pm list packages -f | grep 包名 adb shell pm path 包名如果返回路径是/system/app/MyDemoApp/MyDemoApp.apk或/system/priv-app/MyDemoApp/MyDemoApp.apk说明不可卸载预置成功。这时候到系统设置里看也是找不到卸载按钮的。3. 模式二可卸载APK可卸载模式比不可卸载复杂一些因为涉及到“卸载后恢复出厂设置要不要再回来”这个问题。实际项目里这个需求非常常见尤其是行业设备客户既要用户能卸载又要恢复出厂设置时能还原到出厂状态。3.1 方案A用PRODUCT_COPY_FILES直接预置到data/app最简单的可卸载预置是把APK复制到data分区下的app目录。具体mk写法PRODUCT_COPY_FILES device/rockchip/rk356x/apps/MyDemoApp.apk:data/app/MyDemoApp/MyDemoApp.apk编译打包时APK会被写入userdata.img或data分区的初始文件系统。系统第一次开机PackageManagerService会扫描/data/app由于data分区是可读写的这个APK会被当成一个“出厂时已经安装好的普通应用”用户可以在设置里正常卸载。这里有个很容易误解的点预置到data/app的APK恢复出厂设置后到底还在不在这取决于出厂镜像里data分区的内容。如果刷机时烧录了包含这个APK的userdata.img那么恢复出厂相当于重新刷入userdata内容APK会重新出现。如果恢复出厂操作只是简单格式化data分区出厂镜像里没有这个APK那它就会被清掉。所以客户说“卸载后恢复出厂希望还能出现”就必须确认产线刷机或恢复出厂逻辑是否会重新写回data分区初始内容。3.2 方案B首次开机后从只读目录拷贝到data/app还有一种更灵活的做法APK先放在只读分区的一个预留目录比如/system/oem_app/系统第一次开机时通过一个系统应用或者init脚本把APK拷贝到/data/app然后由PackageManager扫描安装。这个方案的好处是灵活可以动态决定哪些机器安装、哪些机器不安装坏处是逻辑复杂容易踩一个经典坑用户卸载应用后重启设备开机逻辑又把APK从只读目录拷回来了。解决方案很简单必须记录“首次安装”标记。比如在/data/misc/下写一个flag文件或者在应用SharedPreferences里保存一个布尔值发现曾经安装过就不再继续安装。我见过不止一个项目栽在这个细节上客户反馈“应用卸载了怎么重启又回来”一查全是忘记做标记。方案B的另一种用途是版本管理。比如客户希望出厂时安装V1.0但服务器推送了V2.0用户升级后系统重启不应该再把V1.0装回来。这个场景下拷贝前判断一下当前安装版本逻辑会比简单标记更完善一些。3.3 恢复出厂设置的行为差异可卸载模式的恢复出厂行为本质上是“预置数据”和“清除用户数据”之间的博弈。行业设备客户通常希望应用恢复出厂后回到初始状态把APK预置到data/app镜像里是最合适的方式。如果预置到只读分区用户卸载动作根本无法生效不符合“可卸载”的需求。RK3568平台的恢复出厂默认走bootable/recovery下的恢复流程。要修改恢复出厂时保留或清除某个预置应用一般需要改recovery脚本、tw_wipe_data或自定义分区清理逻辑。普通工程师不建议轻易动recovery因为操作流程敏感改不好会影响整机恢复。建议先验证默认行为是否符合需求再决定是否做定制。如果只是希望“卸载后恢复出厂重新出现”优先考虑data分区镜像预置这个方案改动最小。4. 模式三永久删除预置APK永久删除和预置是相反方向的操作。这个需求通常来自两类场景一类是客户觉得系统自带的应用多余比如浏览器、邮件、联系人里某些组件另一类是客户自带应用的包名和老系统应用冲突不删除老应用就无法安装。不管是哪种要的是系统镜像里彻底没有这个东西而不是用pm disable或者Launcher隐藏。4.1 先从编译入口移除永久删除第一步是在整个源码树里找到这个模块的所有引用。以删除内置邮件应用为例grep -rn Email device/rockchip/rk356x/ vendor/rockchip/common/ build/target/product/ grep -rn Email --include*.mk --include*.bp --include*.conf .grep结果里凡是PRODUCT_PACKAGES项中引用的模块名都要删掉。如果源码里直接有packages/apps/Email目录可以考虑把目录删除或从打包列表中剔除。注意一些基础应用可能定义在build/make/target/product/full_base.mk、sdk_base.mk里这些文件在device目录下看不到但编译系统会默认带入需要仔细搜索不能只看产品mk。修改完成后重点来了必须清掉增量编译产物。很多人只改了mk然后继续增量编译旧APK仍然残留在out/target/product/rk3568/system/目录里被打进新镜像。稳妥做法是删除out/target/product/rk3568/system/app/Email/或相关目录或者干脆删除out目录下的system、vendor、product三个子目录再全编。工程量大一点但结果是干净的。4.2 清理残留资源权限XML、so库、overlay和out产物删除APK不只是删一个文件那么简单要连带的资源有几种权限XML如果这个应用申请了privileged权限frameworks/base/data/etc/privapp-permissions-platform.xml或vendor/etc/permissions/下可能有对应条目。不删除的话系统启动时可能出现权限校验警告甚至导致周边应用异常。so库部分APK会预置动态库到system/lib、vendor/lib、system/lib64等目录。删除应用时要把这些库也清掉否则白占分区空间。overlay资源vendor/rockchip/common/overlay下可能有针对这个应用的字符串、图标覆盖配置这些也要一并处理。开机自启动或系统服务引用比如init.rc里写了start xxx或者SystemUI状态栏、快捷开关里引用了该应用这类隐藏引用最容易忽略。我一个习惯做法是grep一次包名和模块名在整个源码树里的全部命中逐条确认而不是只搜显眼的产品mk文件。因为预置一个APK的引用链可能横跨device、vendor、frameworks、build四个目录少一处不干净最后刷机验证时就会出问题。4.3 一个完整示例永久删除系统浏览器假设要删除RK3568 Android12 SDK里的内置浏览器Jelly实际路径可能在packages/apps/Jelly或vendor/rockchip/common/apps/Jelly。完整步骤可以这样走find packages/apps -maxdepth 2 -iname *jelly* grep -rn Jelly --include*.mk --include*.bp device/ vendor/ | grep PRODUCT_PACKAGES如果Jelly出现在PRODUCT_PACKAGES清单里直接去掉这一行。如果源码目录存在且没有被其他模块依赖删除目录或注释Android.mk。然后搜索权限和sogrep -rn jelly --include*.xml frameworks/ vendor/再看out/target/product/rk3568/system/app/Jelly等中间产物目录有残留就删除。最后重新编译source build/envsetup.sh lunch rk3568_r-userdebug make -j$(nproc)编译完成后检查out目录下是否还有Jelly相关路径刷机后进系统验证。4.4 验证删除是否彻底刷机后验证命令adb shell pm list packages -f | grep -i jelly adb shell ls /system/app /system/priv-app /vendor/app /product/app | grep -i jelly没有任何输出说明删除干净。还有一招更稳把system.img和vendor.img挂载到本地直接查镜像内文件防止刷机后实际内容与预期不符。pm list packages只能反映运行时包管理器扫描到的结果万一APK文件还在但没被正确扫描也能通过镜像挂载查出来。5. 三种模式对比与选型建议到这里三种模式的技术路线已经清楚了。把它们放在一起对比选型时会更直观。模式部署位置用户能否卸载恢复出厂后是否存在典型场景不可卸载system/app、system/priv-app、product/app、vendor/app否是行业核心软件、系统组件、客户强制要求常驻可卸载data镜像预置data/app是是取决于userdata镜像可卸载但希望恢复出厂恢复初始状态可卸载首次拷贝只读分区预置目录 - 首次拷贝到data/app是否除非拷贝逻辑保留动态分发、按条件安装、需要版本控制永久删除不在任何分区不适用不适用去除不需要的预装应用、减少空间占用、避免包名冲突我的选型逻辑是这样的客户说是行业App且不能卸载直接选只读分区优先product分区客户要求能卸载同时希望恢复出厂后还回来优先data镜像预置客户要求能卸载但恢复出厂后要消失或者设备需要动态发不同应用那就做首次拷贝方案一定记得加安装标记客户嫌弃预装软件太多直接永久删除别用停用或隐藏来糊弄因为后续系统包名冲突或空间不足时还得回头补课。6. 常见问题与排查技巧实录下面这些问题是RK3568 Android12预置APK项目里反复出现的我把排查思路也写出来可以直接当成速查表用。6.1 预置后APK没有出现在系统里排查顺序先看编译产物里有没有APK文件再看包名是否被PackageManager扫描到最后看APK签名是否有效。Android12对APK签名要求更严格如果客户APK只做v1签名在某些情况下可能不识别建议用v2v3签名的release包。还要检查mk里LOCAL_MODULE名字是否和PRODUCT_PACKAGES一致大小写不匹配也会导致编译时找不到模块。6.2 可卸载APK卸载后重启又重新出现这个问题的根源几乎都是首次开机拷贝逻辑没有做“只执行一次”的标记。解决办法是增加一个flag文件或数据库标记位判断“曾经安装过”就不再拷贝。注意不要只依赖应用自身的SharedPreferences因为用户卸载时可能连data目录一起清掉flag文件要放在系统目录比如/data/misc/。6.3 权限报错或应用闪退如果APK放在priv-app目录却因为没有对应的privapp-permissions白名单条目导致闪退日志里通常会看到not granting permission的提示。解决办法是在对应白名单XML中补充条目或者将APK放到非priv-app目录。还有一种情况是APK缺依赖库比如只带arm64 so却被装到只有32位库的环境中。RK3568本身是arm64位处理器但Android12平台要确认APK里的so有没有打包进vendor或system的lib64目录没有的话运行时会直接崩。6.4 删除后老包仍然存在重启又被拉起来大概率是增量编译残留。修改mk后如果out目录下对应的system/app或vendor/app子目录没删干净编译系统可能不会自动清理。处理办法是删除out目录下对应模块目录后重新编译或者做一次局部clean。另外如果模块名被多个mk文件引用只删了一处另一处又把模块重新带入也会出现这问题。解决办法是全源码grep模块名逐条清除。6.5 分区空间不足预置大APK到系统分区前先检查parameter.txt中分区大小。RK3568平台常见的system分区在2GB左右vendor在1GB左右具体以当前SDK配置为准。如果空间不够优先把应用放到product分区不要盲目改分区大小改分区表涉及底层量产工具和升级脚本影响面较大。也可以考虑精简系统里的其它预置大应用但每次删除前都要确认没有隐藏依赖。7. 一点个人实操体会我在多个RK3568 Android12项目里反复做过这几类操作最大的感受是预置APK本身不复杂复杂的是需求边界没定清楚。客户说“预置一个应用”你要追一句“能不能卸载恢复出厂后要不要还在”客户说“把XX删掉”你要确认是“从桌面上藏起来”还是“系统镜像里彻底没有”。这两个问题一旦确认技术路线基本就定了。另一个体会是编译系统的清理工作一定要做扎实宁可多删一次中间产物也不要被增量编译的残留文件干扰判断。希望这篇文章能帮你在RK3568 Android12开发里少踩几个坑尤其是那些“看起来可以了但重启就露馅”的坑。