Android Recovery 源码解析和界面定制

Recovery主要功能
源码路径和主要原文件
recoverycpp
命令行参数
main 函数
界面定制
实现Recovery UI
实现头部显示和列表项
实现ScreenRecoveryUI
实现设备类
添加编译实现
Android Recovery 源码解析和界面定制
Recovery主要功能
深入了解recovery源码前,先浏览下recovery能够给我们提供哪些功能;

首先是我们熟悉的恢复工厂设置 (清除数据,清楚缓存)–> wipe_data wipe_cache
刷升级包,可以通过sdcard升级,通常说的卡刷,有些还提供ADB sideload升级;
可以进行系统的系统的OTA升级,本质上同手动刷包一样;
源码路径和主要原文件
在Android源码环境中,recovery的源码主要在bootable/recovery文件下,另外再device目录下,会根据各个设备定制自己的接口以及UI界面,也就是文章后半部分分析的界面定制的内容;

在bootable/recovery目录下,主要的源文件有:

LOCAL_SRC_FILES :=
adb_install.cpp
asn1_decoder.cpp
bootloader.cpp
device.cpp
fuse_sdcard_provider.c
install.cpp
recovery.cpp
roots.cpp
screen_ui.cpp
ui.cpp
verifier.cpp \

该部分代码在编译后,会统一输出到 out/recovery/root/目录;

recovery.cpp
命令行参数
recovery最后是编译成一个可执行的命令,放在recovery文件系统中的/sbin/recovery;所以我们可以在终端中直接运行该命令,具体的参数如下:

–send_intent=anystring - 传递给recovery的信息
–adbd -adb sideload升级
–update_package=path - 指定OTA升级包
–wipe_data - 清楚用户数据并重启
–wipe_cache - 清楚缓存并重启
–set_encrypted_filesystem=on|off - 使能或者关闭文件系统加密
–just_exit - 退出并重启

recovery.cpp的main 函数
从main入口函数分析recovery的主要源码:

输出重定向

redirect_stdio(TEMPORARY_LOG_FILE);
//redirect log to serial output
#ifdef LogToSerial
freopen("/dev/ttyFIQ0", "a", stdout); setbuf(stdout, NULL);
freopen("/dev/ttyFIQ0", "a", stderr); setbuf(stderr, NULL);
#endif

这部分代码很容易理解,主要作用是输出log到/tem/recovery.log文件中

执行adb sideload分支

if (argc == 2 && strcmp(argv[1], "--adbd") == 0) {adb_main(0, DEFAULT_ADB_PORT);return 0;
}

判断命令行参数是否为–adbd,并执行adb_main函数,这部分代码在后续adb_install.cpp中分析;

填充fstab结构体

在main函数中调用 load_volume_table(),读取/etc/recovery.emmc.fstab文件内容,并填充fstab结构体,但是并没有执行挂载操作:
load_volume_table函数在roots.cpp文件中,也是很容易理解:

void load_volume_table()
{
...
int emmcState = getEmmcState();//判断是否为emmc设备
if(emmcState) {fstab = fs_mgr_read_fstab("/etc/recovery.emmc.fstab");
}else {fstab = fs_mgr_read_fstab("/etc/recovery.fstab");
}
...
//读取文件中每个条目内容,填充fstab结构体
ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk");
...
//日志打印fstable信息
printf("recovery filesystem table\n");
printf("=========================\n");
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");
}

读取控制参数
recovery 和 bootloader 必须通过内存的一个特定分区,才能进行相互的通信,这个分区一般是/misc;
对应的信息数据结构体为bootloader_message;
参照源码中bootloader_message 的注释

struct bootloader_message {
char command[32];//bootloader 启动时读取改数据,决定是否进入recovery模式
char status[32];//由bootloader进行更新,标识升级的结果;
char recovery[768];//由Android系统进行写入,recovery从中读取信息;
char stage[32];
char reserved[224];
};

recovery 根据命令行参数,再从/misc分区中解析出对应的参数,进行后续的操作,具体的调用函数为get_args(&argc, &argv);

