Android系统升级流程---上篇

前言

大部分Android设备出厂时的软件大都是带着bug风险(低风险)出货的,后期再通过OTA的方式去升级修订补丁。在满足主要功能正常使用的情况下产品抢先出货,其他小功能再通过迭代更新。这个功能的重要性不言而喻。今天就来看看Android系统的升级流程。

概述

一般Android升级流程是,由软件发放端推送软件到服务器,然后由服务器向Android设备推送升级包。在Android设备中,一般会有一个系统服务用于检测是否有版本更新,如果有更新包,则下载下来,下载完成并校验成功后,通过调用系统的接口进入升级流程。本文就是从调用这个接口开始的。

我们先来看看升级服务下载好安装包后,调用了什么接口进行升级:

RecoverySystem.installPackage(context, file);

installPackage的实现如下:

/frameworks/base/core/java/android/os/RecoverySystem.java
@SystemApi
@RequiresPermission(android.Manifest.permission.RECOVERY)
public static void installPackage(Context context, File packageFile, boolean processed)
        throws IOException {
    synchronized (sRequestLock) {
        LOG_FILE.delete();
        // Must delete the file in case it was created by system server.
        UNCRYPT_PACKAGE_FILE.delete();  //先删除"/cache/recovery/uncrypt_file"

        String filename = packageFile.getCanonicalPath();  //获取绝对路径
        Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!");

        // If the package name ends with "_s.zip", it's a security update.
        boolean securityUpdate = filename.endsWith("_s.zip");  //必须以zip结尾

        // If the package is on the /data partition, the package needs to
        // be processed (i.e. uncrypt'd). The caller specifies if that has
        // been done in 'processed' parameter.
        if (filename.startsWith("/data/")) {
            if (processed) {
                if (!BLOCK_MAP_FILE.exists()) {
                    Log.e(TAG, "Package claimed to have been processed but failed to find "
                            + "the block map file.");
                    throw new IOException("Failed to find block map file");
                }
            } else {
                FileWriter uncryptFile = new FileWriter(UNCRYPT_PACKAGE_FILE);
                try {
                    uncryptFile.write(filename + "\n");
                } finally {
                    uncryptFile.close();
                }
                // UNCRYPT_PACKAGE_FILE needs to be readable and writable
                // by system server.
                if (!UNCRYPT_PACKAGE_FILE.setReadable(true, false)
                        || !UNCRYPT_PACKAGE_FILE.setWritable(true, false)) {
                    Log.e(TAG, "Error setting permission for " + UNCRYPT_PACKAGE_FILE);
                }

                BLOCK_MAP_FILE.delete();
            }

            // If the package is on the /data partition, use the block map
            // file as the package name instead.
            filename = "@/cache/recovery/block.map";
        }

        final String filenameArg = "--update_package=" + filename + "\n";
        final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() + "\n";
        final String securityArg = "--security\n";

        String command = filenameArg + localeArg;
        if (securityUpdate) {
            command += securityArg;
        }

        RecoverySystem rs = (RecoverySystem) context.getSystemService(
                Context.RECOVERY_SERVICE);
        if (!rs.setupBcb(command)) {
            throw new IOException("Setup BCB failed");
        }

        // Having set up the BCB (bootloader control block), go ahead and reboot
        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        String reason = PowerManager.REBOOT_RECOVERY_UPDATE;

        // On TV, reboot quiescently if the screen is off
        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
            WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
            if (wm.getDefaultDisplay().getState() != Display.STATE_ON) {
                reason += ",quiescent";
            }
        }
        pm.reboot(reason);

        throw new IOException("Reboot failed (no permissions?)");
    }
}

如上方法中主要做的事如下:

1.判断升级包是否在data分区,如果在data分区,因为data分区加密过了,recovery并不能直接从data分区中获取升级包,需要进行解析,具体解析放到了processPackage方法中,下面会说到;
2.调用setupBcb,将升级包的路径以及升级指令写入到BCB中,系统重启的时候,就会去这个地方检测是否有recovery标志,如有,则进入recovery模式,并触发升级,但此时如果没找到升级包,则会出现error,且系统马上重启;
3.调用pm服务,进行reboot;

该方法向系统提出了升级的需求,并告知了升级包的路径,接下来就是进入到了PowerManagerService的reboot流程,注意这里调用到的reason,它的值是REBOOT_RECOVERY_UPDATE,我们接下来分析的时候会用到。先进入PowerManager:

