这里主要分析non A/B模式下的recovery流程
A/B模式下的recovery在boot中
后续会不断补充,如果有疏漏或者错误的地方,请指出,共同学习,谢谢!

一、流程分析

首先列出recovery流程的几个重要点,接着会详细分析

  1. 加载recovery.fstab分区表
  2. 解析传入的参数
  3. recovery界面相关的设置
  4. 执行命令
  5. 如果没有命令,等待用户输入
  6. 结束recovery

1.加载分区表

首先看recovery.cpp的main函数,后面会解释为什么

[recovery.cpp]
int main(int argc, char **argv) {//因为这个时候还没启动logcat,这里应该是设置将log打印到屏幕上和recovery.log中android::base::InitLogging(argv, &UiLogger);//下面是加载pmsg log文件// Take last pmsg contents and rewrite it to the current pmsg session.static const char filter[] = "recovery/";// Do we need to rotate?//这里应该和重命名log文件有关bool doRotate = false;__android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter,logbasename, &doRotate);// Take action to refresh pmsg contents__android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter,logrotate, &doRotate);//看注释,这里是启动迷你版的adbd,为了使用adb sideload命令if (argc == 2 && strcmp(argv[1], "--adbd") == 0) {minadbd_main();return 0;}//猜测这里是把log输出重定向到/tmp/recovery.logredirect_stdio(TEMPORARY_LOG_FILE);//加载recovery.fstab并建立分区表信息,在etc/目录下,recovery模式连adb可以看到load_volume_table();//从上面建立的分区表信息中读取是否有cache分区,因为log等重要信息都存在cache分区里has_cache = volume_for_path(CACHE_ROOT) != nullptr;..........
}

详细看下是如何加载分区表的

[roots.cpp]
void load_volume_table()
{int i;int ret;fstab = fs_mgr_read_fstab_default();if (!fstab) {LOG(ERROR) << "failed to read default fstab";return;}//将对应的信息加入到一条链表中ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk");if (ret < 0 ) {LOG(ERROR) << "failed to add /tmp entry to fstab";fs_mgr_free_fstab(fstab);fstab = NULL;return;}printf("recovery filesystem table\n");printf("=========================\n");//在last_log中打印分区表信息//打印的顺序是://编号  |  挂载节点  |  文件系统类型  |  块设备  |  长度for (i = 0; i < fstab->num_entries; ++i) {Volume* v = &fstab->recs[i];printf("  %d %s %s %s %lld\n", i, v->mount_point, v->fs_type,v->blk_device, v->length);}printf("\n");
}

跟踪fs_mgr_read_fstab_default

[fs_mgr_fstab.cpp]
struct fstab *fs_mgr_read_fstab_default()
{std::string hw;std::string default_fstab;//下面应该是去其它位置查询fstab文件,由于/sbin/recovery是有的,这里default_fstab是"/etc/recovery.fstab"//A/B和non A/B下fstab文件是不同的if (access("/sbin/recovery", F_OK) == 0) {default_fstab = "/etc/recovery.fstab";} else if (fs_mgr_get_boot_config("hardware", &hw)) {  // normal bootfor (const char *prefix : {"/odm/etc/fstab.","/vendor/etc/fstab.", "/fstab."}) {default_fstab = prefix + hw;if (access(default_fstab.c_str(), F_OK) == 0) break;}} else {LWARNING << __FUNCTION__ << "(): failed to find device hardware name";}//fs_mgr_read_fstab_dt函数是读取/proc/device-tree/firmware/android/fstab文件//fs_mgr_read_fstab是解析/etc/recovery.fstab文件//in_place_merge将上面读取的结果合并struct fstab *fstab_dt = fs_mgr_read_fstab_dt();struct fstab *fstab = fs_mgr_read_fstab(default_fstab.c_str());return in_place_merge(fstab_dt, fstab);
}

2.解析参数

