Android系统升级 Recovery模式(02)Recovery升级过程
该系列文章总纲链接:专题分纲目录 Android系统升级 Recovery模式
本章关键点总结 & 说明:

导图是不断迭代的,这里主要关注➕ recovery升级过程部分即可,主要从 一般升级和sd卡升级角度分析了如何调用到关键方法installPackage,最后从升级入口installPackage解读如何其原理。
recovery升级方式有很多种,多数方式只是上传更新包的方式不同而已,更新系统的过程是相同的,这里以一般升级和卡刷包升级方式进行解读。
1 一般升级方式
这里的一般方式是指:比如开机状态下 settings界面点击进行升级,或者 系统应用程序检测到更新后提示升级,我们点击进去,在android系统中最终都会调用到 RecoverySystem.installPackage()方法,这里也从该方法进行分析,代码如下:
public static void installPackage(Context context, File packageFile)throws IOException {String filename = packageFile.getCanonicalPath();final String filenameArg = "--update_package=" + filename;final String localeArg = "--locale=" + Locale.getDefault().toString();bootCommand(context, filenameArg, localeArg);}
这里继续分析bootCommand,代码实现如下:
//...
private static File RECOVERY_DIR = new File("/cache/recovery");
private static File COMMAND_FILE = new File(RECOVERY_DIR, "command");
private static File LOG_FILE = new File(RECOVERY_DIR, "log");
private static String LAST_PREFIX = "last_";
//...
private static void bootCommand(Context context, String... args) throws IOException {RECOVERY_DIR.mkdirs(); // In case we need itCOMMAND_FILE.delete(); // In case it's not writableLOG_FILE.delete();FileWriter command = new FileWriter(COMMAND_FILE);try {for (String arg : args) {if (!TextUtils.isEmpty(arg)) {command.write(arg);command.write("\n");}}} finally {command.close();}// Having written the command file, go ahead and rebootPowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);pm.reboot(PowerManager.REBOOT_RECOVERY);throw new IOException("Reboot failed (no permissions?)");
}
这里创建了一个文件/cache/recovery/command,然后传递进来的内容写入到该文件中,之后重启进入到recovery模式,之后便是 上一章节中Recovery模式启动的流程了,最后会调用到recovery进程的关键方法install_package。
2 sd卡更新方式(也称作sideload升级模式、卡刷模式)
Android中sideload方式是使用组合键进入recovery模式,进入recovery菜单,选中sd卡上的卡刷包,直接进行升级。安装的入口函数是apply_from_adb函数(参照上一篇Blog Recovery模式启动 执行菜单部分即可),代码如下:
//...
#define FUSE_SIDELOAD_HOST_MOUNTPOINT "/sideload"
#define FUSE_SIDELOAD_HOST_FILENAME "package.zip"
#define FUSE_SIDELOAD_HOST_PATHNAME (FUSE_SIDELOAD_HOST_MOUNTPOINT "/" FUSE_SIDELOAD_HOST_FILENAME)
#define FUSE_SIDELOAD_HOST_EXIT_FLAG "exit"
#define FUSE_SIDELOAD_HOST_EXIT_PATHNAME (FUSE_SIDELOAD_HOST_MOUNTPOINT "/" FUSE_SIDELOAD_HOST_EXIT_FLAG)
//...
int apply_from_adb(RecoveryUI* ui_, int* wipe_cache, const char* install_file) {ui = ui_;stop_adbd(); //停止adbd连接set_usb_driver(true); //启动usb连接ui->Print("\n\nNow send the package you want to apply\n""to the device with \"adb sideload \"...\n");pid_t child;if ((child = fork()) == 0) {//fork子进程,执行recovery --adbd//这时候子进程变成adbd daemon(mini版本),接收用户上传的更新包//放到对应目录下,之后adbd结束execl("/sbin/recovery", "recovery", "--adbd", NULL);_exit(-1);}int result;int status;bool waited = false;struct stat st;for (int i = 0; i < ADB_INSTALL_TIMEOUT; ++i) {//等待子进程adbd结束后再执行if (waitpid(child, &status, WNOHANG) != 0) {result = INSTALL_ERROR;waited = true;break;}//检查sideload/update.zip文件是否存在,查看更新包if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &st) != 0) {if (errno == ENOENT && i < ADB_INSTALL_TIMEOUT-1) {sleep(1);continue;} else {ui->Print("\nTimed out waiting for package.\n\n", strerror(errno));result = INSTALL_ERROR;kill(child, SIGKILL);break;}}//文件存在,则开始安装result = install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache, install_file, false);break;}if (!waited) {stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st);waitpid(child, &status, 0);}if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {if (WEXITSTATUS(status) == 3) {ui->Print("\nYou need adb 1.0.32 or newer to sideload\nto this device.\n\n");} else if (!WIFSIGNALED(status)) {ui->Print("\n(adbd status %d)\n", WEXITSTATUS(status));}}set_usb_driver(false);//关闭usb连接maybe_restart_adbd();return result;
}
整个流程总结如下:开启子进程adbd(mini版本)接收从用户上传的更新包,并将其更名为update.zip,放到指定的路径下,adbd结束。recovery模式继续运行,如果更新包存在,则直接使用install_package来完成升级。
3 升级的入口函数
所有的更新方式都会调用到install_package,接下来开始分析它,代码如下:
int install_package(const char* path, int* wipe_cache, const char* install_file,bool needs_mount)
{FILE* install_log = fopen_path(install_file, "w");if (install_log) {//如果/tmp/last_install日志文件存在fputs(path, install_log);//写入更新路径fputc('\n', install_log);}int result;if (setup_install_mounts() != 0) {//关键点,确保/tmp 和 /cache已经挂载result = INSTALL_ERROR;} else {//关键点,继续安装result = really_install_package(path, wipe_cache, needs_mount);}if (install_log) {//记录安装是否成功fputc(result == INSTALL_SUCCESS ? '1' : '0', install_log);fputc('\n', install_log);fclose(install_log);}return result;
}
这里关注➕两个关键点setup_install_mounts 和really_install_package,分析分别如下。
@1 setup_install_mounts 代码实现如下:
int setup_install_mounts() {//...for (int i = 0; i < fstab->num_entries; ++i) {Volume* v = fstab->recs + i;//确保tmp分区和cache分区挂载if (strcmp(v->mount_point, "/tmp") == 0 ||strcmp(v->mount_point, "/cache") == 0) {//...} else {//确保其他分区没有挂载if (ensure_path_unmounted(v->mount_point) != 0) {//...}}}return 0;
}
这里主要是确认tmp分区和cache分区挂载成功,其他分区不挂载。
@2 really_install_package 代码实现如下:
static int really_install_package(const char *path, int* wipe_cache, bool needs_mount)
{//更新屏幕的提示为正在更新ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);ui->Print("Finding update package...\n");ui->SetProgressType(RecoveryUI::DETERMINATE);ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME);ui->Print("Opening update package...\n");//确保更新包的路径mountif (path && needs_mount) {if (path[0] == '@') {ensure_path_mounted(path+1);} else {ensure_path_mounted(path);}}MemMapping map;if (sysMapFile(path, &map) != 0) {LOGE("failed to map file\n");return INSTALL_CORRUPT;}//装载设备的签名文件int numKeys;Certificate* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys);if (loadedKeys == NULL) {LOGE("Failed to load keys\n");return INSTALL_CORRUPT;}ui->Print("Verifying update package...\n");//校验更新包签名int err;err = verify_file(map.addr, map.length, loadedKeys, numKeys);free(loadedKeys);LOGI("verify_file returned %d\n", err);if (err != VERIFY_SUCCESS) {LOGE("signature verification failed\n");sysReleaseMap(&map);return INSTALL_CORRUPT;}//打开zip包文件ZipArchive zip;err = mzOpenZipArchive(map.addr, map.length, &zip);//...ui->Print("Installing update...\n");ui->SetEnableReboot(false);//开始安装int result = try_update_binary(path, &zip, wipe_cache);ui->SetEnableReboot(true);ui->Print("\n");sysReleaseMap(&map);return result;
}
这里最重要的就是检验更新包的签名,通过签名后,这里调用了关键函数try_update_binary,代码如下:
static int
try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) {const ZipEntry* binary_entry =mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME);if (binary_entry == NULL) {//安装包中找不到update_binary则直接返回mzCloseZipArchive(zip);return INSTALL_CORRUPT;}const char* binary = "/tmp/update_binary";unlink(binary);int fd = creat(binary, 0755);//创建空文件/tmp/update_binaryif (fd < 0) {mzCloseZipArchive(zip);LOGE("Can't make %s\n", binary);return INSTALL_ERROR;}//这里将update_binary文件直接保存到tmp/update_binary中bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd);close(fd);mzCloseZipArchive(zip);//...//创建管道int pipefd[2];pipe(pipefd);//准备参数const char** args = (const char**)malloc(sizeof(char*) * 5);args[0] = binary;args[1] = EXPAND(RECOVERY_API_VERSION); // defined in Android.mkchar* temp = (char*)malloc(10);sprintf(temp, "%d", pipefd[1]);args[2] = temp;args[3] = (char*)path;args[4] = NULL;pid_t pid = fork();//创建子进程if (pid == 0) {umask(022);close(pipefd[0]);execv(binary, (char* const*)args);//子进程执行update_binaryfprintf(stdout, "E:Can't run %s (%s)\n", binary, strerror(errno));_exit(-1);}close(pipefd[1]);*wipe_cache = 0;//父进程和子进程通过管道进行通信//主进程主要负责更新UI、提示信息、更新进度char buffer[1024];FILE* from_child = fdopen(pipefd[0], "r");while (fgets(buffer, sizeof(buffer), from_child) != NULL) {char* command = strtok(buffer, " \n");if (command == NULL) {continue;} else if (strcmp(command, "progress") == 0) {char* fraction_s = strtok(NULL, " \n");char* seconds_s = strtok(NULL, " \n");float fraction = strtof(fraction_s, NULL);int seconds = strtol(seconds_s, NULL, 10);ui->ShowProgress(fraction * (1-VERIFICATION_PROGRESS_FRACTION), seconds);} else if (strcmp(command, "set_progress") == 0) {char* fraction_s = strtok(NULL, " \n");float fraction = strtof(fraction_s, NULL);ui->SetProgress(fraction);} else if (strcmp(command, "ui_print") == 0) {char* str = strtok(NULL, "\n");if (str) {ui->Print("%s", str);} else {ui->Print("\n");}fflush(stdout);} else if (strcmp(command, "wipe_cache") == 0) {*wipe_cache = 1;} else if (strcmp(command, "clear_display") == 0) {ui->SetBackground(RecoveryUI::NONE);//清除背景} else if (strcmp(command, "enable_reboot") == 0) {ui->SetEnableReboot(true);} }fclose(from_child);int status;waitpid(pid, &status, 0);//等待子进程结束if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {return INSTALL_ERROR;}return INSTALL_SUCCESS;
}
从上面的分析中知道,更新的可执行程序并不在recovery模式中,而是在update.zip中,解压升级包后,把里面的update_binary拷贝到/tmp/update_binary,并创建一个子进程开始执行,同时主进程通过管道机制和子进程通信,不断更新进度、UI、提示信息等。直到最后update_binary执行结束。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