/frameworks/base/core/java/android/os/PowerManager.java
* @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.
public void reboot(String reason) {
    try {
        mService.reboot(false, reason, true);
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

这里的代码很简单,直接调用电源管理服务的reboot接口进入到下一个流程,需要注意的是此时传入的参数,第一个参数为false,表示不需要弹窗确认,第三个参数为true表示一直等待直到关机流程完成。

mService是一个PowerManagerService,方法实现如下:

/frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
public void reboot(boolean confirm, String reason, boolean wait) {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null);
        if (PowerManager.REBOOT_RECOVERY.equals(reason)
                || PowerManager.REBOOT_RECOVERY_UPDATE.equals(reason)) {
            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
        }

        final long ident = Binder.clearCallingIdentity();
        try {
            shutdownOrRebootInternal(HALT_MODE_REBOOT, confirm, reason, wait);
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
}

这里针对进入Recovery模式进行了权限检测,调用者需具备permission.RECOVERY权限。之后就进入到了shutdownOrRebootInternal方法中:

private void shutdownOrRebootInternal(final @HaltMode int haltMode, final boolean confirm,
        final String reason, boolean wait) {
        
    ....
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            synchronized (this) {
                if (haltMode == HALT_MODE_REBOOT_SAFE_MODE) {
                    ShutdownThread.rebootSafeMode(getUiContext(), confirm);
                } else if (haltMode == HALT_MODE_REBOOT) {
                    ShutdownThread.reboot(getUiContext(), reason, confirm);
                } else {
                    ShutdownThread.shutdown(getUiContext(), reason, confirm);
                }
            }
        }
    };
    
    // ShutdownThread must run on a looper capable of displaying the UI.
    Message msg = Message.obtain(UiThread.getHandler(), runnable);
    msg.setAsynchronous(true);
    UiThread.getHandler().sendMessage(msg);
    ....
}

这里创建了一个线程,调用ShutdownThread类的reboot方法继续跑重启流程。对于这里创建新线程的目的,ShutdownThread必须在能够显示UI的循环程序上运行。在这里面,需要弹窗显示升级流程或者关机流程等。我们继续往下看:

/frameworks/base/services/core/java/com/android/server/power/ShutdownThread.java
public static void reboot(final Context context, String reason, boolean confirm) {
    mReboot = true;
    mRebootSafeMode = false;
    mRebootHasProgressBar = false;
    mReason = reason;
    shutdownInner(context, confirm);
}

这里代码简单,不做解释,继续往下看:

private static void shutdownInner(final Context context, boolean confirm) {

    // ShutdownThread is called from many places, so best to verify here that the context passed
    // in is themed.
    context.assertRuntimeOverlayThemable();

    // 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;
        }
    }
    
    final int longPressBehavior = context.getResources().getInteger(
                    com.android.internal.R.integer.config_longPressOnPowerBehavior);
    final 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);

    Log.d(TAG, "Notifying thread to start shutdown longPressBehavior=" + longPressBehavior);

    if (confirm) {
        final CloseDialogReceiver closer = new CloseDialogReceiver(context);
        if (sConfirmDialog != null) {
            sConfirmDialog.dismiss();
        }
        sConfirmDialog = new AlertDialog.Builder(context)
                .setTitle(mRebootSafeMode
                        ? com.android.internal.R.string.reboot_safemode_title
                        : com.android.internal.R.string.power_off)
                .setMessage(resourceId)
                .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        beginShutdownSequence(context);
                    }
                })
                .setNegativeButton(com.android.internal.R.string.no, null)
                .create();
        closer.dialog = sConfirmDialog;
        sConfirmDialog.setOnDismissListener(closer);
        sConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
        sConfirmDialog.show();
    } else {
        beginShutdownSequence(context);
    }
}

这里主要做了如下事情:

1.确认上下文环境是否符合要求,如果调用到了系统资源则会抛出异常;
2.判断是否有多方调用关机流程,如果有则直接返回;
3.获取用户关机 Behavior。
4.如果需要显示关机弹窗,则创建各类资源进行弹窗,然后根据用户选择进入不同的流程;
5.如果不需要关机弹窗,则直接进入关机流程;

是否需要弹窗,则由所传入的参数confirm所决定,confirm在PowerManager中传入,传入的值为false,所以这里我们就直接进入关机流程,不创建确认弹窗:

private static void beginShutdownSequence(Context context) {

    synchronized (sIsStartedGuard) {
        if (sIsStarted) {
            Log.d(TAG, "Shutdown sequence already running, returning.");
            return;
        }
        sIsStarted = true;
    }

    sInstance.mProgressDialog = showShutdownDialog(context);
    sInstance.mContext = context;
    sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);

    // make sure we never fall asleep again
    sInstance.mCpuWakeLock = null;
    try {
        sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock(
                PowerManager.PARTIAL_WAKE_LOCK, TAG + "-cpu");
        sInstance.mCpuWakeLock.setReferenceCounted(false);
        sInstance.mCpuWakeLock.acquire();
    } catch (SecurityException e) {
        Log.w(TAG, "No permission to acquire wake lock", e);
        sInstance.mCpuWakeLock = null;
    }

    // also make sure the screen stays on for better user experience
    sInstance.mScreenWakeLock = null;
    if (sInstance.mPowerManager.isScreenOn()) {
        try {
            sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock(
                    PowerManager.FULL_WAKE_LOCK, TAG + "-screen");
            sInstance.mScreenWakeLock.setReferenceCounted(false);
            sInstance.mScreenWakeLock.acquire();
        } catch (SecurityException e) {
            Log.w(TAG, "No permission to acquire wake lock", e);
            sInstance.mScreenWakeLock = null;
        }
    }

    if (SecurityLog.isLoggingEnabled()) {
        SecurityLog.writeEvent(SecurityLog.TAG_OS_SHUTDOWN);
    }

    // start the thread that initiates shutdown
    sInstance.mHandler = new Handler() {
    };
    sInstance.start();
}