[recovery.cpp]
int main(int argc, char **argv) {........//通过get_args方法获取传入的参数,顺便解析到args中,后面会在log中打出参数//get_args方法获取misc分区的command文件std::vector<std::string> args = get_args(argc, argv);std::vector<char*> args_to_parse(args.size());std::transform(args.cbegin(), args.cend(), args_to_parse.begin(),[](const std::string& arg) { return const_cast<char*>(arg.c_str()); });//调用getopt_long解析参数,这是固定用法,可以网上搜索资料,大致意思就是根据OPTIONS,将参数转换成对应的字符//例如:传入的参数是"Command: "/sbin/recovery" "--update_package=/sdcard/update.zip" "--launch_app=update_launcher" "--requester=com.asus.UpdateLauncher" "--locale=zh_CN_#Hans""//其中包含update_package,那么getopt_long依次返回的结果就是"u" "l"while ((arg = getopt_long(args_to_parse.size(), args_to_parse.data(), "", OPTIONS,&option_index)) != -1) {}//加载客制化的东西,比如语言之类的// load_locale_from_cache不展开说了,大致过程就是从之前解析分区表得到的fstab中查询/cache/recovery/last_locale文件是否存在,如果存在就读取里面的值if (locale.empty()) {if (has_cache) {locale = load_locale_from_cache();}if (locale.empty()) {locale = DEFAULT_LOCALE;}}........
}

获取参数比较重要,仔细看看:

//提前了解下bootloader_message结构体
struct bootloader_message {char command[32];char status[32];char recovery[768];char stage[32];char reserved[1184];
};static std::vector<std::string> get_args(const int argc, char** const argv) {CHECK_GT(argc, 0);bootloader_message boot = {};std::string err;//通过函数从misc分区中去读BCB数据库到boot变量中if (!read_bootloader_message(&boot, &err)) {LOG(ERROR) << err;// If fails, leave a zeroed bootloader_message.boot = {};}stage = std::string(boot.stage);if (boot.command[0] != 0) {std::string boot_command = std::string(boot.command, sizeof(boot.command));LOG(INFO) << "Boot command: " << boot_command;}if (boot.status[0] != 0) {std::string boot_status = std::string(boot.status, sizeof(boot.status));LOG(INFO) << "Boot status: " << boot_status;}std::vector<std::string> args(argv, argv + argc);//如果传入参数为空,先看misc分区中的 bootloader_message是否有内容if (args.size() == 1) {boot.recovery[sizeof(boot.recovery) - 1] = '\0';  // Ensure terminationstd::string boot_recovery(boot.recovery);std::vector<std::string> tokens = android::base::Split(boot_recovery, "\n");if (!tokens.empty() && tokens[0] == "recovery") {for (auto it = tokens.begin() + 1; it != tokens.end(); it++) {// Skip empty and '\0'-filled tokens.if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it));}LOG(INFO) << "Got " << args.size() << " arguments from boot message";} else if (boot.recovery[0] != 0) {LOG(ERROR) << "Bad boot message: \"" << boot_recovery << "\"";}}//如果参数为空,并且有cache分区,那么解析"/cache/recovery/command"中内容if (args.size() == 1 && has_cache) {std::string content;if (ensure_path_mounted(COMMAND_FILE) == 0 &&android::base::ReadFileToString(COMMAND_FILE, &content)) {std::vector<std::string> tokens = android::base::Split(content, "\n");// All the arguments in COMMAND_FILE are needed (unlike the BCB message,// COMMAND_FILE doesn't use filename as the first argument).for (auto it = tokens.begin(); it != tokens.end(); it++) {// Skip empty and '\0'-filled tokens.if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it));}LOG(INFO) << "Got " << args.size() << " arguments from " << COMMAND_FILE;}}//将得到的参数写入到misc分区中std::vector<std::string> options(args.cbegin() + 1, args.cend());if (!update_bootloader_message(options, &err)) {LOG(ERROR) << "Failed to set BCB message: " << err;}return args;
}

3.recovery界面设置

[recovery.cpp]
int main(int argc, char **argv) {........//设置更新的类型,如果参数中没有--security,那么在安装界面会显示”正在安装系统更新”,否则显示“正在安装安全更新”//这里会调用SetSystemUpdateText 方法把显示哪种文字的选择存在installing_text中,后面解析具体命令的时候会调用GetCurrentText来显示ui->SetSystemUpdateText(security_update);//显示recovery的背景ui->SetBackground(RecoveryUI::NONE);//参数中没有show_text,此处为false//ShowText的意思是显示菜单,Android默认不显示的if (show_text) ui->ShowText(true);//下面设置selinux权限//相关文件:plat_file_contexts,nonplat_file_contexts,存放在recovery模式根目录下sehandle = selinux_android_file_context_handle();selinux_android_set_sehandle(sehandle);if (!sehandle) {ui->Print("Warning: No file_contexts\n");}//啥都没做device->StartRecovery();//把手机中的属性值打出来property_list(print_property, NULL);........
}

4.执行命令

