Android Recovery 源码解析和界面定制
来源:互联网 发布:数学建模最优化论文 编辑:程序博客网 时间:2024/05/19 10:35
- 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 - 退出并重启
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 voidget_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 name char 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_update if((optarg)&&(!sdboot_update_package)){ sdboot_update_package = strdup(optarg); } break; case 'd': //demo_copy if((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_mounted
int 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 mounted return 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 ;}
添加编译实现
主要是覆盖TARGET_RECOVERY_UI_LIB,输出到/out/…./recovery/root目录下:
Android.mk
LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE_TAGS := engLOCAL_C_INCLUDES += bootable/recoveryLOCAL_SRC_FILES := recovery_ui.cpp# should match TARGET_RECOVERY_UI_LIB set in BoardConfig.mkLOCAL_MODULE := librecovery_ui_$(TARGET_PRODUCT)include $(BUILD_STATIC_LIBRARY)
- Android Recovery 源码解析和界面定制
- android源码定制之android关机界面
- android源码定制之初探--定制android关机界面
- [置顶]android源码定制之初探--定制android关机界面
- android源码定制之初探--定制android关机界面
- android源码定制之初探--定制android关机界面
- android源码定制之初探--定制android关机界面
- Android界面-标题和按钮定制-drawable
- Android界面-标题和按钮定制-drawable
- Android Recovery OTA升级(二)—— Recovery源码解析
- Android Recovery 代码解析
- Android Recovery模式解析
- Android recovery流程解析
- android recovery.img定制之recovery.fstab分区表配置
- android recovery.img定制之recovery.fstab分区表配置
- 定制android启动界面
- 定制android启动界面
- 定制android启动界面
- iOS中nil/Nil/NULL/NSNull的区别
- 关于javaScript和jQuery获取对象的想法
- PHP程序员的思考
- 如何快速赚钱:Python爬虫
- PostgreSql数据库的索引
- Android Recovery 源码解析和界面定制
- Activity的知识点概要
- CoordinatorLayout,AppBarLayout,CollapsingToolbarLayout,Toolbar,NestedScrollView,RecyclerView结合使用
- Android错误集锦
- iOS中frame和Bounds之间的区别
- JavaScript强化教程 —— JS实现一个基本的打地鼠游戏
- Oozie基础小结
- Vue目录结构
- Node.js npm colors