前言
大部分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系统实战开发】,关注有惊喜哦!