static void
get_args(int *argc, char ***argv) {
struct bootloader_message boot;//参数结构体
memset(&boot, 0, sizeof(boot));
get_bootloader_message(&boot);  // 具体的读取信息的函数,可能为空的情况
stage = strndup(boot.stage, sizeof(boot.stage));
...// 如果上述情况为空,则从/cache/recovery/command获取参数,其中 COMMAND_FILE=/cache/recovery/command
if (*argc <= 1) {FILE *fp = fopen_path(COMMAND_FILE, "r");if (fp != NULL) {char *token;char *argv0 = (*argv)[0];*argv = (char **) malloc(sizeof(char *) * MAX_ARGS);(*argv)[0] = argv0;  // use the same program namechar buf[MAX_ARG_LENGTH];for (*argc = 1; *argc < MAX_ARGS; ++*argc) {if (!fgets(buf, sizeof(buf), fp)) break;token = strtok(buf, "\r\n");if (token != NULL) {(*argv)[*argc] = strdup(token);  // Strip newline.} else {--*argc;}}check_and_fclose(fp, COMMAND_FILE);LOGI("Got arguments from %s\n", COMMAND_FILE);}
}//把从/cache/recovery/command获取参数重新写回到/misc分区
// --> write the arguments we have back into the bootloader control block
// always boot into recovery after this (until finish_recovery() is called)
strlcpy(boot.command, "boot-recovery", sizeof(boot.command));
strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery));
int i;
for (i = 1; i < *argc; ++i) {strlcat(boot.recovery, (*argv)[i], sizeof(boot.recovery));strlcat(boot.recovery, "\n", sizeof(boot.recovery));
}
set_bootloader_message(&boot);
}

解析命令行参数