[recovery.cpp]
int main(int argc, char **argv) {........//下面就是具体升级的地方,重点!int status = INSTALL_SUCCESS;if (update_package != NULL) {modified_flash = true;//首先判断电量是否OK,这里用的是系统提供的API
//is_battery_ok()方法的判断逻辑是:
1.手机非充电模式下,电量大于20%
2.手机充电模式下,注意,这里充电必须使用AC电源充电,USB连接电脑是不行的,这种情况下电量大于15%
在Android.mk中有”LOCAL_HAL_STATIC_LIBRARIES := libhealthd”这就是应用的库
recovery.cpp中包含”#include <healthd/BatteryMonitor.h>”if (!is_battery_ok()) {ui->Print("battery capacity is not enough for installing package, needed is %d%%\n",BATTERY_OK_PERCENTAGE);// Log the error code to last_install when installation skips due to// low battery.log_failure_code(kLowBattery, update_package);status = INSTALL_SKIPPED;} else if (bootreason_in_blacklist()) { //这里是判断重启的原因,看看是否是非法的// Skip update-on-reboot when bootreason is kernel_panic or similarui->Print("bootreason is in the blacklist; skip OTA installation\n");log_failure_code(kBootreasonInBlacklist, update_package);status = INSTALL_SKIPPED;} else {//真正执行升级的地方status = install_package(update_package, &should_wipe_cache,TEMPORARY_INSTALL_FILE, true, retry_count);//如果升级成功,并且主动要求清除cache,这里是通过升级脚本来判断的if (status == INSTALL_SUCCESS && should_wipe_cache) {wipe_cache(false, device);}if (status != INSTALL_SUCCESS) {ui->Print("Installation aborted.\n");//如果是IO出现错误,那么重新来一次if (status == INSTALL_RETRY && retry_count < EIO_RETRY_COUNT) {copy_logs();//调用set_retry_bootloader_message向bootloader_message写命令set_retry_bootloader_message(retry_count, args);// Print retry count on screen.ui->Print("Retry attempt %d\n", retry_count);// Reboot and retry the updateif (!reboot("reboot,recovery")) {ui->Print("Reboot failed\n");} else {while (true) {pause();}}}// If this is an eng or userdebug build, then automatically// turn the text display on if the script fails so the error// message is visible.if (is_ro_debuggable()) {ui->ShowText(true);}}}} else if//下面是各种命令,比如清除cache,恢复工厂等,不细看……}else if (!just_exit) {    //如果传入的参数中没有指定,那么进recovery就会走到这status = INSTALL_NONE;  // No command specified//在recovery背景显示”No commnad”这是根据图片no_command_text.png来显示的ui->SetBackground(RecoveryUI::NO_COMMAND);//Android默认user版本不显示选项,只显示机器人倒地和”No command”如果想要进入recovery模式,按power键1~2s,再短按音量上键即可//通常要把判断移除,直接显示选项if (is_ro_debuggable()) {ui->ShowText(true);}}........
}

5.如果没有指定命令,等待用户输入

[recovery.cpp]
int main(int argc, char **argv) {........//如果升级错误,显示”Error!”,通常测试就会提供错误的图片if (!sideload_auto_reboot && (status == INSTALL_ERROR || status == INSTALL_CORRUPT)) {//将log保存下来copy_logs();ui->SetBackground(RecoveryUI::ERROR);}//如果传入的参数中没有指定,那么稍后会自动重启,如果有那么关机Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT;//当然,这只针对user版本,因为userdebug和eng版本在上面执行了ShowText,所以下面IsTextVisible得到的结果是true,就不会重启或关机if ((status != INSTALL_SUCCESS && status != INSTALL_SKIPPED && !sideload_auto_reboot) ||ui->IsTextVisible()) {//prompt_and_wait()函数是个死循环 开始显示recovery选项 并处理用户通过按键或者触摸屏的选项,如重启,升级等Device::BuiltinAction temp = prompt_and_wait(device, status);if (temp != Device::NO_ACTION) {after = temp;}}........
}

看下log如何保存的:

[recovery.cpp]
static void copy_logs() {//如果啥都没有改,不需要存logif (!modified_flash) {return;}// Always write to pmsg, this allows the OTA logs to be caught in logcat -L//这里说是将log写入到pmsg中,下次通过logcat -L就可以查看log,为啥实验不行呢,提示”logcat read failure”copy_log_file_to_pmsg(TEMPORARY_LOG_FILE, LAST_LOG_FILE);copy_log_file_to_pmsg(TEMPORARY_INSTALL_FILE, LAST_INSTALL_FILE);// We can do nothing for now if there's no /cache partition.if (!has_cache) {return;}//确保要存入的log路径是挂载的ensure_path_mounted(LAST_LOG_FILE);ensure_path_mounted(LAST_KMSG_FILE);//更改log的文件名,调整顺序//如果有多份log,last_log是最新的,last_log.1第二,last_log.2第三,依次往后,last_kmsg同样rotate_logs(LAST_LOG_FILE, LAST_KMSG_FILE);//拷贝log文件到cache/recovery/目录copy_log_file(TEMPORARY_LOG_FILE, LOG_FILE, true);copy_log_file(TEMPORARY_LOG_FILE, LAST_LOG_FILE, false);copy_log_file(TEMPORARY_INSTALL_FILE, LAST_INSTALL_FILE, false);//保存kernel log, 这里面能看到selinux权限的问题,挺有用的save_kernel_log(LAST_KMSG_FILE);//下面是修改文件权限chmod(LOG_FILE, 0600);chown(LOG_FILE, AID_SYSTEM, AID_SYSTEM);chmod(LAST_KMSG_FILE, 0600);chown(LAST_KMSG_FILE, AID_SYSTEM, AID_SYSTEM);chmod(LAST_LOG_FILE, 0640);chmod(LAST_INSTALL_FILE, 0644);sync();
}

6.结束recovery

[recovery.cpp]
int main(int argc, char **argv) {........// Save logs and clean up before rebooting or shutting down.finish_recovery();//下面就是检测上面的after变量,没啥switch (after) {case Device::SHUTDOWN:ui->Print("Shutting down...\n");android::base::SetProperty(ANDROID_RB_PROPERTY, "shutdown,");break;case Device::REBOOT_BOOTLOADER:ui->Print("Rebooting to bootloader...\n");android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,bootloader");break;default:ui->Print("Rebooting...\n");reboot("reboot,");break;}while (true) {pause();}// Should be unreachable.return EXIT_SUCCESS;
}

跟踪finish_recovery():

[recovery.cpp]
static void finish_recovery() {//保存locale log到last_locale中,不知道这有什么用if (!locale.empty() && has_cache) {LOG(INFO) << "Saving locale \"" << locale << "\"";FILE* fp = fopen_path(LOCALE_FILE, "w");if (!android::base::WriteStringToFd(locale, fileno(fp))) {PLOG(ERROR) << "Failed to save locale to " << LOCALE_FILE;}check_and_fclose(fp, LOCALE_FILE);}//拷贝log,上面已经介绍过copy_logs();//因为是正常退出recovery,所以需要把BSB清除掉,否则会循环进入recoverystd::string err;if (!clear_bootloader_message(&err)) {LOG(ERROR) << "Failed to clear BCB message: " << err;}// 把"/cache/recovery/command"清除掉,不然也会导致重新进入recoveryif (has_cache) {if (ensure_path_mounted(COMMAND_FILE) != 0 || (unlink(COMMAND_FILE) && errno != ENOENT)) {LOG(WARNING) << "Can't unlink " << COMMAND_FILE;}ensure_path_unmounted(CACHE_ROOT);}sync();  // For good measure.
}

二、recovery总结

1.启动recovery模式的三种方式

  1. adb reboot :
    这是通过调用/sbin/recovery来启动recovery
  2. 上层系统调用升级接口,恢复出厂等:
    这是通过写命令到”/cache/recovery/command”
    从log中可以看到”I:Got arguments from /cache/recovery/command”
    疑问:打出的log应该有命令的大小才对,可是log中并没有
  3. 通过组合键(电源键+音量下键):
    调用/sbin/recovery来启动recovery

2.知识点:

  1. 如果recovery有问题启动不了,那么会卡在开机log的第二帧上
  2. recovery模式下,在sbin/目录下有recovery,和adbd等文件
    这个recovery就是recovery.cpp编译生成的可执行文件(具体可以看recovery下的Android.mk)
    我们可以试着执行这个bin文件,并且带上参数
    执行后发现会重新进入recovery,在tmp/recovery.log中会显示我们的参数
    recovery会被recovery.img执行,这就是为什么我们会从main方法开始分析
    平时使用的adb就是PC端adb通过socket连接到sbin/adbd 执行的命令都是这个adbd执行的

