
Recovery 是 Android 的救命系统,负责 OTA 升级、恢复出厂、用户数据加密管理。本文剖析 Recovery 的架构、启动流程、与主系统的通信机制,并演示如何修改并构建一个自定义 Recovery。一、Recovery 到底是什么?很多人以为 Recovery 是 Android 系统的一个模式,其实它是一个完全独立的 Linux 系统:有自己的 Kernel(实际上和主系统共享一个 boot 镜像的内核)、自己的 ramdisk、自己的 init、自己的应用。它和主系统的关系类似:PC 主系统 - PE 启动盘 Android - RecoveryRecovery 启动时只挂载最小化的文件系统,不启动 Android 框架(没有 Java VM、Zygote、SystemServer),纯 C/C 应用,占用资源极小。二、Recovery 的两种存在形式1. 传统形式:独立 recovery 分区分区表: boot (Kernel 主系统 ramdisk) recovery (Kernel recovery ramdisk)启动时 Bootloader 根据按键 / BCB 决定加载哪个 boot image。2. 现代形式:合并到 boot 中(A/B 设备)A/B 设备砍掉了独立 recovery 分区,把 recovery 的内容融合进 boot 的 ramdisk:boot.img ├── kernel └── ramdisk ├── /init ├── /system/bin/recovery ├── /sbin/... └── /etc/recovery.fstab启动到主系统还是 recovery,看 Kernel cmdline:# 主系统启动 androidboot.force_normal_boot1 # Recovery 启动 (不带 force_normal_boot)init在第一阶段读到这个参数后,执行不同的init.rc。三、Recovery 的目录结构(精简版)/ ├── init (静态链接的 init 二进制) ├── init.recovery.${hw}.rc (硬件相关 init 脚本) ├── etc/ │ ├── recovery.fstab (Recovery 用的 fstab) │ └── recovery-resource.dat ├── sbin/ │ ├── recovery (主程序) │ ├── adbd (USB adb 守护进程) │ └── busybox (基础命令) ├── res/ │ ├── images/ (UI 图片) │ └── recovery.cfg ├── system/ │ └── bin/ └── tmp/ (临时挂载点)recovery.fstab描述了 Recovery 需要挂载的分区:# src mnt_point type mnt_flags fs_mgr_flags /dev/block/by-name/system /system ext4 ro,barrier1 wait,first_stage_mount,logical /dev/block/by-name/userdata /data f2fs defaults wait,check,formattable,quota /dev/block/by-name/cache /cache ext4 defaults wait,check /dev/block/by-name/misc /misc emmc defaults defaults /dev/block/by-name/metadata /metadata f2fs defaults wait,formattable四、Recovery 主程序的核心流程源码在bootable/recovery/,核心入口在recovery_main.cpp:intmain(intargc,char**argv){// 1. 重定向 stdio 到 last_logredirect_stdio(/tmp/recovery.log);// 2. 启动 logd 服务staticconstexprstructoptionOPTIONS[]{{locale,required_argument,NULL,0},{fastboot,no_argument,NULL,0},{0,0,0,0},};// 3. 加载 fstabautofstabLoadFstab();// 4. 解析启动参数(来自 BCB)std::vectorstd::stringargsget_args(argc,argv);// 5. 初始化 Device 抽象autodevicemake_device();autouidevice-GetUI();ui-Init(locale);// 6. 进入主循环InstallResult statusINSTALL_NONE;if(update_package!nullptr){// OTA 包安装statusinstall_package(update_package,...);}elseif(should_wipe_data){wipe_data(device);}elseif(should_wipe_cache){wipe_cache(device);}elseif(should_wipe_ab){wipe_ab_device();}else{// 进入交互式菜单statusprompt_and_wait(device,retry_count);}// 7. 写回结果到 BCBfinish_recovery();// 8. 重启reboot(normal);return0;}prompt_and_wait 交互菜单InstallResultprompt_and_wait(Device*device,intretry_count){staticconstchar*HEADERS[]{Android Recovery,Use volume up/down to move highlight;,power button to select.,nullptr,};staticconstchar*ITEMS[]{Reboot system now,Apply update from ADB,Apply update from SD card,Wipe data/factory reset,Wipe cache partition,Mount /system,View recovery logs,Power off,nullptr,};while(true){intchosenget_menu_selection(HEADERS,ITEMS,...);switch(chosen){case0:returnINSTALL_SUCCESS;case1:apply_from_adb(device);break;case2:apply_from_sdcard(device);break;case3:wipe_data(device);break;case4:wipe_cache(device);break;case5:mount(/system);break;case6:choose_recovery_file(device);break;case7:reboot(poweroff);break;}}}五、与主系统的通信:BCB(Bootloader Control Block)我们在前文讲过,misc 分区存放 BCB。Recovery 主要靠 BCB 与外界沟通:主系统触发 Recovery// frameworks/base/services/core/java/com/android/server/RecoverySystem.javapublicstaticvoidrebootWipeUserData(Context context,...){bootCommand(context,--wipe_data,--reasonreason);}privatestaticvoidbootCommand(Context context,String...args){File RECOVERY_DIRnewFile(/cache/recovery);File COMMAND_FILEnewFile(RECOVERY_DIR,command);StringBuilder commandnewStringBuilder();for(String arg:args){command.append(arg).append(\n);}Files.write(command.toString().getBytes(),COMMAND_FILE);// 触发 reboot 到 recoverySystemProperties.set(sys.powerctl,reboot,recovery);}Recovery 启动后读命令std::vectorstd::stringget_args(intargc,char**argv){std::vectorstd::stringargs;// 1. 优先从 BCB 读 (Bootloader 写入)bootloader_message boot;if(read_bootloader_message(boot)){if(boot.command[0]!0){// boot.recovery 中是用 \n 分隔的命令列表std::vectorstd::stringtokensSplit(boot.recovery,\n);args.insert(args.end(),tokens.begin(),tokens.end());}}// 2. 从 /cache/recovery/command 读if(args.empty()){std::string content;if(ReadFileToString(/cache/recovery/command,content)){std::vectorstd::stringtokensSplit(content,\n);args.insert(args.end(),tokens.begin(),tokens.end());}}returnargs;}退出时清理 BCBvoidfinish_recovery(){// 把 BCB 清掉,下次启动直接进主系统bootloader_message boot{};write_bootloader_message(boot);// 删除 /cache 中的命令文件unlink(/cache/recovery/command);}六、OTA 安装的核心:install_packageRecovery 最重要的工作就是安装 OTA 包。流程:InstallResultinstall_package(conststd::stringpath,...){// 1. 校验 ZIP 签名 (PKCS#7)if(!verify_package(path,key_path)){returnINSTALL_CORRUPT;}// 2. 打开 ZIPZipArchiveHandle zip;if(OpenArchive(path.c_str(),zip)!0){returnINSTALL_CORRUPT;}// 3. 提取并执行 META-INF/com/google/android/update-binaryintpipefd[2];pipe(pipefd);pid_t pidfork();if(pid0){// 子进程close(pipefd[0]);charstatus_fd[8];snprintf(status_fd,sizeof(status_fd),%d,pipefd[1]);execl(/tmp/update-binary,update-binary,3,// versionstatus_fd,path.c_str(),nullptr);exit(1);}// 父进程读 update-binary 的进度close(pipefd[1]);FILE*ffdopen(pipefd[0],r);charbuf[256];while(fgets(buf,sizeof(buf),f)){// 解析 ui_print xxx, progress xxx, set_progress xxxhandle_progress(buf);}intstatus;waitpid(pid,status,0);returnWEXITSTATUS(status)0?INSTALL_SUCCESS:INSTALL_ERROR;}update-binary 协议update-binary通过文件描述符 3 与 Recovery 主进程通信,简单的文本协议:ui_print Hello world!\n progress 0.5 30\n (进度条 50%,持续 30 秒动画) set_progress 0.75\n (进度条 75%) firmware hboot hboot.img\n clear_display\n七、Recovery 的 UI 系统Recovery UI 是个独立的图形系统,不依赖 SurfaceFlinger / WindowManager,直接操作 framebuffer / DRM。核心类:RecoveryUI// bootable/recovery/recovery_ui/include/recovery_ui/ui.hclassRecoveryUI{public:virtualvoidInit(conststd::stringlocale)0;virtualvoidSetBackground(Icon icon)0;virtualvoidSetProgress(floatfraction)0;virtualvoidPrint(constchar*fmt,...)0;virtualintWaitKey()0;virtualvoidShowText(boolvisible)0;};具体实现ScreenRecoveryUI直接操作 framebuffer:voidScreenRecoveryUI::draw_screen_locked(){if(!show_text){// 显示进度条 logodraw_background_locked();draw_foreground_locked();}else{// 显示文字菜单SetColor(MENU);// ...for(inti0;imenu_items;i){if(iselected){DrawHighlightBar(0,y,width,char_height);SetColor(MENU_SEL_BG);}gr_text(menu_font,x,ybaseline,items[i].text,1);}}}voidScreenRecoveryUI::update_screen_locked(){draw_screen_locked();gr_flip();// 把 framebuffer 内容显示到屏幕}gr_flip()内部通过 ioctl 通知 DRM 切换显示缓冲区:intgr_flip(void){gr_drawgr_backup_disp_buffer(gr_draw);structfb_var_screeninfovi;ioctl(fd,FBIOGET_VSCREENINFO,vi);vi.yoffset(current_buffer?vi.yres:0);ioctl(fd,FBIOPAN_DISPLAY,vi);current_buffer!current_buffer;return0;}八、定制 Recovery 实战如果想给设备做一个自定义 Recovery,通常要做几件事:1. 准备源码# 拉 AOSP 源码repo init-uhttps://android.googlesource.com/platform/manifest-bandroid-13.0.0_r80 reposync# 配置自己的设备sourcebuild/envsetup.sh lunchyour_device-userdebug2. 修改设备 Device 类// device/yourcompany/yourdevice/recovery/recovery_ui.cpp#includerecovery_ui/device.h#includerecovery_ui/screen_ui.hclassYourDevice:publicDevice{public:YourDevice(RecoveryUI*ui):Device(ui){}boolPostWipeData()override{// 擦数据后做点啥ResetCustomerSettings();returntrue;}BuiltinActionInvokeMenuItem(size_t menu_position)override{// 添加自定义菜单项if(menu_positionYOUR_CUSTOM_ITEM){DoSomethingCool();returnNO_ACTION;}returnDevice::InvokeMenuItem(menu_position);}};Device*make_device(){returnnewYourDevice(newScreenRecoveryUI);}3. 修改 BoardConfig.mk# device/yourcompany/yourdevice/BoardConfig.mk TARGET_RECOVERY_UI_LIB : librecovery_ui_yourdevice TARGET_RECOVERY_FSTAB : device/yourcompany/yourdevice/recovery.fstab # 启用 fastbootd TARGET_USERIMAGES_USE_F2FS : true BOARD_USES_RECOVERY_AS_BOOT : true BOARD_BUILD_SYSTEM_ROOT_IMAGE : false # UI 主题 TARGET_RECOVERY_PIXEL_FORMAT : RGBX_88884. 编译makebootimage -j$(nproc)# 或者只编译 recovery (传统 boot/recovery 分离的设备)makerecoveryimage -j$(nproc)输出在out/target/product/device/:boot.img— A/B 设备recovery.img— 传统设备5. 刷写adbrebootbootloader# A/B 设备fastboot flash boot boot.img# 传统设备fastboot flash recovery recovery.img九、minimal recovery UI 代码示例(脱离 AOSP)下面是一个最简化的 framebuffer Recovery UI 主循环,展示原理:#includestdio.h#includestdint.h#includefcntl.h#includeunistd.h#includestring.h#includelinux/fb.h#includelinux/input.h#includesys/ioctl.h#includesys/mman.hstructfb_ctx{intfd;uint8_t*pixels;intwidth,height,stride;};staticintfb_init(structfb_ctx*fb){fb-fdopen(/dev/graphics/fb0,O_RDWR);if(fb-fd0)return-1;structfb_var_screeninfovi;structfb_fix_screeninfofi;ioctl(fb-fd,FBIOGET_VSCREENINFO,vi);ioctl(fb-fd,FBIOGET_FSCREENINFO,fi);fb-widthvi.xres;fb-heightvi.yres;fb-stridefi.line_length;size_tsizefb-stride*fb-height;fb-pixelsmmap(0,size,PROT_READ|PROT_WRITE,MAP_SHARED,fb-fd,0);return(fb-pixelsMAP_FAILED)?-1:0;}staticvoidfb_fill(structfb_ctx*fb,uint32_tcolor){uint32_t*p(uint32_t*)fb-pixels;intcountfb-stride*fb-height/4;for(inti0;icount;i)p[i]color;}staticintread_key(intinput_fd){structinput_eventev;while(read(input_fd,ev,sizeof(ev))sizeof(ev)){if(ev.typeEV_KEYev.value1){// key downreturnev.code;}}return-1;}intmain(void){structfb_ctxfb;if(fb_init(fb)0){fprintf(stderr,fb init failed\n);return1;}intinput_fdopen(/dev/input/event0,O_RDONLY);intselected0;constchar*items[]{Reboot system,Wipe data,Apply update,Power off,};intn_itemssizeof(items)/sizeof(items[0]);while(1){// 简化:仅用纯色背景表示当前选项uint32_tcolors[]{0xFF008000,0xFF800000,0xFF000080,0xFF808000};fb_fill(fb,colors[selected]);intkeyread_key(input_fd);if(keyKEY_VOLUMEUP)selected(selected-1n_items)%n_items;if(keyKEY_VOLUMEDOWN)selected(selected1)%n_items;if(keyKEY_POWER){printf(Selected: %s\n,items[selected]);if(selected0)execl(/sbin/reboot,reboot,NULL);if(selected3)execl(/sbin/poweroff,poweroff,NULL);break;}}return0;}这个程序展示了 Recovery UI 的底层本质:直接 mmap framebuffer 读 input event,没有任何 GUI 库。十、踩坑经验fstab 字段错一个,挂载失败:Recovery 没法挂数据分区,wipe 不了 → 注意wait,formattable这些 flagAVB 拒绝刷未签名的 recovery:解锁后才能刷自制版本,或者要用 OEM 签名sideload(adb sideload)的工作目录:推过来的包默认存到/tmp/update.zip,大包会 OOM,要把/tmp挂大点fastbootd 和 recovery 共享 ramdisk:同一个二进制,只是启动参数不同(--fastboot)国行机型可能锁 AVB key:无法刷自制 Recovery,需要单独的解锁工具十一、总结Recovery 是一个被严重低估的系统:它是一个完整的 Linux 系统,只是为了救命而设计得极度精简BCB /cache/command是主系统与 Recovery 之间的桥梁OTA 安装 update-binary是它的核心使命直接操作 framebuffer让它在最恶劣的环境下也能工作理解 Recovery 不仅能帮你做 OEM 定制,也能帮你在用户报开不了机时,快速定位 BCB 或者 fstab 的问题。最后一篇我们来讲 Bootloader 的世界 — U-Boot 的移植实战。