while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {switch (arg) {case 'f': factory_mode = optarg; bFactoryMode = true; break;case 'i': send_intent = optarg; break;case 'u': update_package = optarg; break;case 'w': should_wipe_data = true; break;case 'k':  update_rkimage = optarg;break;case 'c': should_wipe_cache = true; break;case 't': show_text = true; break;case 's': sideload = true; break;case 'a': sideload = true; sideload_auto_reboot = true; break;case 'x': just_exit = true; break;case 'l': locale = optarg; break;case 'g': {if (stage == NULL || *stage == '\0') {char buffer[20] = "1/";strncat(buffer, optarg, sizeof(buffer)-3);stage = strdup(buffer);}break;}case 'f'+'w': //fw_updateif((optarg)&&(!sdboot_update_package)){sdboot_update_package = strdup(optarg);}break;case 'd': //demo_copyif((optarg)&&(! demo_copy_path)){demo_copy_path = strdup(optarg);}break;case 'p': shutdown_after = true; break;case 'r': reason = optarg; break;case 'w'+'a': { should_wipe_all = should_wipe_data = should_wipe_cache = true;show_text = true;} break;case '?':LOGE("Invalid command argument\n");continue;}
}

这部分代码很简单,就是通过getopt_long进行命令行参数的解析并赋值;

显示界面和功能选项

接下来就是创建device,显示对应UI界面和功能选项;

Device* device = make_device();//可以自己实现一个设备
ui = device->GetUI();
gCurrentUI = ui;//赋值ui界面ui->SetLocale(locale);//获取归属地信息
ui->Init();//初始化,可以重载,在init中实现相应功能
ui->SetStage(st_cur, st_max);
ui->SetBackground(RecoveryUI::NONE);

进行分区挂载操作

ensure_path_mountedint ensure_path_mounted(const char* path) {
...
Volume* v = volume_for_path(path);//根据路径名获取分区信息
...
int result;
result = scan_mounted_volumes();const MountedVolume* mv =find_mounted_volume_by_mount_point(v->mount_point);//根据挂载点,获取已挂载分区的信息,如果不为空,说明已经成功挂载
if (mv) {// volume is already mountedreturn 0;
}result = mkdir(v->mount_point, 0755);  // 创建对应目录,确保目录存在,也有可能目录已经存在
if (result!=0)
{printf("failed to create %s dir,err=%s!\n",v->mount_point,strerror(errno));
}// 根据文件系统类型,执行mount操作
if (strcmp(v->fs_type, "yaffs2") == 0) {// mount an MTD partition as a YAFFS2 filesystem.mtd_scan_partitions();const MtdPartition* partition;partition = mtd_find_partition_by_name(v->blk_device);if (partition == NULL) {LOGE("failed to find \"%s\" partition to mount at \"%s\"\n",v->blk_device, v->mount_point);return -1;}return mtd_mount_partition(partition, v->mount_point, v->fs_type, 0);
} else if (strcmp(v->fs_type, "ext4") == 0 ||strcmp(v->fs_type, "ext3") == 0) {result = mount(v->blk_device, v->mount_point, v->fs_type,MS_NOATIME | MS_NODEV | MS_NODIRATIME, "");if (result == 0) return 0;LOGE("failed to mount %s %s (%s)\n", v->mount_point, v->blk_device, strerror(errno));return -1;
} else if (strcmp(v->fs_type, "vfat") == 0) {result = mount(v->blk_device, v->mount_point, v->fs_type,MS_NOATIME | MS_NODEV | MS_NODIRATIME, "shortname=mixed,utf8");if (result == 0) return 0;LOGW("trying mount %s to ntfs\n", v->blk_device);result = mount(v->blk_device, v->mount_point, "ntfs",MS_NOATIME | MS_NODEV | MS_NODIRATIME, "");if (result == 0) return 0;char *sec_dev = v->fs_options;if(sec_dev != NULL) {char *temp = strchr(sec_dev, ',');if(temp) {temp[0] = '\0';}result = mount(sec_dev, v->mount_point, v->fs_type,MS_NOATIME | MS_NODEV | MS_NODIRATIME, "shortname=mixed,utf8");if (result == 0) return 0;LOGW("trying mount %s to ntfs\n", sec_dev);result = mount(sec_dev, v->mount_point, "ntfs",MS_NOATIME | MS_NODEV | MS_NODIRATIME, "");if (result == 0) return 0;}LOGE("failed to mount %s (%s)\n", v->mount_point, strerror(errno));return -1;
}else if (strcmp(v->fs_type, "ntfs") == 0) {LOGW("trying mount %s to ntfs\n", v->blk_device);result = mount(v->blk_device, v->mount_point, "ntfs",MS_NOATIME | MS_NODEV | MS_NODIRATIME, "");if (result == 0) return 0;LOGE("failed to mount %s (%s)\n", v->mount_point, strerror(errno));return -1;
}LOGE("unknown fs_type \"%s\" for %s\n", v->fs_type, v->mount_point);
return -1;
}

界面定制
实现Recovery UI
在自己的设备目录下:device/vendor/recovery/recovery_ui.cpp

#include <linux/input.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>#include "common.h"
#include "device.h"
#include "screen_ui.h"

实现头部显示和列表项

const char* HEADERS[] = { "Volume up/down to move highlight;","power button to select.","",NULL };const char* ITEMS[] ={ "reboot system now",//"apply update from ADB","apply update from external storage","update rkimage from external storage","apply update from cache","wipe data/factory reset","wipe cache partition","recovery system from backup",NULL };

实现ScreenRecoveryUI

class DeviceUI : public ScreenRecoveryUI {public:DeviceUI () :consecutive_power_keys(0) {
}//实现自己的识别key类型的功能,可以为不同的输入设备适配recovery功能
virtual KeyAction CheckKey(int key) {if (IsKeyPressed(KEY_POWER) && key == KEY_VOLUMEUP) {return TOGGLE;}if (key == KEY_POWER) {++consecutive_power_keys;if (consecutive_power_keys >= 7) {return REBOOT;}} else {consecutive_power_keys = 0;}return ENQUEUE;
}private:
int consecutive_power_keys;
};

实现设备类

class MyDevice : public Device {public:
RkDevice() :ui(new DeviceUI ) {
}RecoveryUI* GetUI() { return ui; }int HandleMenuKey(int key_code, int visible) {if (visible) {switch (key_code) {case KEY_DOWN:case KEY_VOLUMEDOWN:return kHighlightDown;case KEY_UP:case KEY_VOLUMEUP:return kHighlightUp;case KEY_ENTER:case KEY_POWER:return kInvokeItem;}}return kNoAction;
}BuiltinAction InvokeMenuItem(int menu_position) {switch (menu_position) {case 0: return REBOOT;//case 1: return APPLY_ADB_SIDELOAD;case 1: return APPLY_EXT;case 2: return APPLY_INT_RKIMG;case 3: return APPLY_CACHE;case 4: return WIPE_DATA;case 5: return WIPE_CACHE;case 6: return RECOVER_SYSTEM;default: return NO_ACTION;}
}const char* const* GetMenuHeaders() { return HEADERS; }
const char* const* GetMenuItems() { return ITEMS; }private:
RecoveryUI* ui;
};//创建自己实现的设备
Device* make_device() {
return new MyDevice ;
}

安卓recovery流程分析【第二篇】相关推荐

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

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

  2. SAP PP COR2下达工单系统报错说-系统状态APNG是激活的- 分析第二篇

    SAP PP COR2下达工单系统报错说-系统状态APNG是激活的- 分析第二篇 笔者所在的项目上启用了ECM(Engineer Change Management)功能,重要数据的修改都要事先创建一 ...

  3. internetreadfile读取数据长度为0_Go发起HTTP2.0请求流程分析(后篇)——标头压缩

    阅读建议 这是HTTP2.0系列的最后一篇,笔者推荐阅读顺序如下: Go中的HTTP请求之--HTTP1.1请求流程分析 Go发起HTTP2.0请求流程分析(前篇) Go发起HTTP2.0请求流程分析 ...

  4. Android 8.0 recovery 流程分析

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

  5. android dex加载过程,8.1版本dex加载流程笔记--第二篇:DexFile::Open流程与简单脱壳原理...

    在看雪发了,52再发一下,共同学习 菜鸟刚刚学完了dex_file.cc这个源码,大致搞明白了大佬们hook脱整体加固的原理了,原理在帖子最后 学习了大佬angelToms的帖子https://bbs ...

  6. Android recovery 流程分析

    Recovery简介 Android利用Recovery模式,进行恢复出厂设置,OTA升级,patch升级及firmware升级. 升级一般通过运行升级包中的META-INF/com/google/a ...

  7. Realtek 8125驱动分析第二篇——触发硬件中断

    书接上文,本文讲述瑞昱2.5G网卡rtl8125的触发硬件中断相关内容. 目录 1 中断处理函数(ISR)注册 2 触发硬件中断 可以通过下面的命令来看interrupts. howard@B150: ...

  8. nutch代码分析第二篇——crawl.crawl

    2021SC@SDUSC 顾名思义,org.apache.nutch.crawl.Crawl实现的是一个完整的抓取过程,包括各种方法的初始化,url集的建立 /* Perform complete c ...

  9. 高通Android智能平台环境搭建_编译流程分析

    高通Android智能平台环境搭建_编译流程分析 高通平台环境搭建,编译,系统引导流程分析 TOC \o \h \z \u 1. 高通平台android开发总结. 7 1.1 搭建高通平台环境开发环境 ...

  10. 高通平台环境搭建,编译,系统引导流程分析 .

    1.高通平台android开发总结 1.1 搭建高通平台环境开发环境 在高通开发板上烧录文件系统 建立高通平台开发环境 高通平台,android和 modem 编译流程分析 高通平台 7620 启动流 ...

最新文章

  1. kotlin Bean加载失败lateinit property has not been initialized
  2. 烧水壶起水沟了怎么办?
  3. C语言删掉无关变量无输出,C语言变量类型与输出控制用法实例教程
  4. SpringMVC日期类型转换问题处理方法归纳
  5. java面试手写单链表_(转)面试大总结之一:Java搞定面试中的链表题目
  6. 工业机器人控制问题---来自睿慕课
  7. 读取excel内容在网页上显示出来
  8. 红包不是你想送就能送 摩拜物联网技术成行业壁垒
  9. nginx报错The program 'nginx' can be found in the following packages
  10. 第05课 Linux命令初探(一)
  11. Stm32 基于蓝牙的串口通信 详细篇
  12. Visio连接线设置箭头形状失效
  13. 耶鲁大学 博弈论(Game Theory) 笔记1
  14. 【论文阅读笔记】High Quality Monocular Depth Estimation via Transfer Learning
  15. 【转】26张PPT让你告别拖延症
  16. linux系统重启网卡命令
  17. ShareSDK 新浪微博平台注册指南
  18. 【附源码】计算机毕业设计SSM天润律师事务所管理系统
  19. HyperMate Pro硬件钱包全体验
  20. 三星台式计算机参数,评测三星平板电脑Galaxy Tab S7 2020款怎么样?三星Galaxy Tab S7 2020款参数配置如何?...

热门文章

  1. Oracle ADF开发实战指南pdf
  2. Linux下svn的部署
  3. WMI 脚本入门:第二部分 (MSDN)
  4. lambda表达式和切片
  5. 干货 | 找工作的经验总结(一)
  6. keras_contrib安装
  7. 【每日算法Day 82】面试经典题:求第K大数,我写了11种实现,不来看看吗?
  8. poj3276(Face The Right Way)反转(开关问题)
  9. 数据预处理—7.数据插补之拉格朗日插值法、牛顿差值法及python实现
  10. pandas—pandas.read_parquet