Android--Recovery模块之恢复出厂设置
来源:互联网 发布:开农村淘宝怎么赚钱 编辑:程序博客网 时间:2024/05/09 09:47
(一)、 在进行详细流程分析之前,先看一下几个重要概念:
一、Recovery的工作需要整个软件平台的配合,从架构角度看,有三个部分:
1、Main system:用boot.img启动的Linux系统,Android的正常工作模式。
2、Recovery:用recovery.img启动的Linux系统,只要是运行Recovery程序。
3、Bootloader:除了加载、启动系统,还会通过读取flash的MISC分区获取来自Main system和Recovery的消息,并以此觉得做何种操作。
在Recovery的工作流程中,上述三个实体的通信是必不可少的。
二、通信接口
1、BCB(bootloader control block)
Bootloader和Recovery模块以及主系统之间的通信是通过系统的misc分区来完成的,描述misc分区的数据结构是bootloader_message,定义如下:
struct bootloader_message { char command[32]; char status[32]; char recovery[768]; // The 'recovery' field used to be 1024 bytes. It has only ever // been used to store the recovery command line, so 768 bytes // should be plenty. We carve off the last 256 bytes to store the // stage string (for multistage packages) and possible future // expansion. char stage[32]; char reserved[224];};command:command字段中存储的是命令,当想要重启进入Recovery模式,或升级radio/bootloader firmware时,会更新这个字段(如果它的值是“boot-recovery”,系统进入Recovery模式;如果它的值是“update-radio”或“update-hboot”,系统进入更新firmware模式,这个更新过程由bootloader完成;如果command中的值为null,则进入主系统,正常启动);当firmware更新完毕,为了启动后进入Recovery做最终的清除,bootloader还会修改它。
status:status字段存储的是更新的结果。update-radio或update-hboot完成后,由Recovery或Bootloader将更新结果写入到这个字段中。
recovery:recovery字段存放的是recovery模块的启动参数。仅被Main system写入,用于向Recovery发送消息,必须以“recovery\n”开头,否则这个字段中的所有内容会被忽略。这一项的内容中“recovery/\n”以后的部分,是/cache/recovery/command支持的命令,可以认为这是在Recovery操作过程中,对命令操作的备份。Recovery也会更新这个域的信息,执行某操作前把该操作命令写到recovery域,并更新command域,操作完成后再清空recovery域及command域,这样在进入Main system之前,就能确保操作被执行。
(二)详细流程分析
一、入口流程分析
1、在MasterClearConfirm.java(/packages/apps/settings/src/com/android/settings/MasterClearConfirm.java)中显示恢复出厂提示和对应button,点击button后调用button的click方法。如果选中了mEraseSdCard(格式化SD卡),则启动ExternalStorageFormatter的服务;否则发送Intent.ACTION_MASTER_CLEAR广播。
private void doMasterClear() { if (mEraseSdCard) { Intent intent = new Intent(ExternalStorageFormatter.FORMAT_AND_FACTORY_RESET); intent.putExtra(Intent.EXTRA_REASON, "MasterClearConfirm"); intent.setComponent(ExternalStorageFormatter.COMPONENT_NAME); getActivity().startService(intent); } else { Intent intent = new Intent(Intent.ACTION_MASTER_CLEAR); intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); intent.putExtra(Intent.EXTRA_REASON, "MasterClearConfirm"); getActivity().sendBroadcast(intent); // Intent handling is asynchronous -- assume it will happen soon. } }2、定位到注册改广播的地方,/frameworks/base/core/res/AndroidMenifest.xml中注册了改广播接收器。
<receiver android:name="com.android.server.MasterClearReceiver" android:permission="android.permission.MASTER_CLEAR"> <intent-filter android:priority="100" > <!-- For Checkin, Settings, etc.: action=MASTER_CLEAR --> <action android:name="android.intent.action.MASTER_CLEAR" /> <!-- MCS always uses REMOTE_INTENT: category=MASTER_CLEAR --> <action android:name="com.google.android.c2dm.intent.RECEIVE" /> <category android:name="android.intent.category.MASTER_CLEAR" /> </intent-filter> </receiver>3、对应的在MasterClearReceiver(\frameworks\base\services\core\java\com\android\server)会接受此广播,在onReceive()方法中会调用RecoverySystem.rebootWipeUserData()方法,并传递三个参数:context;shutdown=false;reason=“MasterClearConfirm”。
@Override public void onReceive(final Context context, final Intent intent) { if (intent.getAction().equals(Intent.ACTION_REMOTE_INTENT)) { if (!"google.com".equals(intent.getStringExtra("from"))) { Slog.w(TAG, "Ignoring master clear request -- not from trusted server."); return; } } final boolean shutdown = intent.getBooleanExtra("shutdown", false); final String reason = intent.getStringExtra(Intent.EXTRA_REASON);//"MasterClearConfirm" Slog.w(TAG, "!!! FACTORY RESET !!!"); // The reboot call is blocking, so we need to do it on another thread. Thread thr = new Thread("Reboot") { @Override public void run() { try { RecoverySystem.rebootWipeUserData(context, shutdown, reason);//shutdown=false;reason="MasterClearConfirm" Log.wtf(TAG, "Still running after master clear?!"); } catch (IOException e) { Slog.e(TAG, "Can't perform master clear/factory reset", e); } catch (SecurityException e) { Slog.e(TAG, "Can't perform master clear/factory reset", e); } } }; thr.start(); }
二、应用层流程分析
设置模块中恢复出厂设置,不管是否删除SD卡,最终都会执行如下两步:
1、 往/cache/recovery/command文件中写入命令字段
2、 重启系统,进入recovery模式
具体可参考framework/base/core/java/android/os/RecoverySystem.java文件,代码片段如下:
/** * Reboots the device and wipes the user data and cache * partitions. This is sometimes called a "factory reset", which * is something of a misnomer because the system partition is not * restored to its factory state. Requires the * {@link android.Manifest.permission#REBOOT} permission. *重启设备并且擦除用户data和cache分区;难免有些用词不当,因为system分区没有回复到出厂状态。 * @param context the Context to use * @param shutdown if true, the device will be powered down after * the wipe completes, rather than being rebooted * back to the regular system. * true,擦除完成后关闭设备;否则重新引导回正常系统; * @throws IOException if writing the recovery command file * fails, or if the reboot itself fails. * @throws SecurityException if the current user is not allowed to wipe data. * 如果当前用户不允许擦除数据,抛出SecurityException。 * @hide */ public static void rebootWipeUserData(Context context, boolean shutdown, String reason) throws IOException { UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); if (um.hasUserRestriction(UserManager.DISALLOW_FACTORY_RESET)) { throw new SecurityException("Wiping data is not allowed for this user."); } final ConditionVariable condition = new ConditionVariable(); Intent intent = new Intent("android.intent.action.MASTER_CLEAR_NOTIFICATION"); intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); context.sendOrderedBroadcastAsUser(intent, UserHandle.OWNER, android.Manifest.permission.MASTER_CLEAR, new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { condition.open(); } }, null, 0, null, null);//发送广播:android.intent.action.MASTER_CLEAR_NOTIFICATION,通知所有接收端处理相关行为 // Block until the ordered broadcast has completed. condition.block();//等待所有接收完成动作 String shutdownArg = null; if (shutdown) { shutdownArg = "--shutdown_after"; }//从上面传递参数,此处shutdown为false String reasonArg = null; if (!TextUtils.isEmpty(reason)) { reasonArg = "--reason=" + sanitizeArg(reason); }//reason为“MasterClearConfirm” final String localeArg = "--locale=" + Locale.getDefault().toString(); bootCommand(context, shutdownArg, "--wipe_data", reasonArg, localeArg);}
<pre name="code" class="java"> /** Used to communicate with recovery. See bootable/recovery/recovery.c. */ 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");
/** * Reboot into the recovery system with the supplied argument. * @param arg to pass to the recovery utility. * @throws IOException if something goes wrong. */ private static void bootCommand(Context context, String arg) throws IOException { RECOVERY_DIR.mkdirs(); // In case we need it COMMAND_FILE.delete(); // In case it's not writable LOG_FILE.delete(); FileWriter command = new FileWriter(COMMAND_FILE); try { command.write(arg);//将arg参数写入/cache/recovery/command文件中 command.write("\n"); } finally { command.close(); } // Having written the command file, go ahead and reboot PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); pm.reboot("recovery");//重启,进入recovery模式。 throw new IOException("Reboot failed (no permissions?)"); }在rebootWipeUserData方法中,会调用bootCommand方法,并传入参数--wipe_data命令字段(命令字段:--wipe_data--reason="MasterClearConfirm"--locale="zh_CN");在bootCommand方法中将命令字段写入/cache/recovery/command文件中,并重启进入recovery模式,recovery服务会通过读取此参数来擦除data和cache分区。详细流程如下。
三、重启流程分析
1、在bootCommand中将命令字段写入/cache/recovery/command文件后,调用PowerManager的reboot方法,代码如下:
public void reboot(String reason) { try { mService.reboot(false, reason, true); } catch (RemoteException e) { } }进而调用PowerManagerService对象的reboot方法,传递三个参数:confirm=false,不会显示重启确认对话框;reason=“recovery”,导致重启的原因是进行恢复出厂设置,重启后进入recovery模式;wait=true,等待重启完成。代码如下:
/** * Reboots the device. * * @param confirm If true, shows a reboot confirmation dialog. * @param reason The reason for the reboot, or null if none. * @param wait If true, this call waits for the reboot to complete and does not return. */ @Override // Binder call public void reboot(boolean confirm, String reason, boolean wait) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null); if (PowerManager.REBOOT_RECOVERY.equals(reason)) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null); } final long ident = Binder.clearCallingIdentity(); try { shutdownOrRebootInternal(false, confirm, reason, wait); } finally { Binder.restoreCallingIdentity(ident); } }
private void shutdownOrRebootInternal(final boolean shutdown, final boolean confirm, final String reason, boolean wait) {//shutdown=false;confirm=false;reason=“recovery”;wait=true; if (mHandler == null || !mSystemReady) { throw new IllegalStateException("Too early to call shutdown() or reboot()"); } Runnable runnable = new Runnable() { @Override public void run() { synchronized (this) { if (shutdown) { ShutdownThread.shutdown(mContext, confirm); } else { ShutdownThread.reboot(mContext, reason, confirm);//恢复出厂设置时执行reboot } } } }; // ShutdownThread must run on a looper capable of displaying the UI.运行在显示用户接口界面 Message msg = Message.obtain(mHandler, runnable); msg.setAsynchronous(true);//设置为异步消息 mHandler.sendMessage(msg); // PowerManager.reboot() is documented not to return so just wait for the inevitable.//reboot没有返回值 if (wait) { synchronized (runnable) { while (true) { try { runnable.wait();//挂起,其他线程调用notify时才会重新激活 } catch (InterruptedException e) { } } } } }
由于传递的shutdown参数为false,此处执行ShutdownThread类的reboot方法,并且传递参数reason为“recovery”;confirm为false。ShutdownThread类位于/frameworks/base/services/core/java/com/android/server/power中。
/** * Request a clean shutdown, waiting for subsystems to clean up their * state etc. Must be called from a Looper thread in which its UI * is shown. * * @param context Context used to display the shutdown progress dialog. * @param reason code to pass to the kernel (e.g. "recovery"), or null. * @param confirm true if user confirmation is needed before shutting down. */ public static void reboot(final Context context, String reason, boolean confirm) { mReboot = true; mRebootSafeMode = false; mRebootReason = reason; shutdownInner(context, confirm); }在reboot方法中分别为变量赋值:mReboot=true;mRebootSafeMode=false;mRebootReason=reason=“recovery”;之后调用shutdownInner方法。
static void shutdownInner(final Context context, boolean confirm) { // ensure that only one thread is trying to power down. // any additional calls are just returned synchronized (sIsStartedGuard) { if (sIsStarted) { Log.d(TAG, "Request to shutdown already running, returning."); return; } } boolean showRebootOption = false; final int longPressBehavior = context.getResources().getInteger( com.android.internal.R.integer.config_longPressOnPowerBehavior); int resourceId = mRebootSafeMode ? com.android.internal.R.string.reboot_safemode_confirm : (longPressBehavior == 2 ? com.android.internal.R.string.shutdown_confirm_question : com.android.internal.R.string.shutdown_confirm); if (showRebootOption && !mRebootSafeMode) { resourceId = com.android.internal.R.string.reboot_confirm; } Log.d(TAG, "Notifying thread to start shutdown longPressBehavior=" + longPressBehavior); if (confirm) { final CloseDialogReceiver closer = new CloseDialogReceiver(context); if (sConfirmDialog != null) { sConfirmDialog.dismiss(); } closer.dialog = sConfirmDialog; sConfirmDialog.setOnDismissListener(closer); sConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); sConfirmDialog.show(); } else { beginShutdownSequence(context); } }上面传递的confirm为false,因此这里直接执行beginShutdownSequence方法。
private static void beginShutdownSequence(Context context) { synchronized (sIsStartedGuard) { if (sIsStarted) { Log.d(TAG, "Shutdown sequence already running, returning."); return; } sIsStarted = true; } 。。。。。。 sInstance.start(); }beginShutdownSequence方法中进行关机的一些准备操作,包括停止播放音乐,显示关机对话框,设置CPU锁避免cpu休眠,screen锁防止屏幕变暗(防止机器在关机过程中休眠);最后执行sInstance.start方法。sInstance是ShutdownThread的对象,ShutdownThread集成Thread,因此当执行sInstance.start方法的时候就是允许Thread的run方法。
/** * Makes sure we handle the shutdown gracefully. * Shuts off power regardless of radio and bluetooth state if the alloted time has passed. */ public void run() { BroadcastReceiver br = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // We don't allow apps to cancel this, so ignore the result. actionDone(); } }; /* * Write a system property in case the system_server reboots before we * get to the actual hardware restart. If that happens, we'll retry at * the beginning of the SystemServer startup.这里设置重启的原因为recovery */ { String reason = (mReboot ? "1" : "0") + (mRebootReason != null ? mRebootReason : ""); SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason); } /* * If we are rebooting into safe mode, write a system property * indicating so. */ if (mRebootSafeMode) { SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1"); } Log.i(TAG, "Sending shutdown broadcast..."); // First send the high-level shut down broadcast. mActionDone = false; Intent intent = new Intent(Intent.ACTION_SHUTDOWN); intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); mContext.sendOrderedBroadcastAsUser(intent,//发送关机广播,通知各app保存数据; UserHandle.ALL, null, br, mHandler, 0, null, null); final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME; synchronized (mActionDoneSync) { while (!mActionDone) { long delay = endTime - SystemClock.elapsedRealtime(); if (delay <= 0) { Log.w(TAG, "Shutdown broadcast timed out"); break; } try { mActionDoneSync.wait(delay); } catch (InterruptedException e) { } } } Log.i(TAG, "Shutting down activity manager..."); final IActivityManager am = ActivityManagerNative.asInterface(ServiceManager.checkService("activity")); if (am != null) { try { am.shutdown(MAX_BROADCAST_TIME);//关闭activity manager,即关闭AppOpsService,UsageStatsService,BatteryStatsService } catch (RemoteException e) { } } Log.i(TAG, "Shutting down package manager..."); final PackageManagerService pm = (PackageManagerService) ServiceManager.getService("package"); if (pm != null) { pm.shutdown();//关闭package manager,保存app使用时间到 disk里 } // Shutdown radios. shutdownRadios(MAX_RADIO_WAIT_TIME);//shutdown radio[NFC,BT,MODEM] // Shutdown MountService to ensure media is in a safe state IMountShutdownObserver observer = new IMountShutdownObserver.Stub() { public void onShutDownComplete(int statusCode) throws RemoteException { Log.w(TAG, "Result code " + statusCode + " from MountService.shutdown"); actionDone(); } }; Log.i(TAG, "Shutting down MountService"); // Set initial variables and time out time.特别要注意这里,很可能会导致关机失败 mActionDone = false; final long endShutTime = SystemClock.elapsedRealtime() + MAX_SHUTDOWN_WAIT_TIME; synchronized (mActionDoneSync) { try { final IMountService mount = IMountService.Stub.asInterface( ServiceManager.checkService("mount")); if (mount != null) { mount.shutdown(observer); } else { Log.w(TAG, "MountService unavailable for shutdown"); } } catch (Exception e) { Log.e(TAG, "Exception during MountService shutdown", e); } while (!mActionDone) { long delay = endShutTime - SystemClock.elapsedRealtime(); if (delay <= 0) { Log.w(TAG, "Shutdown wait timed out"); break; } try { mActionDoneSync.wait(delay); } catch (InterruptedException e) { } } } rebootOrShutdown(mReboot, mRebootReason); }在run方法中设置系统属性“sys.shutdown.requested”为“recovery”,之后关闭一些系统服务,最后调用rebootOrShutdown方法,并传递参数mReboot=true,mRebootReason=“recovery”。
/** * Do not call this directly. Use {@link #reboot(Context, String, boolean)} * or {@link #shutdown(Context, boolean)} instead. * * @param reboot true to reboot or false to shutdown * @param reason reason for reboot */ public static void rebootOrShutdown(boolean reboot, String reason) { if (reboot) { Log.i(TAG, "Rebooting, reason: " + reason); PowerManagerService.lowLevelReboot(reason);//重启 Log.e(TAG, "Reboot failed, will attempt shutdown instead"); } else if (SHUTDOWN_VIBRATE_MS > 0) {//如果是执行关机操作,执行震动 // vibrate before shutting down Vibrator vibrator = new SystemVibrator(); try { vibrator.vibrate(SHUTDOWN_VIBRATE_MS, VIBRATION_ATTRIBUTES); } catch (Exception e) { // Failure to vibrate shouldn't interrupt shutdown. Just log it. Log.w(TAG, "Failed to vibrate during shutdown.", e); } // vibrator is asynchronous so we need to wait to avoid shutting down too soon. try { Thread.sleep(SHUTDOWN_VIBRATE_MS); } catch (InterruptedException unused) { } } // Shutdown power Log.i(TAG, "Performing low-level shutdown..."); PowerManagerService.lowLevelShutdown();//关机 }在rebootOrShutdown方法中根据参数reboot为true,主要是调用了PowerManagerService.lowLevelReboot方法。
/** * Low-level function to reboot the device. On success, this * function doesn't return. If more than 20 seconds passes from * the time a reboot is requested (120 seconds for reboot to * recovery), this method returns. * * @param reason code to pass to the kernel (e.g. "recovery"), or null. */ public static void lowLevelReboot(String reason) { if (reason == null) { reason = ""; } long duration; if (reason.equals(PowerManager.REBOOT_RECOVERY)) {//recovery // If we are rebooting to go into recovery, instead of // setting sys.powerctl directly we'll start the // pre-recovery service which will do some preparation for // recovery and then reboot for us. // // This preparation can take more than 20 seconds if // there's a very large update package, so lengthen the // timeout. SystemProperties.set("ctl.start", "pre-recovery"); duration = 120 * 1000L; } else { SystemProperties.set("sys.powerctl", "reboot," + reason); duration = 20 * 1000L; } try { Thread.sleep(duration); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }在lowLevelReboot方法中,主要是调用SystemProperties.set("ctl.start", "pre-recovery");,启动“pre-recovery”服务。正是这个动作触发关机流程继续往下走,“pre-recovery”服务定义在init.rc(/system/core/rootdir)中。如下所示:
service pre-recovery /system/bin/uncrypt class main disabled //不自动启动 oneshot底层的内容不再分析,执行关机流程,之后开机,底层判断节点后进入recovery模式,recovery.img释放完成后,进入开机流程。
四、恢复模式流程分析
重启后,从recovery模式的init.rc文件中可以看到启动recovery服务,具体可参考bootable/recovery/etc/init.rc文件,代码片段如下:
service recovery /sbin/recovery seclabel u:r:recovery:s0
1、recovery服务的主函数在bootable/recovery/recovery.c文件中,main函数结构清晰,主要流程分析如下:
int main(int argc, char **argv) { time_t start = time(NULL); redirect_stdio(TEMPORARY_LOG_FILE);// 将标准输出和标准错误输出重定向到/tmp/recovery.log。 // If this binary is started with the single argument "--adbd", // instead of being the normal recovery binary, it turns into kind // of a stripped-down version of adbd that only supports the // 'sideload' command. Note this must be a real argument, not // anything in the command file or bootloader control block; the // only way recovery should be run with this argument is when it // starts a copy of itself from the apply_from_adb() function. if (argc == 2 && strcmp(argv[1], "--adbd") == 0) { adb_main();//如果启动参数带有adbd,则作为adbd的demon进程运行 return 0; } printf("Starting recovery (pid %d) on %s", getpid(), ctime(&start)); load_volume_table();//读取/etc/recovery.fstab文件中的分区内容 ensure_path_mounted(LAST_LOG_FILE);// "/cache/recovery/last_log" rotate_last_logs(KEEP_LOG_COUNT);//将last_log文件重命名,Rename last_log -> last_log.1 -> last_log.2 -> ... -> last_log.$max get_args(&argc, &argv);//获得启动参数;将"boot-recovery"和“--wipe_data”写入BCB(bootloader control block) const char *send_intent = NULL; const char *update_package = NULL; int wipe_data = 0, wipe_cache = 0, show_text = 0; bool just_exit = false; bool shutdown_after = false; int arg; while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {//解析命令行选项参数, switch (arg) { case 's': send_intent = optarg; break; case 'u': update_package = optarg; break;//升级系统 case 'w': wipe_data = wipe_cache = 1; break;//擦除数据 case 'c': wipe_cache = 1; break;//擦除CACHE分区 case 't': show_text = 1; break;//指示升级时是否显示UI case 'x': just_exit = true; break;//退出 case 'l': locale = optarg; break;//指定Local case 'g': { if (stage == NULL || *stage == '\0') { char buffer[20] = "1/"; strncat(buffer, optarg, sizeof(buffer)-3); stage = strdup(buffer); } break; } case 'p': shutdown_after = true; break;//退出recovery后关闭系统 case 'r': reason = optarg; break; case '?': LOGE("Invalid command argument\n"); continue; } } if (locale == NULL) {//如果没有指定local load_locale_from_cache();//从文件/cache/recovery/last_local中读取 } printf("locale is [%s]\n", locale); printf("stage is [%s]\n", stage); printf("reason is [%s]\n", reason); Device* device = make_device();//创建DefaultDevice类对象,<span style="font-size: 11.8181819915771px; font-family: Arial, Helvetica, sans-serif;">http://blog.csdn.net/u010223349/article/details/40392789</span> ui = device->GetUI();// 调用Device类的GetUI函数返回RecoveryUI对象,recovery中共有三个关于UI的类:DefaultUI,ScreenRecoveryUI,RecoveryUI gCurrentUI = ui;//初始化RecoveryUI对象 ui->SetLocale(locale);//为UI设置local,这样就能以合适的语音显示 ui->Init(); int st_cur, st_max; if (stage != NULL && sscanf(stage, "%d/%d", &st_cur, &st_max) == 2) { ui->SetStage(st_cur, st_max); } ui->SetBackground(RecoveryUI::NONE);//设置初始状态的背景图 if (show_text) ui->ShowText(true);//如果启动参数指定了显示UI,则设置在UI对象中 struct selinux_opt seopts[] = { { SELABEL_OPT_PATH, "/file_contexts" } }; sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1); if (!sehandle) { ui->Print("Warning: No file_contexts\n"); } device->StartRecovery();//空函数 printf("Command:"); for (arg = 0; arg < argc; arg++) { printf(" \"%s\"", argv[arg]); } printf("\n"); if (update_package) {//预处理更新命令</span> // For backwards compatibility on the cache partition only, if // we're given an old 'root' path "CACHE:foo", change it to // "/cache/foo".如果更新命令以“CACHE:”开头,则把它换成实际的cache目录路径 if (strncmp(update_package, "CACHE:", 6) == 0) { int len = strlen(update_package) + 10; char* modified_path = (char*)malloc(len); strlcpy(modified_path, "/cache/", len); strlcat(modified_path, update_package+6, len); printf("(replacing path \"%s\" with \"%s\")\n", update_package, modified_path); update_package = modified_path; } } printf("\n"); //获取一些property的值 property_list(print_property, NULL); property_get("ro.build.display.id", recovery_version, ""); printf("\n"); int status = INSTALL_SUCCESS; if (update_package != NULL) {//如果命令是更新系统 status = install_package(update_package, &wipe_cache, TEMPORARY_INSTALL_FILE, true);///tmp/last_install if (status == INSTALL_SUCCESS && wipe_cache) {//如果更新成功,并且启动参数要求擦除cache分区 if (erase_volume("/cache")) {//擦除CACHE分区 LOGE("Cache wipe (requested by package) failed."); } } if (status != INSTALL_SUCCESS) {//安装失败 ui->Print("Installation aborted.\n");//打印提示 // 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. char buffer[PROPERTY_VALUE_MAX+1]; property_get("ro.build.fingerprint", buffer, ""); if (strstr(buffer, ":userdebug/") || strstr(buffer, ":eng/")) { ui->ShowText(true);//在屏幕上打印失败的版本信息 } } } else if (wipe_data) {//如果是擦除数据区的命令 if (device->WipeData()) status = INSTALL_ERROR;//成功返回0 if (erase_volume("/data")) status = INSTALL_ERROR;//格式化(擦除)DATA分区 if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR;//格式化(擦除)CACHE分区 if (erase_persistent_partition() == -1 ) status = INSTALL_ERROR; if (status != INSTALL_SUCCESS) ui->Print("Data wipe failed.\n"); } else if (wipe_cache) {//擦除cache分区的命令 if (wipe_cache && erase_volume("/cache")) status = INSTALL_ERROR; if (status != INSTALL_SUCCESS) ui->Print("Cache wipe failed.\n"); } else if (!just_exit) {//只是回退命令 status = INSTALL_NONE; // No command specified ui->SetBackground(RecoveryUI::NO_COMMAND); } if (status == INSTALL_ERROR || status == INSTALL_CORRUPT) { copy_logs(); ui->SetBackground(RecoveryUI::ERROR);//执行命令错误,在屏幕上显示标志 } Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT; if (status != INSTALL_SUCCESS || ui->IsTextVisible()) { Device::BuiltinAction temp = prompt_and_wait(device, status);//进入菜单模式 if (temp != Device::NO_ACTION) after = temp; } // Save logs and clean up before rebooting or shutting down. finish_recovery(send_intent);//擦除BCB switch (after) {//结束recovery模式,关机或重启系统 case Device::SHUTDOWN: ui->Print("Shutting down...\n"); property_set(ANDROID_RB_PROPERTY, "shutdown,"); break; case Device::REBOOT_BOOTLOADER: ui->Print("Rebooting to bootloader...\n"); property_set(ANDROID_RB_PROPERTY, "reboot,bootloader"); break; default: ui->Print("Rebooting...\n"); property_set(ANDROID_RB_PROPERTY, "reboot,"); break; } sleep(5); // should reboot before this finishes return EXIT_SUCCESS;}
我们分析一下main函数的主要流程。
(1)、调用load_volume_table()函数,读取/etc/recovery.fstab文件的内容,这个文件中保存的是进入recovery模式后系统的分区情况,包括分区名称,参数等。
(2)、调用get_args()函数的主要作用就是建立recovery的启动参数,如果命令行中有recovery命令,则优先执行命令行的recovery命令,否则读取misc分区中的命令。如果misc分区中不存在命令,则执行/cache/recovery/command文件中的命令。get_args()函数则通过get_bootloader_message()来读取misc分区中的命令。参考:http://book.51cto.com/art/201504/474440.htm
// command line args come from, in decreasing precedence:// - the actual command line// - the bootloader control block (one per line, after "recovery")// - the contents of COMMAND_FILE (one per line)static voidget_args(int *argc, char ***argv) { struct bootloader_message boot; memset(&boot, 0, sizeof(boot));//分配存储空间 get_bootloader_message(&boot); // this may fail, leaving a zeroed structure读取/misc分区的命令,保存到boot变量中 stage = strndup(boot.stage, sizeof(boot.stage));//将boot.stage拷贝到新建的位置处,有stage指向该位置,使用完后要使用free删除动态申请的内存空间 if (boot.command[0] != 0 && boot.command[0] != 255) { LOGI("Boot command: %.*s\n", (int)sizeof(boot.command), boot.command); } if (boot.status[0] != 0 && boot.status[0] != 255) { LOGI("Boot status: %.*s\n", (int)sizeof(boot.status), boot.status); } // --- if arguments weren't supplied, look in the bootloader control block if (*argc <= 1) {//如果命令行没有传递参数, boot.recovery[sizeof(boot.recovery) - 1] = '\0'; // Ensure termination const char *arg = strtok(boot.recovery, "\n");//将boot.recovery以"\n"为分隔符进行分割,arg指向boot变量的recovery if (arg != NULL && !strcmp(arg, "recovery")) {//如果字符串以recovery开头,则使用从/misc分区读取的命令串建立启动参数 *argv = (char **) malloc(sizeof(char *) * MAX_ARGS); (*argv)[0] = strdup(arg);//拷贝arg的副本,有(*argv)[0]指向该副本 for (*argc = 1; *argc < MAX_ARGS; ++*argc) { if ((arg = strtok(NULL, "\n")) == NULL) break; (*argv)[*argc] = strdup(arg); } LOGI("Got arguments from boot message\n"); } else if (boot.recovery[0] != 0 && boot.recovery[0] != 255) { LOGE("Bad boot message\n\"%.20s\"\n", boot.recovery); } } // --- if that doesn't work, try the command file if (*argc <= 1) {//如果从/misc分区也没有读到命令 FILE *fp = fopen_path(COMMAND_FILE, "r");//打开/cache/recovery/command文件 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); } } // --> write the arguments we have back into the bootloader control block // always boot into recovery after this (until finish_recovery() is called)把启动参数也放到boot对象中 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));//将两个char类型连接 } set_bootloader_message(&boot);}A、bootloader_message这个结构体在文章开始部分做了介绍。
B、get_bootloader_message函数,主要工作是根据分区的文件格式类型(mtd或emmc)从MISC分区中读取BCB数据块到一个临时的变量中,代码如下:
int get_bootloader_message(struct bootloader_message *out) { Volume* v = volume_for_path("/misc"); if (v == NULL) { LOGE("Cannot load volume /misc!\n"); return -1; } if (strcmp(v->fs_type, "mtd") == 0) { return get_bootloader_message_mtd(out, v); } else if (strcmp(v->fs_type, "emmc") == 0) { return get_bootloader_message_block(out, v); } LOGE("unknown misc partition fs_type \"%s\"\n", v->fs_type); return -1;}C、之后开始判断Recovery服务是否有带命令行的参数(/sbin/recovery,根据现有的逻辑是没有的),若没有就从BCB这读取recovery域;如果读取失败则从/cache/recovery/command中继续读取。这样这个BCB的临时变量值得recovery域就被更新了。在将这个BCB的临时变量写回真实的BCB之前,还会更新这个BCB临时变量的command域为“boot-recovery”。这样做的目的是如果在升级失败(比如在升级过程中断电)时,系统在重启之后还会进入Recovery模式,知道升级完成。
D、在这个BCB的临时变量的各个域更新完成后使用set_bootloader_message()写回到真实的BCB块中。
用一个简单的图来概括一下这个过程,如下:
(3)、调用make_device()创建Device对象,这里实际上创建的是DefaultDevice对象,它继承自Device类,DefaultDevice对象又创建了ScreenRecoveryUI对象,代码如下:
public: DefaultDevice() : ui(new ScreenRecoveryUI) { }
我们会看到Device的很多函数都是空函数,并没有实现。这是为什么呢?因为Android提供的Recovery模块可以由厂商进行个性化开发,厂商通过重载Device类来加入自己特有的功能。这些空函数就是留着厂商实现的。
(4)、执行DefaultUI对象的初始化,Recovery的UI就是简单的控制台文本输出界面。
(5)、执行更新系统的命令。如果从参数中得到的是更新系统的命令,则调用install_package函数来更新。
(6)、执行删除DATA分区、CACHE分区的命令。
(7)、执行完命令后,如果命令执行不成功,或者调用DefaultUI的IsTextVisible函数返回true,调用promp_and_wait函数显示菜单。
(8)、finish_recovery(),
// clear the recovery command and prepare to boot a (hopefully working) system,// copy our log file to cache as well (for the system to read), and// record any intent we were asked to communicate back to the system.// this function is idempotent: call it as many times as you like.static voidfinish_recovery(const char *send_intent) { // By this point, we're ready to return to the main system... if (send_intent != NULL) { FILE *fp = fopen_path(INTENT_FILE, "w");// /cache/recovery/intent if (fp == NULL) { LOGE("Can't open %s\n", INTENT_FILE); } else { fputs(send_intent, fp);//向指定的文件写入一个字符串:将send_intent写入到intent文件中 check_and_fclose(fp, INTENT_FILE); } } // Save the locale to cache, so if recovery is next started up // without a --locale argument (eg, directly from the bootloader) // it will use the last-known locale. if (locale != NULL) { LOGI("Saving locale \"%s\"\n", locale); FILE* fp = fopen_path(LOCALE_FILE, "w");// /cache/recovery/last_local fwrite(locale, 1, strlen(locale), fp); fflush(fp); fsync(fileno(fp)); check_and_fclose(fp, LOCALE_FILE); } copy_logs(); // Reset to normal system boot so recovery won't cycle indefinitely. struct bootloader_message boot; memset(&boot, 0, sizeof(boot));//将boot变量清0 set_bootloader_message(&boot); // Remove the command file, so recovery won't repeat indefinitely. if (ensure_path_mounted(COMMAND_FILE) != 0 || (unlink(COMMAND_FILE) && errno != ENOENT)) { LOGW("Can't unlink %s\n", COMMAND_FILE); } ensure_path_unmounted(CACHE_ROOT); sync(); // For good measure.}A、将intent的内容作为参数传进finish_recovery中,如果有intent需要告知Main system,则将其写入/cache/recovery/intent文件中。
B、将local参数写入到/cache/recovery/last_local文件中。
C、copy_logs():将内存文件系统中的Recovery服务的日志/tmp/recovery.log追加到/cache/recovery/log中,并且拷贝到/cache/recovery/last_log中,将/tmp/last_install拷贝到/cache/recovery/last_install中;保存内核日志到/cache/recovery/last_kmsg文件中。
D、擦除MISC分区中的BCB数据块的内容,以便系统重启后不再进入Recovery模式,而是进入Main system。
E、删除/cache/recovery/command文件。因为重启后Bootloader会自动检索这个文件,如果没有删除又会进入Recovery模式。
finish_recovery()函数的处理流程如下:
(8)、退出Recovery。根据参数,选择关闭或重启系统。
2、执行菜单命令
我们看下Recovery是如何执行菜单命令的。prompt_and_wait()函数用来打印屏幕菜单并接收用户输入,当在main函数中执行命令失败并且IsTextVisible显示菜单时,调用prompt_and_wait函数。代码如下:
// Return REBOOT, SHUTDOWN, or REBOOT_BOOTLOADER. Returning NO_ACTION// means to take the default, which is to reboot or shutdown depending// on if the --shutdown_after flag was passed to recovery.如果返回NO_ACTION表示采取由传递进来的--shutdown_after标记决定的默认动作static Device::BuiltinActionprompt_and_wait(Device* device, int status) { const char* const* headers = prepend_title(device->GetMenuHeaders()); for (;;) { finish_recovery(NULL); switch (status) {//根据命令的执行情况,改变UI的背景 case INSTALL_SUCCESS: case INSTALL_NONE: ui->SetBackground(RecoveryUI::NO_COMMAND); break; case INSTALL_ERROR: case INSTALL_CORRUPT: ui->SetBackground(RecoveryUI::ERROR); break; } ui->SetProgressType(RecoveryUI::EMPTY);//设置升级进度 int chosen_item = get_menu_selection(headers, device->GetMenuItems(), 0, 0, device);//系统将在get_menu_selection函数中等待用户输入 // device-specific code may take some action here. It may // return one of the core actions handled in the switch // statement below. Device::BuiltinAction chosen_action = device->InvokeMenuItem(chosen_item);//将用户的选择先交给Device对象处理 int wipe_cache = 0; switch (chosen_action) { case Device::NO_ACTION: break; case Device::REBOOT: case Device::SHUTDOWN: case Device::REBOOT_BOOTLOADER: return chosen_action; case Device::WIPE_DATA://擦除数据区 wipe_data(ui->IsTextVisible(), device); if (!ui->IsTextVisible()) return Device::NO_ACTION; break; case Device::WIPE_CACHE://擦除cache分区 ui->Print("\n-- Wiping cache...\n"); erase_volume("/cache"); ui->Print("Cache wipe complete.\n"); if (!ui->IsTextVisible()) return Device::NO_ACTION; break; case Device::APPLY_EXT: {//从SD卡上更新 ensure_path_mounted(SDCARD_ROOT);//"sdcard" char* path = browse_directory(SDCARD_ROOT, device); if (path == NULL) { ui->Print("\n-- No package file selected.\n", path); break; } ui->Print("\n-- Install %s ...\n", path); set_sdcard_update_bootloader_message(); void* token = start_sdcard_fuse(path); int status = install_package(FUSE_SIDELOAD_HOST_PATHNAME, &wipe_cache, TEMPORARY_INSTALL_FILE, false); finish_sdcard_fuse(token); ensure_path_unmounted(SDCARD_ROOT); if (status == INSTALL_SUCCESS && wipe_cache) { ui->Print("\n-- Wiping cache (at package request)...\n"); if (erase_volume("/cache")) { ui->Print("Cache wipe failed.\n"); } else { ui->Print("Cache wipe complete.\n"); } } if (status >= 0) { if (status != INSTALL_SUCCESS) { ui->SetBackground(RecoveryUI::ERROR); ui->Print("Installation aborted.\n"); } else if (!ui->IsTextVisible()) { return Device::NO_ACTION; // reboot if logs aren't visible } else { ui->Print("\nInstall from sdcard complete.\n"); } } break; } case Device::APPLY_CACHE://从cache中更新,已经被否决 ui->Print("\nAPPLY_CACHE is deprecated.\n"); break; case Device::READ_RECOVERY_LASTLOG: choose_recovery_file(device); break; case Device::APPLY_ADB_SIDELOAD://启动adbd,主要是让用户通过adb连接来执行sideload命令上传,更新文件到/tmp/update.zip,执行更新操作。 status = apply_from_adb(ui, &wipe_cache, TEMPORARY_INSTALL_FILE); if (status >= 0) { if (status != INSTALL_SUCCESS) { ui->SetBackground(RecoveryUI::ERROR); ui->Print("Installation aborted.\n"); copy_logs(); } else if (!ui->IsTextVisible()) { return Device::NO_ACTION; // reboot if logs aren't visible } else { ui->Print("\nInstall from ADB complete.\n"); } } break; } }}prompt_and_wait()函数的逻辑是,在屏幕上打印出菜单,然后等待用户按键输入,根据输入命令进行处理。
- Android--Recovery模块之恢复出厂设置
- Android 恢复出厂设置(recovery)
- Android 恢复出厂设置(recovery)
- Android N恢复出厂设置进入Recovery
- android-恢复出厂设置
- android-恢复出厂设置
- Android恢复出厂设置
- Android恢复出厂设置
- android恢复出厂设置
- Android恢复出厂设置
- android初学------系统设置之恢复出厂设置
- android 恢复出厂设置流程
- android 恢复出厂设置流程
- android恢复出厂设置流程
- android 恢复出厂设置流程
- Android 8.0恢复出厂设置
- android恢复出厂设置以及系统升级流程
- android恢复出厂设置以及系统升级流程
- 当打开vim没有sudo又想保存时
- 陈怡暖:黄金短期或陷入震荡,关注1184支撑
- 社説 20150528 ストレス検査 職場環境の改善につなげたい
- Swift开发Sprite Kit游戏实践(三):物理推力与碰撞检测
- Linux常用操作命令
- Android--Recovery模块之恢复出厂设置
- 欢迎使用CSDN-markdown编辑器
- MATLAB学习笔记(八)
- Cordova插件开发3步骤
- 添加Android Support V7
- C++实现http下载 && 24点计算编码风格
- javascript模板引擎 Arttemplate
- 01-复杂度1. 最大子列和问题(20)
- 在一个IP地址上运行多个基于域名的web站 点