Android 8.0 recovery 流程分析相关推荐

  1. Android8.0(34)----Android 8.0 Settings流程分析与变动

    Android 8.0 Settings流程分析与变动 一,相比Android Settings 7.0 如下图,在7.0的基础上,去掉了7.0新加的侧滑菜单(可能是觉得有点鸡肋吧).多加了一级页面, ...

  2. AOSP Android 8.0 冷启动流程分析(二)

    前奏: Android系统虽然基于Linux系统的,但是由于Android属于嵌入式设备,并没有像PC那样的BISO程序,取而代之的是Bootloader----系统启动加载器. /boot : 存放 ...

  3. Android 7.0 Keyguard流程分析

    在android 6.0 上Keyguard作为了SystemUI的一个库文件被引用,所以编译的时候不会出现Keyguard.apk这个文件,Keyguard也伴随着SystemUI的启动而启动,其中 ...

  4. Android 7.0系统启动流程分析

    随着Android版本的升级,aosp项目中的代码也有了些变化,本文基于Android 7.0分析Android系统启动流程.当我们按下电源键后,整个Android设备大体经过了一下过程:  今天我们 ...

  5. Android 9.0 Vold 流程分析(-)

    Vold 介绍 Vold(volume Daemon),即Volume守护进程,用来管理Android中存储类的热拔插事件,处于Kernel和Framework之间,是两个层级连接的桥梁.vold 代 ...

  6. Android 8.0 学习(23)---recovery 流程分析

    Android 8.0 recovery 流程分析 这里主要分析non A/B模式下的recovery流程  A/B模式下的recovery在boot中  后续会不断补充,如果有疏漏或者错误的地方,请 ...

  7. c++builder启动了怎么停止_App 竟然是这样跑起来的 —— Android App/Activity 启动流程分析...

    在我的上一篇文章: AJie:按下电源键后竟然发生了这一幕 -- Android 系统启动流程分析​zhuanlan.zhihu.com 我们分析了系统在开机以后的一系列行为,其中最后一阶段 AMS( ...

  8. android 屏幕旋转流程,android自动屏幕旋转流程分析.doc

    android自动屏幕旋转流程分析.doc android自动屏幕旋转流程分析 在android设置(Settings)中我们可以看到显示(display)下有一个自动屏幕旋转的checkbox, 如 ...

  9. Android 手机灭屏流程分析详解

    参考地址:https://www.jianshu.com/p/9241f3a91095 本篇文章主要介绍 Android 开发中的部分知识点,通过阅读本篇文章,您将收获以下内容: 1.前言 2.Pow ...

最新文章

  1. activiti任务TASK
  2. python代码大全p-【python】10分钟教你用python一行代码搞点大新闻
  3. java中调用api的方式(postJsonHTTP)
  4. 如何开发一个异常检测系统:异常检测 vs 监督学习
  5. extjs5(05--主界面上加入顶部和底部区域)
  6. vscode中vue代码高亮_Vue中添加友盟代码统计
  7. 两大思维,就可以让你轻松完成任意一个目标
  8. 入门教程:.NET开源OpenID Connect 和OAuth解决方案IdentityServer v3 介绍 (一)
  9. go 获取屏幕分辨率_CS:GO枪神的自我修养 高刷电竞显示器推荐
  10. Shut Down(or Closing) Your Windows and Open The Unix
  11. c语言编写一个用户登录界面,怎么用C语言编写个登陆界面?
  12. 生物化学,材料化学必备!元素周期表
  13. Excel时间段计算的相关公式
  14. java定义一个short_JDK源码解读第七章:java.lang.Short
  15. 怎么理解“不经审视的人生,不值得过!“
  16. Windows端口 说明
  17. Linux学习~树莓派gpio控制
  18. Lua IDE - x-studio 强大的IDE
  19. 有搜python题目的软件吗_Python开发及应用-中国大学mooc-试题题目及答案
  20. RK3326 android10.0(Q) 系统精简瘦身

热门文章

  1. 智源AI日报(2022-08-30): 华为谢凌曦:关于视觉识别领域发展的个人观点
  2. activemq管理页面
  3. Impala和Presto的时间处理
  4. java web编程技术解题与实验指导_javaweb编程技术实验指导书
  5. excel 按照范围替换
  6. apiCloud app调用浏览器打开网页的方法
  7. 荣耀MagicBook Pro性能测试,“秀”出硬实力
  8. 基于区块链的知识共享框架-Aletheia
  9. 计算某天是某年的第多少天
  10. nodejs服务器与服务器之间通讯问题(nodejs服务器端创建客户端)