beginShutdownSequence方法中,先是判断是否已经开始关机,如果是,则直接返回,避免多个关机case。然后拿锁,避免系统休眠,并且保持屏幕常亮。最后调用start开启关机线程:

sInstance.start();

ShutdownThread本身就是继承Thread的线程,我们看看它的run实现:

public void run() {

    ....
    if (mRebootHasProgressBar) {
        sInstance.setRebootProgress(MOUNT_SERVICE_STOP_PERCENT, null);

        // If it's to reboot to install an update and uncrypt hasn't been
        // done yet, trigger it now.
        uncrypt();
    }

    shutdownTimingLog.traceEnd(); // SystemServerShutdown
    metricEnded(METRIC_SYSTEM_SERVER);
    saveMetrics(mReboot, mReason);
    // Remaining work will be done by init, including vold shutdown
    rebootOrShutdown(mContext, mReboot, mReason);
}

前面代码量太多,不详细列出,简单介绍下这个线程做的事情:

1.发送关机广播;
2.调用ActivityManager的shutdown方法,关闭activity管理器;
3.调用PackageManagerService的shutdown方法,关闭包管理器;
4.关闭radios;
5.如果是升级而触发的重启,则通过调用uncrypt方法解析升级包;
6.调用rebootOrShutdown方法继续关机流程;

接下来的rebootOrShutdown实现如下:

public static void rebootOrShutdown(final Context context, boolean reboot, String reason) {

    if (reboot) {
        Log.i(TAG, "Rebooting, reason: " + reason);
        PowerManagerService.lowLevelReboot(reason);
        Log.e(TAG, "Reboot failed, will attempt shutdown instead");
        reason = null;
    } else if (SHUTDOWN_VIBRATE_MS > 0 && context != null) {
        ...
    }
    
    ...
}

我们这里跑的分支是reboot,调用PowerManagerService的lowLevelReboot方法:

public static void lowLevelReboot(String reason) {
    ...
    if (reason.equals(PowerManager.REBOOT_QUIESCENT)) {
        sQuiescent = true;
        reason = "";
    } else if (reason.endsWith("," + PowerManager.REBOOT_QUIESCENT)) {
        sQuiescent = true;
        reason = reason.substring(0,
                reason.length() - PowerManager.REBOOT_QUIESCENT.length() - 1);
    }

    if (reason.equals(PowerManager.REBOOT_RECOVERY)
            || reason.equals(PowerManager.REBOOT_RECOVERY_UPDATE)) {
        reason = "recovery";
    }

    if (sQuiescent) {
        // Pass the optional "quiescent" argument to the bootloader to let it know
        // that it should not turn the screen/lights on.
        reason = reason + ",quiescent";
    }

    SystemProperties.set("sys.powerctl", "reboot," + reason);
    try {
        Thread.sleep(20 * 1000L);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
    Slog.wtf(TAG, "Unexpected return from lowLevelReboot!");
}

这里想列出这个方法,主要是看到了一个静默重启的配置,想拿出来讲讲,之前以为Android没做这个,还曾经自己实现了。没想到Android本身已经有实现。主要是通过reason值传给bootloader,bootloader再根据该值来决定要不要亮屏。如果reason是recovery相关,则赋值reason为【reason】,然后通过属性服务启动reboot。

结语

本文大概分析了从升级服务调用了installPackage方法后发生的一系列的事。阅读源码果然是一条让人了解Android的最佳途径,在本次的源码阅读中,有一个意外收货就是静默重启,记得Android 5.0 没有这个功能的。后面再遇到类似的静默重启需求就可以直接用上了。

再者,本来此次是要写关于Android升级流程的文章,没想到写着写着就变成了Android的关机流程了。这里就当做Android升级流程的上篇吧,下篇再写关于Android重启进入recovery时所发生的事。

整个流程分析下来,竟不知如何分小标题,感觉在哪里拆分都会破坏流程的流畅性。这里就不加小标题了。

最后

我在微信公众号也有写文章,更新比较及时,有兴趣者可以扫描如下二维码,或者微信搜索【Android系统实战开发】,关注有惊喜哦!
在这里插入图片描述

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值