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()函数的逻辑是,在屏幕上打印出菜单,然后等待用户按键输入,根据输入命令进行处理。
    









0 0
原创粉丝点击