
简介这是一份面向 Android 系统开发者的 A/B 分区 OTA 升级应用层调用 UpdateEngine 的实战源码适合已具备一定系统开发基础、正在折腾 OTA 升级却卡在参数与权限问题上的工程师参考。资源包共 115 个文件约 16.39MB以 64 个 xml 配置、6 个 java 源码、8 个 bin 编译产物及若干 gradle、properties、jar 等构建与依赖文件为主结构完整可直接对照工程理解调用链路。作者结合系统代码封装了解析类只需传入 update.zip 包即可完成解析与升级并在代码中备注了容易踩坑的关键点对定位莫名错误、排查权限与参数问题很有帮助。目前已有 7993 人学习下载适合需要快速跑通 A/B 分区 OTA 升级流程、少走弯路的开发者借鉴。1. 从一次“升级完重启还是老版本”说起这套 A/B 分区 OTA 应用层源码到底解决什么如果你做过 Android 系统升级大概率遇到过这种场景后台推了 OTA 包设备也提示“重启安装”结果重启完一看版本号纹丝不动ro.build.version.incremental还是旧的。排查半天发现不是包的问题而是应用层根本没把升级请求正确交给UpdateEngine——要么 payload 路径传错要么没监听onStatusUpdate回调要么 slot 切换后没做markBootSuccessful。这套Android A/B 分区 OTA 系统升级应用层调用 UpdateEngine Apk 源码解决的就是“系统底层有 A/B 升级能力但应用层不会调”的断层问题。它面向的是做车机、TV 盒子、定制平板、工业终端的 Android 系统开发与集成人员。A/B 分区也叫无缝更新本身是 Android 7.0 之后引入的机制系统有两套 slota/b升级时把新镜像写到非当前 slot重启切过去失败还能回滚。但UpdateEngine是系统服务普通 App 拿不到权限必须系统签名或预置到/system/priv-app。这份源码的价值在于它把applyPayload、进度回调、错误码处理、slot 校验、重启触发这一整条链路用可编译的 Apk 工程串起来了你拿到后改包名、改签名、改 payload URL 就能接进自己的项目。下面按“能跑起来 → 能看懂 → 能改对 → 不踩坑”的顺序拆。2. UpdateEngine 调用链路拆解从 bindService 到 applyPayload 的完整参数2.1 为什么必须用系统签名和 priv-app 预置UpdateEngine的 AIDL 接口定义在android.os.UpdateEngine它对应的服务是android.os.UpdateEngineService。这个服务在frameworks/base里注册权限是android.permission.RECOVERY或系统级signature|privileged。普通第三方 App 直接bindService会返回null或者抛SecurityException。常见做法有两种一是把 Apk 用平台签名platform.x509.pem/platform.pk8重签后推到/system/priv-app/YourOta/二是在AndroidManifest.xml里声明android:sharedUserIdandroid.uid.system但这种方式在 Android 10 之后对UpdateEngine的访问限制更严推荐第一种。源码工程里通常包含一个Android.mk或Android.bp用来把 Apk 编进系统镜像。如果你只是调试可以先用adb root adb remount把编译好的 Apk push 到/system/priv-app/然后adb reboot。注意remount在 Android 10 的动态分区上可能失败需要先adb disable-verity再adb reboot这是第一个容易翻车的地方。2.2 bindService 与 UpdateEngineCallback 的注册顺序调用UpdateEngine的第一步是绑定服务。源码里一般会封装一个UpdateEngineManager类核心代码如下// 绑定 UpdateEngine 服务必须在 applyPayload 之前完成 Intent intent new Intent(); intent.setClassName(android, android.os.UpdateEngineService); boolean bound bindService(intent, mConnection, Context.BIND_AUTO_CREATE); private ServiceConnection mConnection new ServiceConnection() { Override public void onServiceConnected(ComponentName name, IBinder service) { // 拿到 UpdateEngine 的 AIDL 代理对象 mUpdateEngine UpdateEngine.Stub.asInterface(service); try { // 注册回调必须在 applyPayload 之前调用 mUpdateEngine.bind(mCallback); } catch (RemoteException e) { Log.e(TAG, bind callback failed, e); } } Override public void onServiceDisconnected(ComponentName name) { mUpdateEngine null; } };逻辑说明bindService是异步的onServiceConnected回调触发后才能拿到mUpdateEngine。参数BIND_AUTO_CREATE保证服务不存在时自动创建。mUpdateEngine.bind(mCallback)这一步注册UpdateEngineCallback用来接收进度和状态。顺序不能反如果先applyPayload再bind你会漏掉最初的IDLE和CHECKING_FOR_UPDATE状态进度条会从中间开始跳。UpdateEngineCallback需要实现两个方法private UpdateEngineCallback mCallback new UpdateEngineCallback() { Override public void onStatusUpdate(int status, float percent) { // status 取值见 UpdateEngine.UpdateStatusConstants // percent 是 0.0~1.0 的浮点数不是 0~100 runOnUiThread(() - mProgressBar.setProgress((int)(percent * 100))); } Override public void onPayloadApplicationComplete(int errorCode) { // errorCode 0 表示成功其他值见 UpdateEngine.ErrorCodeConstants if (errorCode UpdateEngine.ErrorCodeConstants.SUCCESS) { // 升级写入完成可以提示用户重启 } } };参数说明onStatusUpdate的status常见值有IDLE(0)、CHECKING_FOR_UPDATE(1)、DOWNLOADING(2)、VERIFYING(3)、FINALIZING(4)、UPDATED_NEED_REBOOT(5)、REPORTING_ERROR_EVENT(6)、ATTEMPTING_ROLLBACK(7)。percent是 float直接乘 100 取整即可。onPayloadApplicationComplete的errorCode为 0 才是成功1是ERROR2是FILESYSTEM_VERIFIER_ERROR3是NOT_ENOUGH_SPACE4是PAYLOAD_MISMATCHED_TYPE5是PAYLOAD_VERIFICATION_FAILED。这些错误码在源码里通常有对应的中文提示改的时候别只改 UI 不改判断。2.3 applyPayload 的四个关键参数怎么填applyPayload是真正触发升级的方法签名如下mUpdateEngine.applyPayload( payloadUrl, // payload 的 URL支持 file:// 和 http(s):// offset, // payload 在文件中的偏移通常为 0 size, // payload 的字节数必须准确 headers // 传给 payload 的 HTTP 头String[] 形式 );源码里一般会从Intent或本地配置读取这些值。常见做法是OTA 包下载到/data/ota_package/update.zip然后解压出payload.bin用file:///data/ota_package/payload.bin作为payloadUrl。offset填 0size用new File(path).length()获取。headers如果走本地文件传new String[0]即可如果走 HTTP需要传{Authorization: Bearer xxx}这类头。这里有个血泪经验size必须和实际文件大小完全一致差一个字节都会报PAYLOAD_VERIFICATION_FAILED。另外payload.bin的路径不能放在/sdcard因为UpdateEngine运行在系统进程没有外部存储权限必须放在/data下且权限为0644或0600。3. 把源码跑起来编译、签名、预置、触发升级的完整步骤3.1 工程结构与关键文件说明拿到源码包后先看目录。典型的工程结构如下文件/目录作用AndroidManifest.xml声明sharedUserId、权限、Service 和 Activitysrc/com/example/ota/UpdateEngineManager.java封装 bind、applyPayload、回调src/com/example/ota/MainActivity.javaUI 入口进度条和按钮res/layout/activity_main.xml布局含 ProgressBar 和 TextViewAndroid.mk或build.gradle编译配置决定是否编进系统libs/可能包含update_engine.jar或 AIDL 生成的类如果源码里没有update_engine.jar你需要从 Android 源码的out/target/common/obj/JAVA_LIBRARIES/下找或者直接用adb shell dumpsys package确认系统是否支持。常见做法是在Android.bp里加static_libs: [update_engine]但不同 Android 版本路径不同Android 11 之后UpdateEngine的 AIDL 有变化需要对应版本的framework.jar。3.2 用 Android Studio 编译并手动签名如果你不想编进系统镜像可以先用 Android Studio 编译出未签名 Apk再用平台签名工具重签。步骤如下# 1. 用 Android Studio 或 gradle 编译出 app-debug.apk ./gradlew assembleDebug # 2. 用平台签名工具重签需要 platform.x509.pem 和 platform.pk8 # 这两个文件在 Android 源码的 build/target/product/security/ 下 java -jar signapk.jar platform.x509.pem platform.pk8 app-debug.apk app-signed.apk # 3. 推送到系统 priv-app 目录 adb root adb remount adb push app-signed.apk /system/priv-app/YourOta/YourOta.apk adb shell chmod 644 /system/priv-app/YourOta/YourOta.apk adb reboot逻辑说明signapk.jar是 Android 源码里自带的签名工具用平台密钥重签后Apk 就拥有了platform签名权限。adb remount在 Android 10 上可能提示remount failed这时需要先执行adb disable-verity重启后再adb remount。推送后chmod 644保证系统能读取。重启后可以用adb shell pm list packages | grep your.ota确认包已安装。参数说明/system/priv-app/下的 Apk 会被授予privileged权限但还需要在AndroidManifest.xml里显式声明android.permission.RECOVERY。如果源码里没声明加上uses-permission android:nameandroid.permission.RECOVERY / uses-permission android:nameandroid.permission.REBOOT /3.3 触发升级的三种方式与验证源码里通常提供三种触发方式按钮点击、Intent广播、adb命令。最直接的是按钮点击调用UpdateEngineManager.startUpdate(payloadPath)。验证是否成功可以看logcatadb logcat -s UpdateEngineService UpdateEngineManager正常流程会打印onStatusUpdate: status2, percent0.0到percent1.0然后onPayloadApplicationComplete: errorCode0。如果卡在status1不动说明 payload 校验失败检查size和文件路径。如果直接报errorCode5说明 payload 签名和当前系统不匹配需要用同一套密钥签名的 OTA 包。另一个验证点是 slot 状态adb shell update_engine_client --status输出里会显示current_slot和boot_slot。升级写入完成后boot_slot会变成另一个 slot但current_slot不变直到重启。重启后current_slot切换如果新系统启动失败bootloader 会自动回滚到旧 slot。这个机制是 A/B 分区的核心源码里一般会有一个markBootSuccessful的调用但注意这个调用需要在系统启动完成后由SystemServer触发应用层不要手动调否则会破坏回滚逻辑。4. 避坑与排查五个让升级卡住或回滚的典型问题4.1 现象applyPayload 返回成功但进度条不动原因bind(mCallback)在applyPayload之后才调用导致回调没注册上。或者mUpdateEngine为nullapplyPayload抛了RemoteException但被 catch 后没打印。解决把bind放到onServiceConnected里applyPayload放到bind成功后的回调里。可以在UpdateEngineCallback的onStatusUpdate里加一行Log.d(TAG, status status percent percent)确认回调是否触发。4.2 现象报错 errorCode3NOT_ENOUGH_SPACE原因/data分区剩余空间不足。A/B 升级需要把整个新系统镜像写到/data下的payload临时目录通常需要 2~3GB 空闲空间。解决升级前检查StatFs如果剩余空间小于 payload 大小的 2 倍先提示用户清理。源码里一般有checkSpace()方法改的时候把阈值调对。4.3 现象重启后版本没变但 update_engine_client 显示 boot_slot 已切换原因新 slot 的系统镜像有问题bootloader 回滚了。或者markBootSuccessful没被调用系统认为新 slot 不稳定。解决看adb shell dmesg | grep -i slot和adb shell logcat -b all | grep -i boot。如果是镜像问题重新生成 OTA 包如果是markBootSuccessful问题检查SystemServer里的UpdateEngineService是否正常。4.4 现象payload 路径用 /sdcard 时报权限拒绝原因UpdateEngine运行在system_server进程没有READ_EXTERNAL_STORAGE权限无法读取/sdcard。解决把 payload 放到/data/ota_package/下用file:///data/ota_package/payload.bin。如果必须从外部存储读先用FileInputStream拷贝到/data下再调applyPayload。4.5 现象Android 10 以上 remount 失败push 不进去原因动态分区和verity校验导致/system只读。解决adb disable-verity→adb reboot→adb remount。如果还不行用adb shell mount -o rw,remount /手动挂载。注意disable-verity会清除用户数据调试机无所谓量产机别这么干。5. 进阶把进度回调做成状态机并验证 slot 切换的可靠性源码里最常见的写法是直接在onStatusUpdate里更新 UI但这样在status跳变时会闪。我一般会加一个状态机把status映射成枚举只有状态变化时才更新 UIpercent单独走进度条。核心代码如下private int mLastStatus -1; Override public void onStatusUpdate(int status, float percent) { if (status ! mLastStatus) { mLastStatus status; runOnUiThread(() - mStatusText.setText(mapStatus(status))); } runOnUiThread(() - mProgressBar.setProgress((int)(percent * 100))); } private String mapStatus(int status) { switch (status) { case UpdateEngine.UpdateStatusConstants.IDLE: return 空闲; case UpdateEngine.UpdateStatusConstants.CHECKING_FOR_UPDATE: return 检查更新; case UpdateEngine.UpdateStatusConstants.DOWNLOADING: return 下载中; case UpdateEngine.UpdateStatusConstants.VERIFYING: return 校验中; case UpdateEngine.UpdateStatusConstants.FINALIZING: return 写入中; case UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT: return 等待重启; case UpdateEngine.UpdateStatusConstants.REPORTING_ERROR_EVENT: return 上报错误; case UpdateEngine.UpdateStatusConstants.ATTEMPTING_ROLLBACK: return 回滚中; default: return 未知; } }参数说明mLastStatus初始为 -1保证第一次回调一定更新。mapStatus里的常量来自UpdateEngine.UpdateStatusConstants不同 Android 版本可能多一个DISABLED用default兜底。验证 slot 切换可靠性可以写一个脚本循环触发升级并重启for i in 1 2 3; do adb shell am start -n com.example.ota/.MainActivity --es payload /data/ota_package/payload.bin sleep 30 adb reboot sleep 60 adb shell getprop ro.boot.slot_suffix done逻辑说明每次升级后重启打印当前 slot 后缀。正常情况下_a和_b会交替出现。如果连续两次都是同一个 slot说明升级没生效或回滚了。这个脚本我一般在量产前跑 10 次确认没有偶发回滚。最后说个习惯从那以后我每次接 OTA 项目都强制先跑一遍update_engine_client --status和logcat -s UpdateEngineService确认底层服务正常再动应用层代码。希望帮到你。本文还有配套的精品资源点击获取