Android系统升级流程 您所在的位置:网站首页 如何升安卓版本 Android系统升级流程

Android系统升级流程

2023-10-24 15:27| 来源: 网络整理| 查看: 265

前言

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



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有