PKMS启动详解(七)之BOOT_PROGRESS_PMS_SYSTEM_SCAN_START阶段流程分析

Android PackageManagerService系列博客目录:

PKMS启动详解系列博客概要
PKMS启动详解(一)之整体流程分析
PKMS启动详解(二)之怎么通过packages.xml对已安装应用信息进行持久化管理?
PKMS启动详解(三)之BOOT_PROGRESS_PMS_START流程分析
PKMS启动详解(四)之Android包信息体和包解析器(上)
PKMS启动详解(五)之Android包信息体和包解析器(中)
PKMS启动详解(六)之Android包信息体和包解析器(下)
PKMS启动详解(七)之BOOT_PROGRESS_PMS_SYSTEM_SCAN_START阶段流程分析
PKMS启动详解(八)之BOOT_PROGRESS_PMS_DATA_SCAN_START阶段流程分析



本篇博客编写思路总结和关键点说明:

为了更加方便的读者阅读博客,通过导读思维图的形式将本博客的关键点列举出来,从而方便读者取舍和阅读!


引言

  欢迎回来继续PKMS启动详解之旅,在上一期的博客PKMS启动详解(三)之BOOT_PROGRESS_PMS_START启动流程分析中我们重点分析了PKMS服务启动的BOOT_PROGRESS_PMS_START的相关核心步骤和事宜!也许相信的读者发现了为啥篇章序列号从(三)一下子跳转到(七)了,这是什么鬼逻辑呢?其实从最上面的Android PKMS系列博客目录可以看出,在这其中我们插入了三篇关于Android包信息体和Android包解析器的篇章,通过这三篇博客我们掌握了如下的几个关键知识点:

  • 包管理机制设计者如何从架构层出发设计Android包信息体PackageInfo

  • 然后从实际源码角度出发,分析了包信息体PackageInfo的具体设计实现

  • 接着从包管理机制设计者意图了解PackageParser包解析器,即它的概述和定义是什么

  • 再接着PackageParser包解析器作为一个工具类,它的核心成员变量是什么(当然是和包信息体的封装有关)

  • 最后PackageParser包解析器作为一个工具类,它的核心方法有那些(核心方法就是解析Android包)

这其中插入的三篇,真的不是我临时见色起意,或者忘恩负义随意添加的!我这么做的一切一切的原因都是为了读者能更好的理解PKMS启动的BOOT_PROGRESS_PMS_SYSTEM_SCAN_START流程!

掌握了上述的相关知识点后,对于今天博客要分析的PKMS启动BOOT_PROGRESS_PMS_SYSTEM_SCAN_START阶段流程读者朋友应该理解起来就会更加的得心应手了!因为我们从英文缩写就可以看出这阶段的核心流程就是扫描系统应用Android目录,而扫描的核心诉求点就是将静态的安装包文件(主要是APK文件)解析扫描为一定的数据结构供PKMS进行对应的管理,而这个数据结构就是包信息体PackageInfo的具体载体Package。此时的读者是不是已经非常好奇PKMS是怎么扫描系统安装目录的呢?不用着急,让我们一起来开启这段奇妙的探索之旅,相信经过我们的一起努力BOOT_PROGRESS_PMS_SYSTEM_SCAN_START阶段会很轻松的拿下的。

注意:本篇的介绍是基于Android 7.xx平台为基础的(并且为了书写简便后续PackageManagerService统一简称为PKMS),其中涉及的代码路径如下:

--- frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.javaPackageSetting.java Settings.javaPackageInstallerService.java--- frameworks/base/core/java/android/content/pm/PackageParser.java--- frameworks/native/cmds/installd/install.cpp

一.储备知识提前亮

  为了使读者能更加畅快的理解BOOT_PROGRESS_PMS_SYSTEM_SCAN_START的相关逻辑,在正式开始分析前,我们十分有必要且必须的将本篇博客将要牵涉的一些重点知识点提前摆出来放在明面上让读者提前理解和掌握,这样才能是大家对于后续的逻辑切入才能更加的顺畅!

如果读者觉得,你的预备知识弹药库已经足够,请绕过此处,直接进入第二章节!此处不值得你留恋,爷请回!

1.1 PMKS中几个重要变量的理解

不要问我为啥没有将这几个重要的变量在最最前面的博客中放出来!

因为我在前面放出来,没有使用到读者也不会注意到,反而会增加读者的负担,在此处我不得不放出来了,因为在此阶段我们真正的要接触并使用到了

好了啥也不多说了,直接上变量,然后来理解!

// 【 PackageManagerService.java 】
public class PackageManagerService extends IPackageManager.Stub {...ApplicationInfo mAndroidApplication;final ActivityInfo mResolveActivity = new ActivityInfo();final ResolveInfo mResolveInfo = new ResolveInfo();ComponentName mResolveComponentName;PackageParser.Package mPlatformPackage;ComponentName mCustomResolverComponentName;...
}

在理解上述变量之前,我们有必要这里先说一下/system/framework/framework-res.apk,它是一个个非常非常特殊的apk,它的包名为"android"。这个APK里面有两个常用且常用的Acitvity:

  • ChooserActivity: (当多个Activity符合某个Intent的时候,系统会弹出的Activity)和
  • ShutdownActivity: (长按电源键关机时,弹出的Activity)

好了理解清楚了上述framework-res.apk,对于上述变量的理解就很展开了,听我一一道来:

  • 由于framework-res.apk在PKMS中的特殊性,所以PMKS中特地使用mPlatformPackage用于保存该Package信息,同时使用成员变量mAndroidApplication用于保存此Package中的ApplicationInfo

    特权出身的人就是不一样

  • mResolveActivity: 指向用于表示ChooserActivity信息的ActivityInfo,因为ChooserActivity使用地方很多,因此PackageManagerService在此处保存这些信息,以较高运行过程中的效率

  • mResolveInfo为ResolveInfo类型,它用于存储系统解析Intent(经IntentFilter过滤)得到的结果信息,例如满足某个Intnet的Activity信息

    ResolveInfo是最终的Intent解析结果的数据结构,并不复杂,就是各类组件信息的一个包装。需要注意的是,其中的组件信息ActivityInfo、ProviderInfo、ServiceInfo只有一个不为空,这样就可以区分不同组件的解析结果

  • mResolveComponentName:指向我们默认的mResolveActivity的ComponentName信息

  • mCustomResolverComponentName:此处指向客制化的ResolveActivity的ComponentName

1.2 Native层的installd

PKMS服务涉及的应用安装,扫描都不是凭一己之力可以完成的,所以引入了/system/bin/installd用来处理一些Android应用包安装过程中涉及的数据目录的创建,dex优化等等相关的工作,并且它和PKMS所在进程system_server是通过LocalSocket进行通信的。至于为啥system_server为啥不自己干,而是将其委托给installd个人认为有两方面原因:

  • 执行效率问题
  • 权限问题

关于installd暂时不是我们研究的重点,我们先理解到这里就OK了!感兴趣的可以参见博客Android装配服务installd源码分析和installd守护进程!PKMS和installd的交互可以使用如下的图示来表示:

1.3 PKMS.mPackages和Settings.mPackages成员变量

关于这两个变量它们的名称非常类似,但是二者之间的意义却是截然不同的。并且二者对于理解清楚PKMS非常重要,所以这里有必要对其了解一下!

1.3.1 PKMS.mPackages

首先让我们看下它的定义,如下:

// 【 PackageManagerService.java 】
public class PackageManagerService extends IPackageManager.Stub {.../*以Android包的包名为key,以Pakcage为value的哈希表*/@GuardedBy("mPackages")final ArrayMap<String, PackageParser.Package> mPackages =new ArrayMap<String, PackageParser.Package>();    ...
}

当Android包被PKMS成功扫描后会创建一个Package数据结构来管理Android包信息,而我们知道PKMS在扫描过程中会创建许多个这种Package,所以PKMS需要有一个数据结构来管理它,此时mPackages对象就应用而生了。所以当一个APK顺利通过扫描过程之后,其Package对象便会被添加到mPackages这个映射表中。所以导致在PKMS的很多方法里面有很多"mPackages的锁",因为需要对其进行操作,必须上锁。

1.3.2 Settings.mPackages

其实关于Settings.mPackages我们在之前的博客<>PKMS启动详解(三)之BOOT_PROGRESS_PMS_START流程分析中已经有详细的介绍过了,这里简单的一笔带过!

首先让我们看下它的定义,如下:

final class Settings {.../** Map from package name to settings *///被安装应用的包名为key,以安装应用相关信息为valuefinal ArrayMap<String, PackageSetting> mPackages = new ArrayMap<>();...

该变量以<String, PackageSetting>键值对的形式存在,每次开机PKMS服务初始化时会从/data/system/packages.xml中恢复出Android包安装的相关的信息到Settings.mPackages中。然后PKMS在扫描相关安装包目录时会通过此mPackages中的信息判断新扫出来的安装包Package是否合法,是否有效是否能覆盖升级等。

1.4 系统APP升级的方式

由于在后续的PKMS处理系统应用安装目录扫描时涉及到系统应用升级逻辑的处理,所以这里必须对系统APP的升级方式和逻辑简单概括一下。对于一个系统APP而言,它的升级有如下两种途径:

  • 覆盖安装:这种方式PMS会动态修改到packages.xml 文件,即升级结果记录下来

    要怎么理解这种升级方式呢,这种就是比较常见的通常是应用商店或者其它途径升级预置的系统应用到高版本,譬如小米预置了1.0版本小米便签,然后由于工程师996的不断攻关添加了许多功能,上线了新版本,用户通过应用市场覆盖升级小米便签到2.0版本。这就是一个典型的覆盖安装的使用场景!

  • 通过更新Android固件然后更新系统应用的OTA升级方式:这种方式方式,packages.xml中不会记录到相关信息,需要PKMS进行扫描时候才会被处理

下面我们对两种方式简单梳理一下:

1.4.1 覆盖安装

对于系统APP,覆盖安装的话,新的APP会安装到data分区,然后packages.xml中相应数据会发生如下变化。这里我们以系统应用/system/app/XXX为例

 <!-- 覆盖安装到data目录下的应用信息 --><package name="com.xxx.xxx" codePath="/data/app/com.xxx.xxx-1" nativeLibraryPath="/data/app/com.xxx.xxx-1/lib" primaryCpuAbi="armeabi-v7a" publicFlags="940097221" privateFlags="0" ft="177d2d079b0" it="177c7dd5c08" ut="177d2d0815f" version="12" versionName="1.0.10_20210127" applicationName="com.xxx.xxx.XXX" sharedUserId="1000" isOrphaned="true"><sigs count="1"><cert index="1" /></sigs><perms><item name="android.permission.BIND_INCALL_SERVICE" granted="true" flags="0" /><item name="android.permission.WRITE_SETTINGS" granted="true" flags="0" />...</perms><proper-signing-keyset identifier="1" /></package><!-- 被覆盖安装的系统应用 --><updated-package name="com.xxx.xxx" codePath="/system/app/XXX" ft="177c7dd5c08" it="177c7dd5c08" ut="177c7dd5c08" version="11" nativeLibraryPath="/system/app/XXX/lib" primaryCpuAbi="armeabi-v7a" sharedUserId="1000" />

可以看到,package的codePath、nativeLibraryPath都发生了变化,根据上面的解析过程,data分区的覆盖安装的APK将被保存到PKMS.mPackages中,system分区原来的旧APK将被保存到PKMS.mDisablePackage中。

1.4.2 通过Android固件更新的OTA升级方式

这个要怎么理解了,譬如我们的Android大版本固件从1.0升级到2.0,然后对其中的内置应用将版本从1.XXX的版本升级到2.XXX版本。

这种方式是通过进入Recovery升级(或者是 A/B 升级方式),通过patch和文件覆盖方式来升级,这种方式不会即时更改packages.xml文件的信息,真正的修改是在OTA成功之后重启终端后的在PKMS的扫描中去做的。


二.系统应用安装目录扫描前期准备阶段

怎么感觉这个标题这么拗口呢!在BOOT_PROGRESS_PMS_SYSTEM_SCAN_START阶段,牵涉的逻辑并不是非常的复杂,但是其源码涉及的细节却是非常非常的多繁琐(注释扫描安装目录相关应用),所以这里为了篇幅的排版和美观我会将BOOT_PROGRESS_PMS_SYSTEM_SCAN_START阶段又划分为三个小阶段(这里是我人为划分的,实际上并不存在这三个阶段之说):
1.前期

2.高潮(不是那个不要想歪了,这里只是暂时没有找到比较好的词来描述而已的替代词)

3.结尾
等三个小阶段来对上述流程展开分析。

并且我们还需要重点关注的是,PKMS在启动扫描系统目录阶段第一次开机运行和后续开机运行执行的相关逻辑是有所差别的,这个读者一定要心里有底。因为绝大部分的博客并没有强调这一方面,这个需要读者引起注意。

知识储备,磨刀砍柴阶段结束了,至此我们要正式开始第一小阶段的分析了(当然了这个阶段也是一个开胃菜而已,重点的重点在于高潮阶段)。由于BOOT_PROGRESS_PMS_SYSTEM_SCAN_START源码篇幅很长,我们先来个总体的阶段的概括:

// 【 PackageManagerService.java 】
public class PackageManagerService extends IPackageManager.Stub {public PackageManagerService(Context context, Installer installer,boolean factoryTest, boolean onlyCore) {/************************* PKMS启动BOOT_PROGRESS_PMS_START阶段 *************************///写入日志,标识PackageManagerService正式开始第一阶段的启动EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START,SystemClock.uptimeMillis());...//获取扫描开始的时间long startTime = SystemClock.uptimeMillis();/************************* PKMS启动BOOT_PROGRESS_PMS_SYSTEM_SCAN_START阶段 *************************///写入开始扫描系统应用的日志EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SYSTEM_SCAN_START,startTime);/************************* 前期阶段 *************************///设置扫描的参数final int scanFlags = SCAN_NO_PATHS | SCAN_DEFER_DEX | SCAN_BOOTING | SCAN_INITIAL;// 获取Java启动类库的路径,可以通过echo $BOOTCLASSPATH查看final String bootClassPath = System.getenv("BOOTCLASSPATH");// 获取systemServer的路径final String systemServerClassPath = System.getenv("SYSTEMSERVERCLASSPATH");if (bootClassPath == null) {Slog.w(TAG, "No BOOTCLASSPATH found!");}if (systemServerClassPath == null) {Slog.w(TAG, "No SYSTEMSERVERCLASSPATH found!");}//活得系统指令集合final List<String> allInstructionSets = InstructionSets.getAllInstructionSets();final String[] dexCodeInstructionSets =getDexCodeInstructionSets(allInstructionSets.toArray(new String[allInstructionSets.size()]));// 确保所有的共享库都被dexopt优化,详见章节 【 2.1 】if (mSharedLibraries.size() > 0) {for (String dexCodeInstructionSet : dexCodeInstructionSets) {for (SharedLibraryEntry libEntry : mSharedLibraries.values()) {final String lib = libEntry.path;if (lib == null) {continue;}try {//判断共享库是否需要执行odex操作int dexoptNeeded = DexFile.getDexOptNeeded(lib, dexCodeInstructionSet,getCompilerFilterForReason(REASON_SHARED_APK),false /* newProfile */);// 如果需要odex操作,对共享库进行一次预编译(AOT)if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {// 调用install的dexopt命令,优化后的文件放在/data/dalvik-cache/下面mInstaller.dexopt(lib, Process.SYSTEM_UID, dexCodeInstructionSet,dexoptNeeded, DEXOPT_PUBLIC /*dexFlags*/,getCompilerFilterForReason(REASON_SHARED_APK),StorageManager.UUID_PRIVATE_INTERNAL,SKIP_SHARED_LIBRARY_CHECK);}} catch (FileNotFoundException e) {Slog.w(TAG, "Library not found: " + lib);} catch (IOException | InstallerException e) {Slog.w(TAG, "Cannot dexopt " + lib + "; is it an APK or JAR? "+ e.getMessage());}}}}File frameworkDir = new File(Environment.getRootDirectory(), "framework");//获取系统版本信息final VersionInfo ver = mSettings.getInternalVersion();// 判断是否是OTA升级,如果当前版本的指纹与历史版本的指纹信息不一致,表示当前版本是一次OTA升级上来更新版本!mIsUpgrade = !Build.FINGERPRINT.equals(ver.fingerprint);/*对于旧版本升级的情况,将安装时获取权限变更为运行时申请权限对于Android M之前版本升级上来的情况,需要将系统应用程序权限从安装升级到运行时*/mPromoteSystemApps =mIsUpgrade && ver.sdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1;// 对于Android N之前版本升级上来的情况,需像首次启动一样处理PackagemIsPreNUpgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.N;mIsPreNMR1Upgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.N_MR1;// 在扫描之前保存现有系统软件包的名称;我们不希望自动授予新系统应用程序的运行时权限// 保存从Android 6.0升级前已经存在的系统应用// 扫描过程会将安装时权限变为运行时权限!if (mPromoteSystemApps) {Iterator<PackageSetting> pkgSettingIter = mSettings.mPackages.values().iterator();while (pkgSettingIter.hasNext()) {PackageSetting ps = pkgSettingIter.next();if (isSystemApp(ps)) {mExistingSystemPackages.add(ps.name);}}}/************************* 高潮阶段 *************************/String overlayThemeDir = SystemProperties.get(VENDOR_OVERLAY_THEME_PROPERTY);if (!overlayThemeDir.isEmpty()) {scanDirTracedLI(new File(VENDOR_OVERLAY_DIR, overlayThemeDir), mDefParseFlags| PackageParser.PARSE_IS_SYSTEM| PackageParser.PARSE_IS_SYSTEM_DIR| PackageParser.PARSE_TRUSTED_OVERLAY, scanFlags | SCAN_TRUSTED_OVERLAY, 0);}// 扫描收集目录/vendor/overlay下的供应商应用包,用于资源替换scanDirTracedLI(new File(VENDOR_OVERLAY_DIR), mDefParseFlags| PackageParser.PARSE_IS_SYSTEM| PackageParser.PARSE_IS_SYSTEM_DIR| PackageParser.PARSE_TRUSTED_OVERLAY, scanFlags | SCAN_TRUSTED_OVERLAY, 0);// 扫描收集目录/system/framework下的应用包!scanDirTracedLI(frameworkDir, mDefParseFlags| PackageParser.PARSE_IS_SYSTEM| PackageParser.PARSE_IS_SYSTEM_DIR| PackageParser.PARSE_IS_PRIVILEGED,scanFlags | SCAN_NO_DEX, 0);// 扫描收集目录/system/priv-app/下的应用包信息final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");scanDirTracedLI(privilegedAppDir, mDefParseFlags| PackageParser.PARSE_IS_SYSTEM| PackageParser.PARSE_IS_SYSTEM_DIR| PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0);// 扫描收集目录/system/app/下的应用包信息final File systemAppDir = new File(Environment.getRootDirectory(), "app");scanDirTracedLI(systemAppDir, mDefParseFlags| PackageParser.PARSE_IS_SYSTEM| PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);File vendorAppDir = new File("/vendor/app");try {vendorAppDir = vendorAppDir.getCanonicalFile();} catch (IOException e) {}// 扫描收集目录/vendor/app/下的应用包信息scanDirTracedLI(vendorAppDir, mDefParseFlags| PackageParser.PARSE_IS_SYSTEM| PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);// 扫描收集目录/oem/app/下的应用包信息final File oemAppDir = new File(Environment.getOemDirectory(), "app");scanDirTracedLI(oemAppDir, mDefParseFlags| PackageParser.PARSE_IS_SYSTEM| PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);// 扫描收集目录/system/vendor/Default下的应用包信息if (RegionalizationEnvironment.isSupported()) {Log.d(TAG, "Load Regionalization vendor apks");final List<File> RegionalizationDirs =RegionalizationEnvironment.getAllPackageDirectories();for (File f : RegionalizationDirs) {File RegionalizationSystemDir = new File(f, "system");scanDirLI(new File(RegionalizationSystemDir, "priv-app"),PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR| PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0);scanDirLI(new File(RegionalizationSystemDir, "app"),PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR,scanFlags, 0);scanDirLI(new File(RegionalizationSystemDir, "vendor/overlay"),PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR,scanFlags | SCAN_TRUSTED_OVERLAY, 0);}}/************************* 结尾阶段 *************************/// 删除任何不再存在的系统包,这类List表示的是有可能有升级包的系统应用final List<String> possiblyDeletedUpdatedSystemApps = new ArrayList<String>();//构造一个List来存放不存在的packages路径if (!mOnlyCore) {// 遍历上一次安装的信息!Iterator<PackageSetting> psit = mSettings.mPackages.values().iterator();while (psit.hasNext()) {PackageSetting ps = psit.next();//忽略掉非系统应用if ((ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0) {continue;}// 保存的是上面调用scanDirLI方法扫描目录得到的应用信息,不要和mSettings.mPackages弄混了final PackageParser.Package scannedPkg = mPackages.get(ps.name);if (scannedPkg != null) {// 如果在disable列表中,那么,说明它是通过OTA方式进行升级更新添加的,因此,清除相应数据// 说明一定是通过覆盖更新的,移除之前扫描的结果,保证之前用户安装的应用能够被扫描!// “disable” 列表是package.xml中<update-package>标签标示的应用if (mSettings.isDisabledSystemPackageLPr(ps.name)) {logCriticalInfo(Log.WARN, "Expecting better updated system app for "+ ps.name + "; removing system app.  Last known codePath="+ ps.codePathString + ", installStatus=" + ps.installStatus+ ", versionCode=" + ps.versionCode + "; scanned versionCode="+ scannedPkg.mVersionCode);// 从扫描列表mPackages中移除removePackageLI(scannedPkg, true);// 放入mExpectingBetter列表,后面会进行处理的。mExpectingBetter.put(ps.name, ps.codePath);}//跳出循环,确保不会被删除continue;}// 注意此处的前提是scannedPkg为null// 运行到这里说明ps表示的应用不在被扫描列表mPackages中,也就是在系统中不存在 if (!mSettings.isDisabledSystemPackageLPr(ps.name)) {// 如果这个文件不属于<update-package>标识的应用,说明这个应用是残留在packages.xml中的,// 可能还有数据目录,因此要删除psit.remove();logCriticalInfo(Log.WARN, "System package " + ps.name+ " no longer exists; it's data will be wiped");} else {// 如果这个应用不在系统中,但是被标记了<update-package>在不可用列表中//  则加入到possiblyDeletedUpdatedSystemApps列表final PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(ps.name);if (disabledPs.codePath == null || !disabledPs.codePath.exists()) {possiblyDeletedUpdatedSystemApps.add(ps.name);}}}}// 扫描并删除未安装成功的apk包ArrayList<PackageSetting> deletePkgsList = mSettings.getListOfIncompleteInstallPackagesLPr();for (int i = 0; i < deletePkgsList.size(); i++) {// Actual deletion of code and data will be handled by later// reconciliation stepfinal String packageName = deletePkgsList.get(i).name;logCriticalInfo(Log.WARN, "Cleaning up incompletely installed app: " + packageName);synchronized (mPackages) {mSettings.removePackageLPw(packageName);}}//  删除临时文件deleteTempPackageFiles();// 删除掉Settings中的没有关联任何应用的SharedUserSetting对象mSettings.pruneSharedUsersLPw();}
}

上面的源码按照执行的相关逻辑划分可以分为三个小阶段:,每个小阶段的主要流程如下:

  • 前期

    • 执行前期相关的准备工作,譬如设置扫描参数,获取环境变量等,其中这个阶段最最重要的就是对所有的共享库执行odex操作!

      当然不是每次PKMS启动都会执行odex操作,而是会进行相关的逻辑判断是否已经有执行过,如果没有则执行!

  • 中期(好像叫个高潮期,总感觉别扭,我可是一个很正经的人!)

    • 扫描Android终端系统系列目录,获取安装包相关信息!
  • 后期

    • 收集那些可能已经不存在的系统应用包,在扫描完data分区安装应用后再处理!
    • 清理所有安装不完全的应用包!

我们接下来继续分析:

2.1 对共享库执行dex优化操作

此处的主要代码逻辑如下:

            //确保所有的共享库都被dexopt优化if (mSharedLibraries.size() > 0) {for (String dexCodeInstructionSet : dexCodeInstructionSets) {for (SharedLibraryEntry libEntry : mSharedLibraries.values()) {final String lib = libEntry.path;if (lib == null) {continue;}try {//判断共享库是否需要执行odex操作int dexoptNeeded = DexFile.getDexOptNeeded(lib, dexCodeInstructionSet,getCompilerFilterForReason(REASON_SHARED_APK),false /* newProfile */);// 如果需要odex操作,对共享库进行一次预编译(AOT)if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {// 调用install的dexopt命令,优化后的文件放在/data/dalvik-cache/下面mInstaller.dexopt(lib, Process.SYSTEM_UID, dexCodeInstructionSet,dexoptNeeded, DEXOPT_PUBLIC /*dexFlags*/,getCompilerFilterForReason(REASON_SHARED_APK),StorageManager.UUID_PRIVATE_INTERNAL,SKIP_SHARED_LIBRARY_CHECK);}} catch (FileNotFoundException e) {Slog.w(TAG, "Library not found: " + lib);} catch (IOException | InstallerException e) {Slog.w(TAG, "Cannot dexopt " + lib + "; is it an APK or JAR? "+ e.getMessage());}}}}

其主要流程就是下面两个步骤:

  • 判断前面从系统配置文件中获取到的共享库是否需要执行odex操作;
  • 如果需要执行dex操作,就对共享库进行预(AOT)处理;

这里如果要执行共享库的dex优化操作,需要借助到另外一个进程就是installd,它是一个native层的可执行文件,如下:

xxx:/ # ps | grep installd
root      544   1     5220   1800  unix_strea a79e96f0 S /system/bin/installd

并且此时PKMS和installd的通信是跨进程的,此时用到了LocalSocket的通信方式,关于此种通信可以参见博客Android Java层和Native层通信实战大荟萃之LocalSocket实现通信有详细介绍,它们二者之间的通信模型可以使用如下的模型图来表示:

并且PKMS在处理应用安装和扫描的逻辑中有多处需要借助到installd提供的命令来实现,installd提供的常用命令如下:

// 每个具体命令就不解析了,这块读者自行研究,前面我也放出了相关博客的链接
struct cmdinfo cmds[] = { { "ping",                 0, do_ping },{ "create_app_data",      7, do_create_app_data },{ "restorecon_app_data",  6, do_restorecon_app_data },{ "migrate_app_data",     4, do_migrate_app_data },{ "clear_app_data",       5, do_clear_app_data },{ "destroy_app_data",     5, do_destroy_app_data },{ "move_complete_app",    7, do_move_complete_app },{ "get_app_size",         6, do_get_app_size },{ "get_app_data_inode",   4, do_get_app_data_inode },{ "create_user_data",     4, do_create_user_data },{ "destroy_user_data",    3, do_destroy_user_data },{ "dexopt",              10, do_dexopt },{ "markbootcomplete",     1, do_mark_boot_complete },{ "rmdex",                2, do_rm_dex },{ "freecache",            2, do_free_cache },{ "linklib",              4, do_linklib },{ "idmap",                3, do_idmap },{ "createoatdir",         2, do_create_oat_dir },{ "rmpackagedir",         1, do_rm_package_dir },{ "clear_app_profiles",   1, do_clear_app_profiles },{ "destroy_app_profiles", 1, do_destroy_app_profiles },{ "linkfile",             3, do_link_file },{ "move_ab",              3, do_move_ab },{ "merge_profiles",       2, do_merge_profiles },{ "dump_profiles",        3, do_dump_profiles },{ "delete_odex",          3, do_delete_odex },
};

最终我们会在/data/dalvik-cache/arm/下面看到一大对被dex优化过的文件,当然不止包括共享库还有其它的,如下:

xxx:/data/dalvik-cache/arm # ls -a | grep system@framework
system@framework@ConnectivityExt.jar@classes.dex
system@framework@boot-apache-xml.art
system@framework@boot-apache-xml.oat
system@framework@boot-bouncycastle.art
system@framework@boot-bouncycastle.oat
system@framework@boot-conscrypt.art
system@framework@boot-conscrypt.oat
system@framework@boot-core-junit.art
system@framework@boot-core-junit.oat
system@framework@boot-core-libart.art
system@framework@boot-core-libart.oat
system@framework@boot-ext.art
system@framework@boot-ext.oat
system@framework@boot-framework.art
system@framework@boot-framework.oat
system@framework@boot-ims-common.art
system@framework@boot-ims-common.oat
system@framework@boot-oem-services.art
system@framework@boot-oem-services.oat

二.正式开始系统应用安装目录扫描

感觉这标题不是很霸气和扣住主题,先不管了还是先将源码逻辑理清楚,再回过头来想一个霸气的标题。

如果说前面阶段的工作是PKMS启动BOOT_PROGRESS_PMS_SYSTEM_SCAN_START阶段的开胃菜,那么从此阶段就是正式要开始上主菜了,所以能不能吃不吃得饱就看这个阶段了。从源码看,该阶段的主要代码如下:

// 【 PackageManagerService.java 】...//设置扫描的参数final int scanFlags = SCAN_NO_PATHS | SCAN_DEFER_DEX | SCAN_BOOTING | SCAN_INITIAL;/************************* 中期高潮阶段 *************************/// 关于scanDirTracedLI方法详见章节 【 2.1 】String overlayThemeDir = SystemProperties.get(VENDOR_OVERLAY_THEME_PROPERTY);if (!overlayThemeDir.isEmpty()) {scanDirTracedLI(new File(VENDOR_OVERLAY_DIR, overlayThemeDir), mDefParseFlags| PackageParser.PARSE_IS_SYSTEM| PackageParser.PARSE_IS_SYSTEM_DIR| PackageParser.PARSE_TRUSTED_OVERLAY, scanFlags | SCAN_TRUSTED_OVERLAY, 0);}// 扫描收集目录/vendor/overlay下的供应商应用包,用于资源替换scanDirTracedLI(new File(VENDOR_OVERLAY_DIR), mDefParseFlags| PackageParser.PARSE_IS_SYSTEM| PackageParser.PARSE_IS_SYSTEM_DIR| PackageParser.PARSE_TRUSTED_OVERLAY, scanFlags | SCAN_TRUSTED_OVERLAY, 0);// 扫描收集目录/system/framework下的应用包!scanDirTracedLI(frameworkDir, mDefParseFlags| PackageParser.PARSE_IS_SYSTEM| PackageParser.PARSE_IS_SYSTEM_DIR| PackageParser.PARSE_IS_PRIVILEGED,scanFlags | SCAN_NO_DEX, 0);// 扫描收集目录/system/priv-app/下的应用包信息final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");scanDirTracedLI(privilegedAppDir, mDefParseFlags| PackageParser.PARSE_IS_SYSTEM| PackageParser.PARSE_IS_SYSTEM_DIR| PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0);// 扫描收集目录/system/app/下的应用包信息final File systemAppDir = new File(Environment.getRootDirectory(), "app");scanDirTracedLI(systemAppDir, mDefParseFlags| PackageParser.PARSE_IS_SYSTEM| PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);File vendorAppDir = new File("/vendor/app");try {vendorAppDir = vendorAppDir.getCanonicalFile();} catch (IOException e) {}// 扫描收集目录/vendor/app/下的应用包信息scanDirTracedLI(vendorAppDir, mDefParseFlags| PackageParser.PARSE_IS_SYSTEM| PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);// 扫描收集目录/oem/app/下的应用包信息final File oemAppDir = new File(Environment.getOemDirectory(), "app");scanDirTracedLI(oemAppDir, mDefParseFlags| PackageParser.PARSE_IS_SYSTEM| PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);// 扫描收集目录/system/vendor/Default下的应用包信息if (RegionalizationEnvironment.isSupported()) {Log.d(TAG, "Load Regionalization vendor apks");final List<File> RegionalizationDirs =RegionalizationEnvironment.getAllPackageDirectories();for (File f : RegionalizationDirs) {File RegionalizationSystemDir = new File(f, "system");scanDirLI(new File(RegionalizationSystemDir, "priv-app"),PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR| PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0);scanDirLI(new File(RegionalizationSystemDir, "app"),PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR,scanFlags, 0);scanDirLI(new File(RegionalizationSystemDir, "vendor/overlay"),PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR,scanFlags | SCAN_TRUSTED_OVERLAY, 0);}}

通过上述的源码我们可以看到在该阶段最最最重要的核心诉求点只有一个按照特定循序调用scanDirTracedLI方法扫描应用安装目录,被扫描的目录循序如下:

  • /vendor/overlay
  • /system/framework
  • /system/priv-app
  • /system/app
  • /vendor/app
  • /oem/app
  • /system/vendor/Default

并且关于此阶段的扫描我们有如下的两个点需要注意:

  • 系统目录扫描阶段传入的扫描参数scanFlags的取值如下:

    final int scanFlags = SCAN_NO_PATHS | SCAN_DEFER_DEX | SCAN_BOOTING | SCAN_INITIAL;
    

    这里为啥要重点关注扫描参数呢,因为后续的/data等分区的应用安装目录扫描也是走的scanDirLI方法,它们之间的一些分支走向是由扫描参数进行决定的,所以这里强调一下!

  • 在该扫描阶段,需要注意的是被扫描目录的顺序,这个顺序通常意味着:先被扫描到的文件,就是最终被用到的文件

    这个只是通常,因为如果牵涉到应用的覆盖升级的情况,那就是另外的特殊处理了!

该阶段的扫描最终调用的都是scanDirTracedLI方法,我们这里以扫描/system/priv-app为例来说明(其它的目录,读者自行分析)。这里在进行相关的详细分析之前,我们先来整体看下该扫描j阶段的整体时序图,如下:

2.1 PKMS.scanDirTracedLI(…)

从此方法开始开始真正开启扫描流程,从此天涯是路人!我们先来看下此方法:

// 【 PackageManagerService.java 】private void scanDirTracedLI(File dir, final int parseFlags, int scanFlags, long currentTime) {Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir");try {scanDirLI(dir, parseFlags, scanFlags, currentTime);// 详见章节 【 2.2 】} finally {Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);}}

可以看到此方法平淡无奇,只是接着调用另一个方法scanDirLI继续处理,但是我这里有必要对其中它的两个入参parseFlags和scanFlags捯饬捯饬一下:

  • parseFlags:此参数的取值表明的是对于被扫描目录的处理逻辑的flag值,影响的是扫描目录源码的分支走向

  • scanFlags:此参数的取值表明的是对于扫描目录中的应用安装包解析处理逻辑的flag值,影响的是对具体的安装包的解析逻辑,即最终PackageParser.parsePackagesparsePackages的处理逻辑

好了我们接着往下看!

2.2 PKMS.scanDirLI(…)

没有啥前提要交代的,继续接着干!

// 【 PackageManagerService.java 】private void scanDirLI(File dir, final int parseFlags, int scanFlags, long currentTime) {final File[] files = dir.listFiles();if (ArrayUtils.isEmpty(files)) {Log.d(TAG, "No files in app dir " + dir);return;}if (DEBUG_PACKAGE_SCANNING) {Log.d(TAG, "Scanning app dir " + dir + " scanFlags=" + scanFlags+ " flags=0x" + Integer.toHexString(parseFlags));}Log.d(TAG, "start scanDirLI:"+dir);// use multi thread to speed up scanning// 通过线程加速扫描int iMultitaskNum = SystemProperties.getInt("persist.pm.multitask", 6);Log.d(TAG, "max thread:" + iMultitaskNum);final MultiTaskDealer dealer = (iMultitaskNum > 1) ? MultiTaskDealer.startDealer(MultiTaskDealer.PACKAGEMANAGER_SCANER, iMultitaskNum) : null;for (File file : files) {// 判断file是否是Package,必须要同时满足下面2个条件// 1、file 以 ".apk" 结尾或者file是一个目录;// 2、file不是安装阶段临时的apk类型的文件;final boolean isPackage = (isApkFile(file) || file.isDirectory())&& !PackageInstallerService.isStageName(file.getName());/*********** 这里为了方便演示直接展开了 ****************************/public static boolean isStageName(String name) {final boolean isFile = name.startsWith("vmdl") && name.endsWith(".tmp");final boolean isContainer = name.startsWith("smdl") && name.endsWith(".tmp");final boolean isLegacyContainer = name.startsWith("smdl2tmp");return isFile || isContainer || isLegacyContainer;}     /*********** 这里为了方便演示直接展开了 ****************************/      if (!isPackage) {continue;}if (RegionalizationEnvironment.isSupported()) {if (RegionalizationEnvironment.isExcludedApp(file.getName())) {Slog.d(TAG, "Regionalization Excluded:" + file.getName());continue;}}final File ref_file = file;final int ref_parseFlags = parseFlags;final int ref_scanFlags = scanFlags;final long ref_currentTime = currentTime;Runnable scanTask = new Runnable() {public void run() {try {// 注意此时增加了一个解析参数就是PARSE_MUST_BE_APKscanPackageTracedLI(ref_file, ref_parseFlags | PackageParser.PARSE_MUST_BE_APK,ref_scanFlags, ref_currentTime, null);// 详见章节 【 2.3 】} catch (PackageManagerException e) {Slog.w(TAG, "Failed to parse " + ref_file + ": " + e.getMessage());// 如果扫描data分区的Apk失败,则删除data分区扫描失败的文件if ((ref_parseFlags & PackageParser.PARSE_IS_SYSTEM) == 0 &&e.error == PackageManager.INSTALL_FAILED_INVALID_APK) {logCriticalInfo(Log.WARN, "Deleting invalid package at " + ref_file);removeCodePathLI(ref_file);}}}};if (dealer != null)dealer.addTask(scanTask);elsescanTask.run();// 开始扫描}if (dealer != null)dealer.waitAll();Log.d(TAG, "end scanDirLI:"+dir);}

此处源码的逻辑清晰明了,主要分为两大步骤:

  • 首先判断被扫描目录下各个安装包路径的合法性,它必须满足如下的两个条件:

    • 首先各个安装包路径必须是apk文件或者是一个文件夹

    • 然后各个安装包文件名称不能是应用安装过程的临时存储类型的文件

      文件名称不可以是:
      1.不能以vmdl开头且不以 .tmp结尾;
      2.不能以smdl开头且不以 .tmp结尾;
      3.不能以smdl2tmp开头;

      读者是不是很好奇,什么时候会产生上述类型的安装包文件呢,在应用的安装过程中在/data目录下会产生上述类型的临时文件/data/app/vmdl1611774040.tmp/base.apk。关于这块在后续的博客应用的安装流程中会涉及到。

  • 然后通过多线程的方式继续调用scanPackageTracedLI进行下一步的扫描流程

    关于此处有两点需要注意:

    1、在Android AOSP的源码中,scanPackageTracedLI方法并不是在多线程中进行的,这是方案厂为了优化开机速度慢而进行优化的测量,我想这个手段很多做ROM开发的都有使用过(因为PKMS的扫描时很耗时间的)

    2.注意这里的try catch语句,后面方法抛出的异常都是在这个进 catch的,并且如果是扫描的/data分区的安装应用目录,在catch代码执行逻辑中会,若扫描失败则删除data分区扫描失败的文件。

好了我们接着往下看!

2.3 PKMS.scanPackageTracedLI(File…)

此处有一点需要注意的是scanPackageTracedLI方法存在重载,所以要认清具体调用的是哪个,继续接着干!

// 【 PackageManagerService.java 】private PackageParser.Package scanPackageTracedLI(File scanFile, final int parseFlags,int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanPackage");try {// 扫描单个安装安装包,可能是/system/priv-app/xxx/类型的,也可能是/system/priv-app/xxx.apkreturn scanPackageLI(scanFile, parseFlags, scanFlags, currentTime, user);// 详见章节 【 2.4 】} finally {Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);}}

此代码比较懒,啥也没有干,直接当了个传递手调用另外一个方法scanPackageLI继续执行扫描逻辑。

2.4 PKMS.scanPackageLI(File …)

还记得为啥这一篇和前面分析PKMS中间差了三个篇章吗,这里就是原因了,因为这其中涉及到了解析Android安装包的流程,并其解析流程parsePackage是一个比较繁琐的,所以特意花了三个篇章来分析。

并且由于前面已经重点分析过了解析安装包的流程,这里就不需要大费周章的重复了,具体的就可以参见PKMS启动详解(四)之Android包信息体和包解析器了。

此处有一点需要注意的是scanPackageLI方法存在重载,所以要认清具体调用的是哪个,继续接着干,一直撸一直爽!

// 【 PackageManagerService.java 】/*此时parseFlags的取值为: PackageParser.PARSE_IS_SYSTEM| PackageParser.PARSE_IS_SYSTEM_DIR| PackageParser.PARSE_IS_PRIVILEGED| PackageParser.PARSE_MUST_BE_APK此时scanFlags的取值是SCAN_NO_PATHS | SCAN_DEFER_DEX | SCAN_BOOTING | SCAN_INITIAL*/private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,long currentTime, UserHandle user) throws PackageManagerException {if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanFile);// 构建一个PackagePares包解析器对象实例,用于解析包// 并且填充前面PKMS构造方法中的相关成员信息PackageParser pp = new PackageParser(mContext);pp.setSeparateProcesses(mSeparateProcesses);pp.setOnlyCoreApps(mOnlyCore);pp.setOnlyPowerOffAlarmApps(mOnlyPowerOffAlarm);pp.setDisplayMetrics(mMetrics);//判断扫描模式if ((scanFlags & SCAN_TRUSTED_OVERLAY) != 0) {parseFlags |= PackageParser.PARSE_TRUSTED_OVERLAY;}Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");// 构建PackageParser.Package实例对象pkg用来保存解析结果final PackageParser.Package pkg;try {/*这里解析具体某个App中的AndroidManifestxml文件解析出来的数据放在PackageParser的内部类Package的实例pkg中,这里的parseFlags也是重点,系统(system)和非系统文件夹(/data/app)下扫描的tag是不一样的*/pkg = pp.parsePackage(scanFile, parseFlags);} catch (PackageParserException e) {throw PackageManagerException.from(e);} finally {Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);}// 然后需要将pkg中的信息传递给PKMS服务,继续进行相关的扫描处理return scanPackageLI(pkg, scanFile, parseFlags, scanFlags, currentTime, user);// 详见章节 【 2.5 】}

此处scanPackageLI方法的处理逻辑层次分明,主要分为三大部分:

  • 构建PackageParser安装包解析实例对象,并且使用PKMS中相关的成员信息填充它

    关于PackageParser我想就不用过大的介绍了,它是一个用于解析Android安装包的工具类,通过它可以得到完整的一个安装包的所有信息。

  • 接着调用PackageParser解析器的parsePackage方法正式开始解析安装包,得到安装包信息对象Package实例对象

    关于此处具体的就可以参见PKMS启动详解(四)之Android包信息体和包解析器,里面有特别详尽的对于Android包解析器和安装包信息体从设计到实施的全方位解读!

  • 将前面解析安装包得到的Package安装包信息对象继续交由重载的scanPackageLI进行下一步处理,然后返回得到继续处理的Pakcage实例对象

    虽然通过parsePackage方法解析得到了安装包的信息,但是这个不是最终的还必须进过进一步的加工和验证才可以!

我们接着继续往下一阶段进军!

2.5 PKMS.scanPackageLI(PackageParser.Package …,6)

该方法的参数入参是六个,不要和后面2.7章节的搞混淆了!注意,注意!

此处无声胜有声,接着往下看!

// 【 PackageManagerService.java 】/*此时policyFlags的取值为: PackageParser.PARSE_IS_SYSTEM| PackageParser.PARSE_IS_SYSTEM_DIR| PackageParser.PARSE_IS_PRIVILEGED| PackageParser.PARSE_MUST_BE_APK它的值来源于前面的parseFlags此时scanFlags的取值是SCAN_NO_PATHS | SCAN_DEFER_DEX | SCAN_BOOTING | SCAN_INITIAL*/private PackageParser.Package scanPackageLI(PackageParser.Package pkg, File scanFile,final int policyFlags, int scanFlags, long currentTime, UserHandle user)throws PackageManagerException {// SCAN_CHECK_ONLY标签是为了检测是否所有的包(parent 和child)都可以被成功的扫描到if ((scanFlags & SCAN_CHECK_ONLY) == 0) {if (pkg.childPackages != null && pkg.childPackages.size() > 0) {scanFlags |= SCAN_CHECK_ONLY;}} else {scanFlags &= ~SCAN_CHECK_ONLY;}// 继续处理当前解析得到的package信息,然后得到最终扫描处理的scannedPkg信息为扫描的包信息!PackageParser.Package scannedPkg = scanPackageInternalLI(pkg, scanFile, policyFlags,scanFlags, currentTime, user);// 详见章节 【 2.6 】// 扫描当前package的子pacakge(如果有),我们不管子包存在的情况final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;for (int i = 0; i < childCount; i++) {PackageParser.Package childPackage = pkg.childPackages.get(i);scanPackageInternalLI(childPackage, scanFile, policyFlags, scanFlags,currentTime, user);}// 如果设置SCAN_CHECK_ONLY标志位, 就调用自身,再次处理!,当前扫描条件不会进入此分支if ((scanFlags & SCAN_CHECK_ONLY) != 0) {return scanPackageLI(pkg, scanFile, policyFlags, scanFlags, currentTime, user);}return scannedPkg;}

关于此处对于被扫描的package有child package我们不予考虑,最终该方法会继续调用scanPackageInternalLI执行余下的扫描处理逻辑。

如果被扫描的package有child package,并且是第一次进入该方法的话,就需要检测是否所有的包(parent和child)是否都可以被成功的扫描到。

对于有child package的情况的scanFlags本来是没有SCAN_CHECK_ONLY位的,所以这里会将其SCAN_CHECK_ONLY置为1,这样在方法的最后,又会调用自身,这次又会将 SCAN_CHECK_ONLY 位置为 0!

2.6 PKMS.scanPackageInternalLI(…)

如果说前面的逻辑读者理解起来毫不费力,那么从此处开始我想读者将不得不打气十二分精神了!

因为此处源码的逻辑牵涉到很多关于应用安装包处理的情况,如果是刚开始肯定是一遍过不了的,没有关系多整几遍,捋顺了关系就可以了。当然如果读者能一遍给整清楚那是最好不过的了(反正当初我学习的时候,是没有做到一次就OVER了)。

我们继续来看,通过前面的处理逻辑,我们解析获得了应用程序的PackageParser.Package对象,同时,我们也已经获得了上一次的应用的安装信息(相关信息存储在Settings.mPackages中),接下来,就是通过上述的相关数据继续处理解析获得的数据:

// 【 PackageManagerService.java 】/*通过前面的扫描解析,我们获得了应用程序的PackageParser.Package对象同时,我们也已经获得了上一次的安装信息,接下来,就是要处理解析获得的数据此时policyFlags的取值为: PackageParser.PARSE_IS_SYSTEM| PackageParser.PARSE_IS_SYSTEM_DIR| PackageParser.PARSE_IS_PRIVILEGED| PackageParser.PARSE_MUST_BE_APK此时scanFlags的取值是SCAN_NO_PATHS | SCAN_DEFER_DEX | SCAN_BOOTING | SCAN_INITIAL*/private PackageParser.Package scanPackageInternalLI(PackageParser.Package pkg, File scanFile,int policyFlags, int scanFlags, long currentTime, UserHandle user)throws PackageManagerException {// 这里传入的参数pkg表示解析扫描App应用的AndroidManifest.xml得到的结果/********************** 第一步 **********************//*判断系统APP是否需要更新*///这里的ps用来存储保存在Settings中的扫描的APK信息PackageSetting ps = null;PackageSetting updatedPkg;//表示需要升级的pkg// readersynchronized (mPackages) {// Look to see if we already know about this package.// 通过mSettings查找Package是否被重命名过,有的话获得其oldName// 对于系统apk来说,才有源包String oldName = mSettings.mRenamedPackages.get(pkg.packageName);// 如果package有源包,并且源包的名字是当前扫描的package的旧名字,// 那就用源包的PackageSetting作为当前package的数据!if (pkg.mOriginalPackages != null && pkg.mOriginalPackages.contains(oldName)) {//该方法用于获得 packageName 对应的 PackageSetting 对象ps = mSettings.peekPackageLPr(oldName);}// If there was no original package, see one for the real package name.// 如果没有源包,就使用当前解析到的package的包名从已经安装的应用信息中查找一个PackageSettingif (ps == null) {ps = mSettings.peekPackageLPr(pkg.packageName);}/* 检查解析出来的软件包是否是一个被隐藏或者更新的系统包如果当前系统package被更新过,就查找到被更新之前的PackageSetting!对于非系统的apk,updatePkg是为null的!但对于覆盖安装的系统apk来说,updatePkg不为null*/updatedPkg = mSettings.getDisabledSystemPkgLPr(ps != null ? ps.name : pkg.packageName);if (DEBUG_INSTALL && updatedPkg != null) Slog.d(TAG, "updatedPkg = " + updatedPkg);// 如果系统package并且被更新过,需要处理新旧child package的差异!// 比较更新后的child package和更新前的child package,如果更新前的child package不存在了;// 就要移除!if ((policyFlags & PackageParser.PARSE_IS_SYSTEM) != 0) {PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(pkg.packageName);if (disabledPs != null) {// 当前package的child package个数final int scannedChildCount = (pkg.childPackages != null)? pkg.childPackages.size() : 0;// 更新之前的child package个数!final int disabledChildCount = disabledPs.childPackageNames != null? disabledPs.childPackageNames.size() : 0;for (int i = 0; i < disabledChildCount; i++) {String disabledChildPackageName = disabledPs.childPackageNames.get(i);boolean disabledPackageAvailable = false;for (int j = 0; j < scannedChildCount; j++) {PackageParser.Package childPkg = pkg.childPackages.get(j);if (childPkg.packageName.equals(disabledChildPackageName)) {disabledPackageAvailable = true;break;}}// 更新前的child package,不包含在更新后的child package中,无效,// 就从mDisabledSysPackages中删除掉!if (!disabledPackageAvailable) {mSettings.removeDisabledSystemPackageLPw(disabledChildPackageName);}}}}}boolean updatedPkgBetter = false;// 首先检查是否是需要更新的系统应用// First check if this is a system package that may involve an update/*处理覆盖更新的情况注意此时解析的是系统apk,并且之前有被覆盖安装过注意:ps表示上次安装的信息,updatedPkg表示是由于覆盖安装而被更新前的信息!pkg则是本次扫描的system app的信息!*/if (updatedPkg != null && (policyFlags & PackageParser.PARSE_IS_SYSTEM) != 0) {// 如果新的安装包不位于"/system/pri-app"(例如由于OTA),则需要删除FLAG_PRIVILEGED标志if (locationIsPrivileged(scanFile)) {updatedPkg.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;} else {updatedPkg.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;}// 上次更新后的应用路径和这次扫描解析的路径不一样,上次更新到了data分区,而这次扫描的是system分区,// 就需要比较一下versionCode的大小,if (ps != null && !ps.codePath.equals(scanFile)) {//  如果路径和上次扫描的发生变化了,根据我们存储的路径检查新路径的版本             if (DEBUG_INSTALL) Slog.d(TAG, "Path changing from " + ps.codePath);// pkg的versioncode小于等于上次更新后的versioncode,说明data分区的apk仍然是最新的// 那就用本次扫描解析的数据,来更新上次更新前的旧数据!if (pkg.mVersionCode <= ps.versionCode) {// 如果升级系统包已经是最新的,则停止此次的扫描if (DEBUG_INSTALL) Slog.i(TAG, "Package " + ps.name + " at " + scanFile+ " ignored: updated version " + ps.versionCode+ " better than this " + pkg.mVersionCode);// updatedPkg.codePath不等于当前的扫描目录,说明该system app目录发生了变化!// 而该system apk是被覆盖更新了,所以也需要更新updatedPkg的数据if (!updatedPkg.codePath.equals(scanFile)) {Slog.w(PackageManagerService.TAG, "Code path for hidden system pkg "+ ps.name + " changing from " + updatedPkg.codePathString+ " to " + scanFile);updatedPkg.codePath = scanFile;updatedPkg.codePathString = scanFile.toString();updatedPkg.resourcePath = scanFile;updatedPkg.resourcePathString = scanFile.toString();}updatedPkg.pkg = pkg;updatedPkg.versionCode = pkg.mVersionCode;// Update the disabled system child packages to point to the package too.final int childCount = updatedPkg.childPackageNames != null? updatedPkg.childPackageNames.size() : 0;for (int i = 0; i < childCount; i++) {String childPackageName = updatedPkg.childPackageNames.get(i);PackageSetting updatedChildPkg = mSettings.getDisabledSystemPkgLPr(childPackageName);if (updatedChildPkg != null) {updatedChildPkg.pkg = pkg;updatedChildPkg.versionCode = pkg.mVersionCode;}}throw new PackageManagerException(Log.WARN, "Package " + ps.name + " at "+ scanFile + " ignored: updated version " + ps.versionCode+ " better than this " + pkg.mVersionCode);} else {               /*pkg的versioncode大于上次更新后的versioncode,说明当前在system分区的apk要比data分区的新,这说明system app又通过OTA升级更新了那就要保留本次扫描解析的数据,删除上一次更新的数据!PS:系统应用升级完成后会安装在data分区,之前的system分区的应用会被标记为disable状态*///重新安装包到系统分区中synchronized (mPackages) {// Just remove the loaded entries from package lists.// 从PackageManagerService的安装包列表中删除该包// 这个地方有个疑惑,此时的pkg信息还没有添加进去,删除的是什么呢mPackages.remove(ps.name);}logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + scanFile+ " reverting from " + ps.codePathString+ ": new version " + pkg.mVersionCode+ " better than installed " + ps.versionCode);//创建安装参数InstallArgsInstallArgs args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps),ps.codePathString, ps.resourcePathString, getAppDexInstructionSets(ps));synchronized (mInstallLock) {// 移除data分区的apk和相应的dex文件!args.cleanUpResourcesLI();}synchronized (mPackages) {// 将更新前的旧数据PackageSetting ,从mDisabledSysPackages中移除,// 并复用旧数据,创建一个新的PackageSetting,添加到Setting的mPackage中!mSettings.enableSystemPackageLPw(ps.name);}updatedPkgBetter = true;// 设置updatedPkgBetter为true!}}}// 非系统apk,不进入这个分支!// 但是对于覆盖安装的系统apk,updatedPkg不为null,所以要添加指定的扫描位,表示当前解析的是一个系统 apk!if (updatedPkg != null) {// 对于被更新的系统app的flag进行设置!// An updated system app will not have the PARSE_IS_SYSTEM flag set// initially// 更新的系统应用程序最初不会设置PARSE_IS_SYSTEM的flagpolicyFlags |= PackageParser.PARSE_IS_SYSTEM;// An updated privileged app will not have the PARSE_IS_PRIVILEGED// flag set initially// 已经更新的应用不设置PARSE_IS_PRIVILEGED的flagif ((updatedPkg.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {policyFlags |= PackageParser.PARSE_IS_PRIVILEGED;}}/********************** 第二步**********************//*前面构建Package实例对象是还没有解析并填充APK的签名信息,现在正是要把签名信息填进去的时候,因为到这一步已经确认要安装APK了,APK能安装的前提就是一定要有签名信息,如果对已有的APK进行升级,则签名信息必须与已有APK相匹配(不然就乱套了),collectCertificatesLI方法就是从Apk的包中的META-INF目录中读取签名信息,*/// Verify certificates against what was last scanned// 安装包检验collectCertificatesLI(ps, pkg, scanFile, policyFlags);/********************** 第三步 ********************//*处理系统APK没有覆盖,但是data和system分区出现了相同的apk的情况!package并没有发生覆盖更新,但之前apk是安装在data分区,而后来出现了相同包名的新apk安装在了system分区(比如系统OTA升级导致的apk分区改变,或者root后push一个相同apk到system分区然后重启)!或者还有一种是之前安装在data分区,然后应用移动到了system分区!那就要判断是否隐藏system分区的这个apk了!*/// 我们扫描安装一个系统APP的时候,发现已经有了一个相同包名的APP,而且这个相同包名APP是在非系统的分区中,并且它也不是// 升级系统App而来的boolean shouldHideSystemApp = false;if (updatedPkg == null && ps != null&& (policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0 && !isSystemApp(ps)) {/** Check to make sure the signatures match first. If they don't,* wipe the installed application and its data.*//*如果两个APK签名不匹配,则调用deletePackageLI方法清除位于data分区的APK文件及其数据看来Android系统还是偏心啊,systme分区的apk优先级高于data分区的*/if (compareSignatures(ps.signatures.mSignatures, pkg.mSignatures)!= PackageManager.SIGNATURE_MATCH) {logCriticalInfo(Log.WARN, "Package " + ps.name + " appeared on system, but"+ " signatures don't match existing userdata copy; removing");// 创建一个PackageFreezer用来冻结指定的 package!try (PackageFreezer freezer = freezePackage(pkg.packageName,"scanPackageInternalLI")) {// 删除位于data分区的APK文件deletePackageLIF(pkg.packageName, null, true, null, 0, null, false, null);}ps = null;} else {//如果签名匹配了/** If the newly-added system app is an older version than the* already installed version, hide it. It will be scanned later* and re-added like an update.*/// 如果新添加的位于system分区的apk的版本号小于等于位于data分区的apk;// 那就要隐藏system分区的apk;if (pkg.mVersionCode <= ps.versionCode) {shouldHideSystemApp = true;logCriticalInfo(Log.INFO, "Package " + ps.name + " appeared at " + scanFile+ " but new version " + pkg.mVersionCode + " better than installed "+ ps.versionCode + "; hiding system");} else {// system分区的应用版本更高,删除data分区的apk,同时保留数据!logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + scanFile+ " reverting from " + ps.codePathString + ": new version "+ pkg.mVersionCode + " better than installed " + ps.versionCode);// 创建一个安装参数InstallArgs args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps),ps.codePathString, ps.resourcePathString, getAppDexInstructionSets(ps));synchronized (mInstallLock) {// 删除data分区的apk,但是保留用户数据args.cleanUpResourcesLI();}}}}// 对于非系统的apk,可能存在apk路径和data路径不一样的情况!这个不在本分支考虑范围之内if ((policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {if (ps != null && !ps.codePath.equals(ps.resourcePath)) {// 如果不一样,就加上PackageParser.PARSE_FORWARD_LOCK位!policyFlags |= PackageParser.PARSE_FORWARD_LOCK;}}// TODO: extend to support forward-locked splits// 下面是处理data apk的apk路径和资源路径!String resourcePath = null;String baseResourcePath = null;// 设置resourcePath和baseResourcePath的值if ((policyFlags & PackageParser.PARSE_FORWARD_LOCK) != 0 && !updatedPkgBetter) {if (ps != null && ps.resourcePathString != null) {resourcePath = ps.resourcePathString;baseResourcePath = ps.resourcePathString;} else {// Should not happen at all. Just log an error.Slog.e(TAG, "Resource path not set for package " + pkg.packageName);}} else {resourcePath = pkg.codePath;baseResourcePath = pkg.baseCodePath;}// Set application objects path explicitly.// 设置Package.ApplicationInfo对象的path属性pkg.setApplicationVolumeUuid(pkg.volumeUuid);pkg.setApplicationInfoCodePath(pkg.codePath);pkg.setApplicationInfoBaseCodePath(pkg.baseCodePath);pkg.setApplicationInfoSplitCodePaths(pkg.splitCodePaths);pkg.setApplicationInfoResourcePath(resourcePath);pkg.setApplicationInfoBaseResourcePath(baseResourcePath);pkg.setApplicationInfoSplitResourcePaths(pkg.splitCodePaths);// Note that we invoke the following method only if we are about to unpack an application// 调用另外一个scanPackageLI()方法,对包进行扫描PackageParser.Package scannedPkg = scanPackageLI(pkg, policyFlags, scanFlags| SCAN_UPDATE_SIGNATURE, currentTime, user);// 详见章节 【 2.7 】// 如果确认data分区的apk版本比system分区的版本高// 就隐藏system分区的app,让data分区的apk显示出来!if (shouldHideSystemApp) {synchronized (mPackages) {// 如果新安装的系统APP会被旧的APP数据覆盖,所以需要隐藏系统应用程序。并重新扫描/data/app目录mSettings.disableSystemPackageLPw(pkg.packageName, true);}}return scannedPkg;}

各位看官朋友,是不是被前面的一大坨的源码给唬住了!是的面对着这么一大堆的东东,如果不仔细将逻辑理清楚,上去就是一顿硬干,结局只有一个就是搞得云里雾里的(当然刚开始的我也是如此)。阅读源码都是一个痛苦的过程,但是当你攻克了它之后,你会发现原来它是那么美!

我们可以将scanPackageInternalLI方法的执行逻辑,大概归纳为如下几点,且听我慢慢道来:

  • 判断当前被扫描到的系统应用是否又被覆盖升级过,如果被覆盖升级过则进行相应的逻辑处理

    在此阶段有几个大的知识点读者一定需要攻克:

    1.首先就是要理解Settings.mRenamedPackages和和mSettings.mDisabledSysPackages的含义,这两个其实我已经在前面的博客中已经有多次重点提过了,这里还是简单的说下mRenamedPackages对应的packages.xml中的<renamed-package>标签信息指向的是被重命名的安装包应用,而mDisabledSysPackages指向的是被覆盖安装的系统应用信息。关于二者详细的解释请参见博客PKMS启动详解(二)之怎么通过packages.xml对已安装应用信息进行持久化管理?和博客PKMS启动详解(三)之BOOT_PROGRESS_PMS_START流程分析。

    2.另外的一个就是Android是怎么判断系统应用是否需要升级?这里最最主要利用的就是前面我们获取的已经安装安装包信息大管家Settings中的信息来对比了,我们首先通过PKMS中Settings来获取保存在PKMS中的的APK安装信息对象(即ps对象),然后和前面扫描得到的APK信息pkg对象进行相关的对比(对比的逻辑是先判断被扫描的系统APK是否被覆盖升级过,如果有则对比当前扫描到的和覆盖安装之后的版本)。

    通过上述对比逻辑我们就能知道当前扫描的APK与已经覆盖安装在data分区的历史APK的差异。如果当前扫描的系统APK版本比已经被覆盖安装的系统APK版本要低,则中断扫描过程,直接抛出异常。如果当前扫描的系统APK版本比已经被覆盖安装的系统APK版本要高,则需要重新将系统APK设置为enable状态。(PS:系统应用升级后会安装在data分区,之前的system分区的应用会被标记为disable状态)。

    关于此处的逻辑,感觉还是有点没有说清楚,但是臣妾真的是尽力了,只能读者用心体会了,并其关于系统APK的升级方式在前面我也放出来了。

  • 获取扫描到的安装包签名情况

    在前面的流程中调用包解析器解析安装包获取Package对象时,并没有获取APK的签名信息,现在正是要把APK签名信息填进去的时候,因为到这一步已经确认要安装APK了,APK能安装的前提就是一定要有签名信息。如果对已有APK进行升级,则签名必须与已有APK相匹配(PKMS.collectCertificatesLI()方法就是从APK包中的META-INF目录中读取签名信息,它最后还是调用的包解析器处理相关的逻辑)

  • 处理系统APK没有覆盖安装,但是data和系统分区出现了相同的APK的情况

    注意这里需要和前面的被扫描到的系统应用被覆盖安装的逻辑区别开来!

    此时当前被扫描到的package并没有发生覆盖更新,但之前存在同样的APK是安装在data分区,而此时被扫描到的相同包名的新APK出现在了system分区(比如系统OTA升级导致的APK分区改变,或者root后push一个相同APK到system分区然后重启),或者还有一种是之前安装在data分区,然后应用移动到了system分区!那就要判断是否隐藏system分区的这个APK了!

    并其这里我们还需要注意一点,就是这里的shouldHideSystemApp表示是否需要将系统APK设置为Disable状态,默认情况下为false;如果安装过APK的版本号比当前扫描系统APK的版本要高则意味着要使用data分区的APK,隐藏系统APK,则shouldHideSystem被设置为true

  • 调用scanPackageLI(PackageParser.Package, int, int, long, UserHandle)方法对解析出来的PackageParse.Package继续进行下一步的处理

至此关于scanPackageInternalLI的主线逻辑分析完毕了,这里我们还是简单看下Settings大管家中关于已安装包安装信息的相关操作的几个方法,如下:

// 【 Settings.java 】//被安装应用的包名为key,以安装应用相关信息为valuefinal ArrayMap<String, PackageSetting> mPackages = new ArrayMap<>();PackageSetting peekPackageLPr(String name) {return mPackages.get(name);}/*对应packages.xml中的<updated-package>标签被覆盖升级的系统应用其中key为被覆盖升级的应用包名,value为被覆盖升级前的安装信息*/private final ArrayMap<String, PackageSetting> mDisabledSysPackages =new ArrayMap<String, PackageSetting>();public PackageSetting getDisabledSystemPkgLPr(String name) {PackageSetting ps = mDisabledSysPackages.get(name);return ps;}void removeDisabledSystemPackageLPw(String name) {mDisabledSysPackages.remove(name);}PackageSetting enableSystemPackageLPw(String name) {//从 mDisabledSysPackages中获得被更新前的PackageSettingsPackageSetting p = mDisabledSysPackages.get(name);if(p == null) {Log.w(PackageManagerService.TAG, "Package " + name + " is not disabled");return null;}// Reset flag in ApplicationInfo object// 去掉ApplicationInfo.FLAG_UPDATED_SYSTEM_APP标志位!if((p.pkg != null) && (p.pkg.applicationInfo != null)) {p.pkg.applicationInfo.flags &= ~ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;}// 复用数据,创建一个新的PackageSetting,并根据uid添加到指定集合中!PackageSetting ret = addPackageLPw(name, p.realName, p.codePath, p.resourcePath,p.legacyNativeLibraryPathString, p.primaryCpuAbiString,p.secondaryCpuAbiString, p.cpuAbiOverrideString,p.appId, p.versionCode, p.pkgFlags, p.pkgPrivateFlags,p.parentPackageName, p.childPackageNames);// 从mDisabledSysPackages移除这个packagemDisabledSysPackages.remove(name);return ret;}

2.7 PKMS.scanPackageLI(PackageParser.Package …,5)

这里可以看到PKMS对于解析得到的Package应用的处理非常谨慎,需要经过多次校验和验证最终确定它是否y一个合格并且可以使用的安装包信息!

注意该方法的入参参数是五个,不要和章节2.5的重载搞混淆了!

好了,我们接着继续往下看PKMS继续对扫描结果的进一步处理逻辑!

// 【 PackageManagerService.java 】private PackageParser.Package scanPackageLI(PackageParser.Package pkg, final int policyFlags,int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {boolean success = false;try {// 调用scanPackageDirtyLI开始解析PackageParser.Packagefinal PackageParser.Package res = scanPackageDirtyLI(pkg, policyFlags, scanFlags,currentTime, user);// 详见章节 【 2.8 】success = true;return res;} finally {if (!success && (scanFlags & SCAN_DELETE_DATA_ON_FAILURES) != 0) {// 如果解析失败,则删除相应目录// DELETE_DATA_ON_FAILURES is only used by frozen pathsdestroyAppDataLIF(pkg, UserHandle.USER_ALL,StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);destroyAppProfilesLIF(pkg, UserHandle.USER_ALL);}}}

此方法也是一个传球手,它的内部继续调用scanPackageDirtyLI方法进行下一步的处理。

2.8 PKMS.scanPackageDirtyLI(PackageParser.Package …)

各位父老相亲们,如果在章节2.6 PKMS.scanPackageInternalLI方法中读者朋友还是没有吃饱喝足的话,那么在scanPackageDirtyLI(PackageParser.Package …)我想肯定够喝饱吃足的了。

墙裂建议读者在开始分析此章节之前,该上厕所的上厕所,该干啥的先干啥,然后再集中火力争取一次性干过!

如果将扫描应用安装目录比喻为万里长征的话,那么此时就相当于进入了最后的关键时刻,成败就此一举了。我们看下扫描的后期的相关处理逻辑!

// 【 PackageManagerService.java 】/*继续处理前面扫描得到的数据并且源码中的英文注释,有保留了下来主要是为了读者能查看原文注释更加体会设计者的意图可能本人有些理解并没有完全吃透此时policyFlags的取值为: PackageParser.PARSE_IS_SYSTEM| PackageParser.PARSE_IS_SYSTEM_DIR| PackageParser.PARSE_IS_PRIVILEGED| PackageParser.PARSE_MUST_BE_APK它的值来源于前面的parseFlags*/private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg,final int policyFlags, final int scanFlags, long currentTime, UserHandle user)throws PackageManagerException {//PackageParser.Package pkg 封装了扫描的结果信息/****************** 第一步 ***********/final File scanFile = new File(pkg.codePath);//非空路径判断if (pkg.applicationInfo.getCodePath() == null ||pkg.applicationInfo.getResourcePath() == null) {// Bail out. The resource and code paths haven't been set.throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,"Code and resource paths haven't been set correctly");}// Apply policy/****************** 第二步 ***********/// 根据policyFlags设置package以及其中applicationInfo等成员信息// 对非系统应用设置pkg.coreApp = false// 处理系统 apk 标志位if ((policyFlags&PackageParser.PARSE_IS_SYSTEM) != 0) {pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;if (pkg.applicationInfo.isDirectBootAware()) {// we're direct boot aware; set for all componentsfor (PackageParser.Service s : pkg.services) {s.info.encryptionAware = s.info.directBootAware = true;}for (PackageParser.Provider p : pkg.providers) {p.info.encryptionAware = p.info.directBootAware = true;}for (PackageParser.Activity a : pkg.activities) {a.info.encryptionAware = a.info.directBootAware = true;}for (PackageParser.Activity r : pkg.receivers) {r.info.encryptionAware = r.info.directBootAware = true;}}} else {// Only allow system apps to be flagged as core apps.// data分区apk进入这个分支,coreApp的值为false,并清除一些flagpkg.coreApp = false;// clear flags not applicable to regular appspkg.applicationInfo.privateFlags &=~ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE;pkg.applicationInfo.privateFlags &=~ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE;}pkg.mTrustedOverlay = (policyFlags&PackageParser.PARSE_TRUSTED_OVERLAY) != 0;// 根据扫描时候参数中是否携带了PARSE_IS_PRIVILEGED的值进行相关的处理// 如果设置了,则设置对应pkg.applicationInfo.privateFlags,说明这个apk是特权apkif ((policyFlags&PackageParser.PARSE_IS_PRIVILEGED) != 0) {pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;}if ((policyFlags & PackageParser.PARSE_ENFORCE_CODE) != 0) {enforceCodePolicy(pkg);}// *********** 第三步 ******************//mCustomResolverComponentName是从系统资源中读出的,可以配置if (mCustomResolverComponentName != null &&mCustomResolverComponentName.getPackageName().equals(pkg.packageName)) {// 这里的用途和下面判断packageName是否为"android"有联系,// 因为调用setUpCustomResolverActivity(pkg)后mResolverReplaced为true。setUpCustomResolverActivity(pkg);}//针对包名为“android”的APK进行处理,也就是framework-res.apk,属于系统平台包!if (pkg.packageName.equals("android")) {synchronized (mPackages) {// 如果mAndroidApplication已经被初始化了,则抛异常if (mAndroidApplication != null) {Slog.w(TAG, "*************************************************");Slog.w(TAG, "Core android package being redefined.  Skipping.");Slog.w(TAG, " file=" + scanFile);Slog.w(TAG, "*************************************************");throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,"Core android package being redefined.  Skipping.");}if ((scanFlags & SCAN_CHECK_ONLY) == 0) {// 进入该分支,设置系统平台包相关的属性!// Set up information for our fall-back user intent resolution activity.mPlatformPackage = pkg;pkg.mVersionCode = mSdkVersion;mAndroidApplication = pkg.applicationInfo;// 如果上面的代码中调用了setUpCustomResolverActivity方法,// 在setUpCustomResolverActivity方法里面设置了mResolverReplaced=true.if (!mResolverReplaced) {// 建立ResolverActivity的内存对象// *********** 第四步 ******************// 如果没有调用setUpCustomResolverActivity方法,配置相应mResolveActivity的属性mResolveActivity.applicationInfo = mAndroidApplication;mResolveActivity.name = ResolverActivity.class.getName();mResolveActivity.packageName = mAndroidApplication.packageName;mResolveActivity.processName = "system:ui";mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;mResolveActivity.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NEVER;mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;mResolveActivity.theme = R.style.Theme_Material_Dialog_Alert;mResolveActivity.exported = true;mResolveActivity.enabled = true;mResolveActivity.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;mResolveActivity.configChanges = ActivityInfo.CONFIG_SCREEN_SIZE| ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE| ActivityInfo.CONFIG_SCREEN_LAYOUT| ActivityInfo.CONFIG_ORIENTATION| ActivityInfo.CONFIG_KEYBOARD| ActivityInfo.CONFIG_KEYBOARD_HIDDEN;mResolveInfo.activityInfo = mResolveActivity;mResolveInfo.priority = 0;mResolveInfo.preferredOrder = 0;mResolveInfo.match = 0;mResolveComponentName = new ComponentName(mAndroidApplication.packageName, mResolveActivity.name);}}}}if (DEBUG_PACKAGE_SCANNING) {if ((policyFlags & PackageParser.PARSE_CHATTY) != 0)Log.d(TAG, "Scanning package " + pkg.packageName);}synchronized (mPackages) {// *********** 第五步 ******************// 如果这个安装包的包名存在已经安装的列表中,说明该APP已经安装了,则不能重复安装,则抛出异常// mPackage用于保存系统内所有Package,以packageName为keyif (mPackages.containsKey(pkg.packageName)|| mSharedLibraries.containsKey(pkg.packageName)) {throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,"Application package " + pkg.packageName+ " already installed.  Skipping duplicate.");}// If we're only installing presumed-existing packages, require that the// scanned APK is both already known and at the path previously established// for it.  Previously unknown packages we pick up normally, but if we have an// a priori expectation about this package's install presence, enforce it.// With a singular exception for new system packages. When an OTA contains// a new system package, we allow the codepath to change from a system location// to the user-installed location. If we don't allow this change, any newer,// user-installed version of the application will be ignored.// 如果我们只安装已经存在的APP的包,因为是已经存在APP,// 所以可以通过PackageSetting获取它的路径,如果路径不一致,则抛异常// 系统apk不进入这个分支,SCAN_REQUIRE_KNOWN只有在扫描data分区是才会被设置!!if ((scanFlags & SCAN_REQUIRE_KNOWN) != 0) {//这个分支扫描/data/app目录会走/* 扫描data分区时,会判断mExpectingBetter中是否包含该package!如果mExpectingBetter中有该应用的包名,说明该应用是覆盖了system apk这种情况,我们后续会选择versionCode更高的显示出来*/if (mExpectingBetter.containsKey(pkg.packageName)) {logCriticalInfo(Log.WARN,"Relax SCAN_REQUIRE_KNOWN requirement for package " + pkg.packageName);} else {//获得上一次安装的信息PackageSetting known = mSettings.peekPackageLPr(pkg.packageName);if (known != null) {if (DEBUG_PACKAGE_SCANNING) {Log.d(TAG, "Examining " + pkg.codePath+ " and requiring known paths " + known.codePathString+ " & " + known.resourcePathString);}/*如果本次扫描和之前安装后的codePath或者ResourcePath 不一样,抛出异常,结束处理这种情况,我们会忽视这个apk*/if (!pkg.applicationInfo.getCodePath().equals(known.codePathString)|| !pkg.applicationInfo.getResourcePath().equals(known.resourcePathString)) {throw new PackageManagerException(INSTALL_FAILED_PACKAGE_CHANGED,"Application package " + pkg.packageName+ " found at " + pkg.applicationInfo.getCodePath()+ " but expected at " + known.codePathString+ "; ignoring.");}}}}}// Initialize package source and resource directories        // *********** 第六步 ******************// Initialize package source and resource directories// 获得扫描到的 apk 和资源的路径,下面会用到!File destCodeFile = new File(pkg.applicationInfo.getCodePath());File destResourceFile = new File(pkg.applicationInfo.getResourcePath());// 代表Package的SharedUserSettings对象SharedUserSetting suid = null;// 代表Pacakge的PacakgeSettings对象PackageSetting pkgSetting = null;/*  非系统apk,要取消以下属性,判断是否是system app的依据是这个Apk的ApplicationInfo.FLAG_SYSTEM标志位只有系统APK才配拥有下面的三个属性*/if (!isSystemApp(pkg)) {// Only system apps can use these features.pkg.mOriginalPackages = null;pkg.mRealPackage = null;pkg.mAdoptPermissions = null;}// Getting the package setting may have a side-effect, so if we// are only checking if scan would succeed, stash a copy of the// old setting to restore at the end.PackageSetting nonMutatedPs = null;// writer// *********** 第七步 ******************// 锁上mPackages对象,意味着要对这个数据结构进行写操作,里面保存的就是解析出来的包信息synchronized (mPackages) {//如果apk是共享uid的if (pkg.mSharedUserId != null) {// 如果已经定义ShareUserId,则创建Package对应的ShareduserSetting// 然后加入到PackageManangerService中的Settings对象维护的数据结构中suid = mSettings.getSharedUserLPw(pkg.mSharedUserId, 0, 0, true);if (suid == null) {// 创建ShareduserSetting失败,抛异常throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,"Creating application package " + pkg.packageName+ " for shared user failed");}if (DEBUG_PACKAGE_SCANNING) {if ((policyFlags & PackageParser.PARSE_CHATTY) != 0)Log.d(TAG, "Shared UserID " + pkg.mSharedUserId + " (uid=" + suid.userId+ "): packages=" + suid.packages);}}// Check if we are renaming from an original package name./* 对于系统 package,如果有源包,那就要尝试将 package 的名字改为源包的名字!需要找到一个合适的源包,用来改名!这里有一点需要注意,非系统apk无源码,不进入这个分支覆盖安装的系统apk,可以进入这个分支 这里的目的是找到合适的源包,用来设置包名,并且复用源包的数据!*/ PackageSetting origPackage = null;//<original-package>:应用源码所在包//<manifest package=...>:应用运行时的进程名,同样也是R.java所在包名//关于这部分可以参见博客https://my.oschina.net/u/859753/blog/701727String realName = null;// 如果存在重命名前的包名if (pkg.mOriginalPackages != null) {// This package may need to be renamed to a previously// installed name.  Let's check on that...// 获取重命名的包名final String renamed = mSettings.mRenamedPackages.get(pkg.mRealPackage);// 如果当前package被重命名过,并且有源包的名字是重命名前的名字,就将package的名字改为以前的!if (pkg.mOriginalPackages.contains(renamed)) {// This package had originally been installed as the// original name, and we have already taken care of// transitioning to the new one.  Just update the new// one to continue using the old name.// 如果这个包原来是使用原始的名字,后面变更为新的名字,所以我们只需要更新到新的名字。realName = pkg.mRealPackage;//进行重命名操作if (!pkg.packageName.equals(renamed)) {// Callers into this function may have already taken// care of renaming the package; only do it here if// it is not already done.pkg.setPackageName(renamed);}} else {// 如果不包含在mOriginalPackages中// 遍历mOriginalPackagesfor (int i=pkg.mOriginalPackages.size()-1; i>=0; i--) {// 判断pkg的原始包的某一个是否出现在mSettings里面,如果出现过,则说明之前有过if ((origPackage = mSettings.peekPackageLPr(pkg.mOriginalPackages.get(i))) != null) {// We do have the package already installed under its// original name...  should we use it?/* 对当前 package 和其源包进行校验如果源包是非系统package,不是同一分区,无法重命名为源包名,返回false;或者源包是系统package,但是PMS.mPackage中仍然有其扫描数据,源包仍然存在,返回false;*/if (!verifyPackageUpdateLPr(origPackage, pkg)) {// New package is not compatible with original.origPackage = null;continue;} else if (origPackage.sharedUser != null) {// Make sure uid is compatible between packages./* 源包是系统 package,并且共享用户 uid;如果当前的 package 和源包的共享 uid 不匹配,也会返回 false!表示无法迁移数据;*/if (!origPackage.sharedUser.name.equals(pkg.mSharedUserId)) {Slog.w(TAG, "Unable to migrate data from " + origPackage.name+ " to " + pkg.packageName + ": old uid "+ origPackage.sharedUser.name+ " differs from " + pkg.mSharedUserId);origPackage = null;continue;}// TODO: Add case when shared user id is added [b/28144775]} else {if (DEBUG_UPGRADE) Log.v(TAG, "Renaming new package "+ pkg.packageName + " to old name " + origPackage.name);}break;}}}}// mTransferedPackages用于保存那些自身数据已经被转移到其他 package 的 package!if (mTransferedPackages.contains(pkg.packageName)) {Slog.w(TAG, "Package " + pkg.packageName+ " was transferred to another, but its .apk remains");}// See comments in nonMutatedPs declarationif ((scanFlags & SCAN_CHECK_ONLY) != 0) {PackageSetting foundPs = mSettings.peekPackageLPr(pkg.packageName);if (foundPs != null) {nonMutatedPs = new PackageSetting(foundPs);}}// Just create the setting, don't add it yet. For already existing packages// the PkgSetting exists already and doesn't have to be created./* 获得当前扫描的这个package对应的packageSetting对象,如果已经存在就直接返回,不存在就创建!如果origPackage不为null,创建新的需要重命名为源包的名字!生成PackageSetting对象,对应的数据结构将序列化在/data/system/packages.xml文中中*/pkgSetting = mSettings.getPackageLPw(pkg, origPackage, realName, suid, destCodeFile,destResourceFile, pkg.applicationInfo.nativeLibraryRootDir,pkg.applicationInfo.primaryCpuAbi,pkg.applicationInfo.secondaryCpuAbi,pkg.applicationInfo.flags, pkg.applicationInfo.privateFlags,user, false);// 详见章节 【 2.8.1 】if (pkgSetting == null) {//如果失败,则抛出异常throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,"Creating application package " + pkg.packageName + " failed");}/* 如果是有原始包的,则需要进进行原始包操作非系统apk不会进入此分支对于系统package,如果origPackage不为null,改名为源包的名字*/if (pkgSetting.origPackage != null) {// If we are first transitioning from an original package,// fix up the new package's name now.  We need to do this after// looking up the package under its new name, so getPackageLP// can take care of fiddling things correctly.// 设置这个APP的包名为原始包名,并设置origPackage为nullpkg.setPackageName(origPackage.name);// File a report about this.String msg = "New package " + pkgSetting.realName+ " renamed to replace old package " + pkgSetting.name;reportSettingsProblem(Log.WARN, msg);// Make a note of it.// 如果没有设置SCAN_CHECK_ONLY,并且源包是存在的,就将源包添加到mTransferedPackages中!if ((scanFlags & SCAN_CHECK_ONLY) == 0) {mTransferedPackages.add(origPackage.name);}// No longer need to retain this.// 清空originPackage属性!pkgSetting.origPackage = null;}// 如果真实的名字不为空,即有过重命名的,则添加进去if ((scanFlags & SCAN_CHECK_ONLY) == 0 && realName != null) {// Make a note of it.mTransferedPackages.add(pkg.packageName);}/* 如果是这个安装包在mDisabledSysPackages列表中,则设置其flag为FLAG_UPDATED_SYSTEM_APPmSetting.mDisabledSysPackages中保存了所有已替换的安装包如果当前的系统apk被覆盖更新过,就添加 ApplicationInfo.FLAG_UPDATED_SYSTEM_APP 标签!*/if (mSettings.isDisabledSystemPackageLPr(pkg.packageName)) {// 如果当前安装的APK在mDisabledSysPackages列表中表示当前正在升级的system apkpkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;}// *********** 第八步 ******************   // 创建共享库// 如果Package声明了需要library或option-libaray,// PackageManagerService需要确保这些library已经被加载到mSharedLibraries中if ((policyFlags&PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {//如果不是系统目录// Check all shared libraries and map to their actual file path.// We only do this here for apps not on a system dir, because those// are the only ones that can fail an install due to this.  We// will take care of the system apps by updating all of their// library paths after the scan is done.// 检查所有共享库并映射到其具体的文件路径。我们只为非系统目录下的应用进行如下操作为,因为只有他们才需要如此做// 扫描完成后,我们将通过更新所有库路径来处理系统应用程序updateSharedLibrariesLPw(pkg, null);}// *********** 第九步 ****************** if (mFoundPolicyFile) {// 根据policy文件,找到Pacakge对应的seinfo,然后存入Package的applicationInfo中SELinuxMMAC.assignSeinfoValue(pkg);}// 处理keySet更新和Package的签名信息,还包括更新和验证pkg.applicationInfo.uid = pkgSetting.appId;// 将pkgSetting保存到pkg.mExtras中pkg.mExtras = pkgSetting;// shouldCheckUpgradeKeySetLP方法进行秘钥检查,是否一致if (shouldCheckUpgradeKeySetLP(pkgSetting, scanFlags)) {//详见章节【 2.8.2】if (checkUpgradeKeySetLP(pkgSetting, pkg)) {// 详见章节 【 2.8.3 】// 签名正确,更新本地的签名信息// We just determined the app is signed correctly, so bring// over the latest parsed certs.pkgSetting.signatures.mSignatures = pkg.mSignatures;} else {if ((policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,"Package " + pkg.packageName + " upgrade keys do not match the "+ "previously installed version");} else {// 签名错误pkgSetting.signatures.mSignatures = pkg.mSignatures;String msg = "System package " + pkg.packageName+ " signature changed; retaining data.";reportSettingsProblem(Log.WARN, msg);}}} else {// 如果不检查KetSet更新的话,就直接校验签名!try {// 重新验证签名verifySignaturesLP(pkgSetting, pkg);// We just determined the app is signed correctly, so bring// over the latest parsed certs.// 如果签名校验出现问题,这里会先恢复成本次解析的签名pkgSetting.signatures.mSignatures = pkg.mSignatures;} catch (PackageManagerException e) {// 验证错误处理流程if ((policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {throw e;}// The signature has changed, but this package is in the system// image...  let's recover!pkgSetting.signatures.mSignatures = pkg.mSignatures;// However...  if this package is part of a shared user, but it// doesn't match the signature of the shared user, let's fail.// What this means is that you can't change the signatures// associated with an overall shared user, which doesn't seem all// that unreasonable.// 但是如果是sharedUser的情况,就会报错if (pkgSetting.sharedUser != null) {if (compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,pkg.mSignatures) != PackageManager.SIGNATURE_MATCH) {throw new PackageManagerException(INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,"Signature mismatch for shared user : "+ pkgSetting.sharedUser);}}// File a report about this.String msg = "System package " + pkg.packageName+ " signature changed; retaining data.";reportSettingsProblem(Log.WARN, msg);}}// Verify that this new package doesn't have any content providers// that conflict with existing packages.  Only do this if the// package isn't already installed, since we don't want to break// things that are installed.// *********** 第十步 ****************** /*应用安装时候才会进入这个分支判断这个package使用的content  providers是否和已经存在的package冲突*/if ((scanFlags & SCAN_NEW_INSTALL) != 0) {// 获取安装包中的provider的数量final int N = pkg.providers.size();int i;for (i=0; i<N; i++) {PackageParser.Provider p = pkg.providers.get(i);if (p.info.authority != null) {String names[] = p.info.authority.split(";");for (int j = 0; j < names.length; j++) {// 如果包含同样的provider,其中mProvidersByAuthority是系统中已有的providerif (mProvidersByAuthority.containsKey(names[j])) {PackageParser.Provider other = mProvidersByAuthority.get(names[j]);final String otherPackageName =((other != null && other.getComponentName() != null) ?other.getComponentName().getPackageName() : "?");throw new PackageManagerException(INSTALL_FAILED_CONFLICTING_PROVIDER,"Can't install because provider name " + names[j]+ " (in package " + pkg.applicationInfo.packageName+ ") is already used by " + otherPackageName);}}}}}// *********** 第十一步 ****************** // 是否需要获取其他包的权限,同样的,只有系统apk才能进入该分支,用于权限继承!if ((scanFlags & SCAN_CHECK_ONLY) == 0 && pkg.mAdoptPermissions != null) {// This package wants to adopt ownership of permissions from// another package.// 如果设置mAdoptPermissions属性,对应的AndroidManifest里面的"android:adopt-permissions",// 则对应设置对应的权限!该处有不明白的,请百度"android:adopt-permissions"。就明白了for (int i = pkg.mAdoptPermissions.size() - 1; i >= 0; i--) {final String origName = pkg.mAdoptPermissions.get(i);final PackageSetting orig = mSettings.peekPackageLPr(origName);if (orig != null) {// 校验要被继承权限的package一是否存在,二是否是系统应用!// 条件不满足,无法权限继承!if (verifyPackageUpdateLPr(orig, pkg)) {Slog.i(TAG, "Adopting permissions from " + origName + " to "+ pkg.packageName);//将origName的权限转给pkgmSettings.transferPermissionsLPw(origName, pkg.packageName);}}}}}//确定进程的名称,一般为packageNamefinal String pkgName = pkg.packageName;// 从base.apk或者split.apk(如果有)中选择修改时间最晚的作为扫描时间!final long scanFileTime = getLastModifiedTime(pkg, scanFile);final boolean forceDex = (scanFlags & SCAN_FORCE_DEX) != 0;pkg.applicationInfo.processName = fixProcessName(pkg.applicationInfo.packageName,pkg.applicationInfo.processName,pkg.applicationInfo.uid);if (pkg != mPlatformPackage) {// Get all of our default paths setuppkg.applicationInfo.initForUser(UserHandle.USER_SYSTEM);}// *********** 第十二步 ****************** //设置native相关属性final String path = scanFile.getPath();//获取被扫描的应用支持的平台final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, pkgSetting);/* 扫描系统安装目录不会进入此分支设置本地库和系统平台相关的属性*/if ((scanFlags & SCAN_NEW_INSTALL) == 0) {// 在/data/data/packageName/lib下建立和CPU类型对应的目录,例如ARM平台 arm,MIP平台 mips/derivePackageAbi(pkg, scanFile, cpuAbiOverride, true /* extract libs */);// Some system apps still use directory structure for native libraries// in which case we might end up not detecting abi solely based on apk// structure. Try to detect abi based on directory structure.// 如果是系统APP,系统APP的native库统一放到/system/lib下// 所以系统不会提取系统APP目录apk包中native库if (isSystemApp(pkg) && !pkg.isUpdatedSystemApp() &&pkg.applicationInfo.primaryCpuAbi == null) {setBundledAppAbisAndRoots(pkg, pkgSetting);setNativeLibraryPaths(pkg);}} else {// 如果是移动if ((scanFlags & SCAN_MOVE) != 0) {// We haven't run dex-opt for this move (since we've moved the compiled output too)// but we already have this packages package info in the PackageSetting. We just// use that and derive the native library path based on the new codepath.//设置支持的类型pkg.applicationInfo.primaryCpuAbi = pkgSetting.primaryCpuAbiString;pkg.applicationInfo.secondaryCpuAbi = pkgSetting.secondaryCpuAbiString;}// Set native library paths again. For moves, the path will be updated based on the// ABIs we've determined above. For non-moves, the path will be updated based on the// ABIs we determined during compilation, but the path will depend on the final// package path (after the rename away from the stage path).setNativeLibraryPaths(pkg);}// This is a special case for the "system" package, where the ABI is// dictated by the zygote configuration (and init.rc). We should keep track// of this ABI so that we can deal with "normal" applications that run under// the same UID correctly./* "系统"安装包有特殊情况,其中ABI是由zygote配置(init.rc)指定。我们应该跟踪这个ABI。这样我们就可以在相同的UID下,正确处理"正常"的应用程序。此处处理的是framework-res.apk*/if (mPlatformPackage == pkg) {pkg.applicationInfo.primaryCpuAbi = VMRuntime.getRuntime().is64Bit() ?Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0];}// If there's a mismatch between the abi-override in the package setting// and the abiOverride specified for the install. Warn about this because we// would've already compiled the app without taking the package setting into// account.if ((scanFlags & SCAN_NO_DEX) == 0 && (scanFlags & SCAN_NEW_INSTALL) != 0) {// 如果程序中设置的abi-override和为安装指定的abiOverride之间不匹配if (cpuAbiOverride == null && pkgSetting.cpuAbiOverrideString != null) {Slog.w(TAG, "Ignoring persisted ABI override " + cpuAbiOverride +" for package " + pkg.packageName);}}// 初始化abi属性pkgSetting.primaryCpuAbiString = pkg.applicationInfo.primaryCpuAbi;pkgSetting.secondaryCpuAbiString = pkg.applicationInfo.secondaryCpuAbi;pkgSetting.cpuAbiOverrideString = cpuAbiOverride;// Copy the derived override back to the parsed package, so that we can// update the package settings accordingly.//赋值abi属性到pkg中pkg.cpuAbiOverride = cpuAbiOverride;if (DEBUG_ABI_SELECTION) {Slog.d(TAG, "Resolved nativeLibraryRoot for " + pkg.applicationInfo.packageName+ " to root=" + pkg.applicationInfo.nativeLibraryRootDir + ", isa="+ pkg.applicationInfo.nativeLibraryRootRequiresIsa);}// Push the derived path down into PackageSettings so we know what to// clean up at uninstall time.//保存lib路径,方便卸载时清理pkgSetting.legacyNativeLibraryPathString = pkg.applicationInfo.nativeLibraryRootDir;if (DEBUG_ABI_SELECTION) {Log.d(TAG, "Abis for package[" + pkg.packageName + "] are" +" primary=" + pkg.applicationInfo.primaryCpuAbi +" secondary=" + pkg.applicationInfo.secondaryCpuAbi);}if ((scanFlags & SCAN_BOOTING) == 0 && pkgSetting.sharedUser != null) {// We don't do this here during boot because we can do it all// at once after scanning all existing packages.//// We also do this *before* we perform dexopt on this package, so that// we can avoid redundant dexopts, and also to make sure we've got the// code and package path correct.// 调整共享用户的abiadjustCpuAbisForSharedUserLPw(pkgSetting.sharedUser.packages,pkg, true /* boot complete */);}if (mFactoryTest && pkg.requestedPermissions.contains(android.Manifest.permission.FACTORY_TEST)) {pkg.applicationInfo.flags |= ApplicationInfo.FLAG_FACTORY_TEST;}if (isSystemApp(pkg)) {pkgSetting.isOrphaned = true;}ArrayList<PackageParser.Package> clientLibPkgs = null;if ((scanFlags & SCAN_CHECK_ONLY) != 0) {if (nonMutatedPs != null) {synchronized (mPackages) {mSettings.mPackages.put(nonMutatedPs.name, nonMutatedPs);}}return pkg;}// *********** 第十三步 ****************** // Only privileged apps and updated privileged apps can add child packages.// 处理特权apk的子包,只有特权apk才能添加子包;// 特权apk包括两部分:// 1、特定uid的app// 2、framework-res.apk 和system/priv-app目录下的apk!if (pkg.childPackages != null && !pkg.childPackages.isEmpty()) {if ((policyFlags & PARSE_IS_PRIVILEGED) == 0) {throw new PackageManagerException("Only privileged apps and updated "+ "privileged apps can add child packages. Ignoring package "+ pkg.packageName);}final int childCount = pkg.childPackages.size();for (int i = 0; i < childCount; i++) {PackageParser.Package childPkg = pkg.childPackages.get(i);if (mSettings.hasOtherDisabledSystemPkgWithChildLPr(pkg.packageName,childPkg.packageName)) {throw new PackageManagerException("Cannot override a child package of "+ "another disabled system app. Ignoring package " + pkg.packageName);}}}// writer/*  处理系统APK更新时,链接库的改变如果是系统package,并且他之前更新过,就要尝试对共享库lib进行更新!只有系统package才能添加共享lib*/synchronized (mPackages) {if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {// Only system apps can add new shared libraries.// 只有系统应用才可以添加新的共享库if (pkg.libraryNames != null) {//遍历库for (int i=0; i<pkg.libraryNames.size(); i++) {String name = pkg.libraryNames.get(i);boolean allowed = false;// 是需要更新的系统APPif (pkg.isUpdatedSystemApp()) {// New library entries can only be added through the// system image.  This is important to get rid of a lot// of nasty edge cases: for example if we allowed a non-// system update of the app to add a library, then uninstalling// the update would make the library go away, and assumptions// we made such as through app install filtering would now// have allowed apps on the device which aren't compatible// with it.  Better to just have the restriction here, be// conservative, and create many fewer cases that can negatively// impact the user experience.// 调用getDisabledSystemPkgLPr方法获取其对应的PackageSettingfinal PackageSetting sysPs = mSettings.getDisabledSystemPkgLPr(pkg.packageName);// 进行对比if (sysPs.pkg != null && sysPs.pkg.libraryNames != null) {for (int j=0; j<sysPs.pkg.libraryNames.size(); j++) {if (name.equals(sysPs.pkg.libraryNames.get(j))) {//如果包名相同,可以添加共享库allowed = true;break;}}}} else {//可以添加共享库allowed = true;}// 如果可以添加共享库if (allowed) {// 如果共享库里面没有这个库的名字,则添加if (!mSharedLibraries.containsKey(name)) {mSharedLibraries.put(name, new SharedLibraryEntry(null, pkg.packageName));} else if (!name.equals(pkg.packageName)) {Slog.w(TAG, "Package " + pkg.packageName + " library "+ name + " already exists; skipping");}} else {Slog.w(TAG, "Package " + pkg.packageName + " declares lib "+ name + " that is not declared on system image; skipping");}}// 如果不是启动,我们需要更新共享库应用程序。如果我们在启动过程中,这一切都将在扫描完成后完成if ((scanFlags & SCAN_BOOTING) == 0) {                       // If we are not booting, we need to update any applications// that are clients of our shared library.  If we are booting,// this will all be done once the scan is complete.//更新共享库clientLibPkgs = updateAllSharedLibrariesLPw(pkg);}}}}// 处理和冻结相关的逻辑if ((scanFlags & SCAN_BOOTING) != 0) {// 如果是开机扫描,不需要冻结,因为没有应用在此时可以运行// No apps can run during boot scan, so they don't need to be frozen} else if ((scanFlags & SCAN_DONT_KILL_APP) != 0) {// 如果扫描过程中不允许 kill app,那就不会冻结!// Caller asked to not kill app, so it's probably not frozen} else if ((scanFlags & SCAN_IGNORE_FROZEN) != 0) {// Caller asked us to ignore frozen check for some reason; they// probably didn't know the package name// 如果扫描过程中显式指定忽略冻结,那就不会冻结!} else {// We're doing major surgery on this package, so it better be frozen// right now to keep it from launching// 其他情况,我们会默认冻结该应用,防止其启动!checkPackageFrozen(pkgName);}// Also need to kill any apps that are dependent on the library.// 杀掉所有依赖于库文件的应用,因为库发生了更新!if (clientLibPkgs != null) {for (int i=0; i<clientLibPkgs.size(); i++) {PackageParser.Package clientPkg = clientLibPkgs.get(i);killApplication(clientPkg.applicationInfo.packageName,clientPkg.applicationInfo.uid, "update lib");}}// Make sure we're not adding any bogus keyset info// 添加秘钥集信息KeySetManagerService ksms = mSettings.mKeySetManagerService;ksms.assertScannedPackageValid(pkg);// writerTrace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings");//记录trace事件// *********** 第十四步 ****************** boolean createIdmapFailed = false;synchronized (mPackages) {// We don't expect installation to fail beyond this pointif (pkgSetting.pkg != null) {// Note that |user| might be null during the initial boot scan. If a codePath// for an app has changed during a boot scan, it's due to an app update that's// part of the system partition and marker changes must be applied to all users.maybeRenameForeignDexMarkers(pkgSetting.pkg, pkg,(user != null) ? user : UserHandle.ALL);}// Add the new setting to mSettings// 把pkgSetting保存到Settings的变量mPackages中,String对应此包名,mSettings.insertPackageSettingLPw(pkgSetting, pkg);// 插入扫描到的安装包信息// Add the new setting to mPackages// 将pkg保存到PackageManagerService的成员变量mPackages中,key为包名mPackages.put(pkg.applicationInfo.packageName, pkg);// Make sure we don't accidentally delete its data.//清理空间 删除 已经的卸载的、但还占用存储空间的软件final Iterator<PackageCleanItem> iter = mSettings.mPackagesToBeCleaned.iterator();while (iter.hasNext()) {PackageCleanItem item = iter.next();if (pkgName.equals(item.packageName)) {iter.remove();}}// Take care of first install / last update times.            // 更新安装时间:即首次安装或者最后一次更新时间// 如果有当前时间          if (currentTime != 0) {// 若有没有首次安装时间,则说明是首次安装,即设置安装时间if (pkgSetting.firstInstallTime == 0) {pkgSetting.firstInstallTime = pkgSetting.lastUpdateTime = currentTime;} else if ((scanFlags&SCAN_UPDATE_TIME) != 0) {// 如果有首次安装时间,则说明是更新安装,则设置最后更新时间pkgSetting.lastUpdateTime = currentTime;}} else if (pkgSetting.firstInstallTime == 0) {// We need *something*.  Take time time stamp of the file.// 如果没有当前时间且没有首次安装时间,则设置首次时间和最后更新时间等于当前扫描时间pkgSetting.firstInstallTime = pkgSetting.lastUpdateTime = scanFileTime;} else if ((policyFlags&PackageParser.PARSE_IS_SYSTEM_DIR) != 0) {if (scanFileTime != pkgSetting.timeStamp) {// A package on the system image has changed; consider this// to be an update.pkgSetting.lastUpdateTime = scanFileTime;}}// Add the package's KeySets to the global KeySetManagerService// 添加安装包的到全局的KeySetManagerService里面ksms.addScannedPackageLPw(pkg);、         // *********** 第十五步 ****************** /* 在此之前,四大组件的信息都是Package对象的私有变量,通过后续的代码,将他们注册到PKMS里面。这样PKMS就有了所有的组件信息,这样后续就能提供组件的在线查询服务*/int N = pkg.providers.size();StringBuilder r = null;int i;// 处理该Package中的Provider信息,将其注册到PKMS的mProvider中for (i=0; i<N; i++) {PackageParser.Provider p = pkg.providers.get(i);// 设置进程名称。如果在AndroidManifest里面配置了进程名称,就以配置为准,如果没有配置,就是默认包名p.info.processName = fixProcessName(pkg.applicationInfo.processName,p.info.processName, pkg.applicationInfo.uid);mProviders.addProvider(p);p.syncable = p.info.isSyncable;if (p.info.authority != null) {String names[] = p.info.authority.split(";");p.info.authority = null;for (int j = 0; j < names.length; j++) {if (j == 1 && p.syncable) {// We only want the first authority for a provider to possibly be// syncable, so if we already added this provider using a different// authority clear the syncable flag. We copy the provider before// changing it because the mProviders object contains a reference// to a provider that we don't want to change.// Only do this for the second authority since the resulting provider// object can be the same for all future authorities for this provider.p = new PackageParser.Provider(p);p.syncable = false;}if (!mProvidersByAuthority.containsKey(names[j])) {mProvidersByAuthority.put(names[j], p);if (p.info.authority == null) {p.info.authority = names[j];} else {p.info.authority = p.info.authority + ";" + names[j];}if (DEBUG_PACKAGE_SCANNING) {if ((policyFlags & PackageParser.PARSE_CHATTY) != 0)Log.d(TAG, "Registered content provider: " + names[j]+ ", className = " + p.info.name + ", isSyncable = "+ p.info.isSyncable);}} else {PackageParser.Provider other = mProvidersByAuthority.get(names[j]);Slog.w(TAG, "Skipping provider name " + names[j] +" (in package " + pkg.applicationInfo.packageName +"): name already used by "+ ((other != null && other.getComponentName() != null)? other.getComponentName().getPackageName() : "?"));}}}if ((policyFlags&PackageParser.PARSE_CHATTY) != 0) {if (r == null) {r = new StringBuilder(256);} else {r.append(' ');}r.append(p.info.name);}}if (r != null) {if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Providers: " + r);}N = pkg.services.size();r = null;// 处理该Package中的Service信息,将其注册到PKMS的mServices中for (i=0; i<N; i++) {PackageParser.Service s = pkg.services.get(i);s.info.processName = fixProcessName(pkg.applicationInfo.processName,s.info.processName, pkg.applicationInfo.uid);mServices.addService(s);if ((policyFlags&PackageParser.PARSE_CHATTY) != 0) {if (r == null) {r = new StringBuilder(256);} else {r.append(' ');}r.append(s.info.name);}}if (r != null) {if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Services: " + r);}N = pkg.receivers.size();r = null;// 处理该Package中的receive信息,然后注册到PKMS的mReceivers中for (i=0; i<N; i++) {PackageParser.Activity a = pkg.receivers.get(i);a.info.processName = fixProcessName(pkg.applicationInfo.processName,a.info.processName, pkg.applicationInfo.uid);mReceivers.addActivity(a, "receiver");if ((policyFlags&PackageParser.PARSE_CHATTY) != 0) {if (r == null) {r = new StringBuilder(256);} else {r.append(' ');}r.append(a.info.name);}}if (r != null) {if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Receivers: " + r);}N = pkg.activities.size();r = null;// 处理该Package中的activity信息,然后注册到PKMS的mActivities中for (i=0; i<N; i++) {PackageParser.Activity a = pkg.activities.get(i);a.info.processName = fixProcessName(pkg.applicationInfo.processName,a.info.processName, pkg.applicationInfo.uid);mActivities.addActivity(a, "activity");if ((policyFlags&PackageParser.PARSE_CHATTY) != 0) {if (r == null) {r = new StringBuilder(256);} else {r.append(' ');}r.append(a.info.name);}}if (r != null) {if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Activities: " + r);}// 处理该Package中的PermissionGroups信息,注册到PKMSS.mPermissionGroups 中N = pkg.permissionGroups.size();r = null;for (i=0; i<N; i++) {PackageParser.PermissionGroup pg = pkg.permissionGroups.get(i);PackageParser.PermissionGroup cur = mPermissionGroups.get(pg.info.name);final String curPackageName = cur == null ? null : cur.info.packageName;final boolean isPackageUpdate = pg.info.packageName.equals(curPackageName);// 如果isPackageUpdate为 true,说明要更新权限组的信息!// 如果cur为 null,说明是新添加的权限组信息!if (cur == null || isPackageUpdate) {mPermissionGroups.put(pg.info.name, pg); 添加到PKMS的mPermissionGroups中!if ((policyFlags&PackageParser.PARSE_CHATTY) != 0) {if (r == null) {r = new StringBuilder(256);} else {r.append(' ');}if (isPackageUpdate) {r.append("UPD:");}r.append(pg.info.name);}} else {Slog.w(TAG, "Permission group " + pg.info.name + " from package "+ pg.info.packageName + " ignored: original from "+ cur.info.packageName);if ((policyFlags&PackageParser.PARSE_CHATTY) != 0) {if (r == null) {r = new StringBuilder(256);} else {r.append(' ');}r.append("DUP:");r.append(pg.info.name);}}}if (r != null) {if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Permission Groups: " + r);}N = pkg.permissions.size();r = null;//处理该Package中的定义Permission和Permission-tree信息for (i=0; i<N; i++) {PackageParser.Permission p = pkg.permissions.get(i);// Assume by default that we did not install this permission into the system.// 默认取消掉PermissionInfo.FLAG_INSTALLED标志位p.info.flags &= ~PermissionInfo.FLAG_INSTALLED;// Now that permission groups have a special meaning, we ignore permission// groups for legacy apps to prevent unexpected behavior. In particular,// permissions for one app being granted to someone just becase they happen// to be in a group defined by another app (before this had no implications).// 设置权限所属的group,前提是只有Android5.1以后才支持Permission Groups!if (pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {p.group = mPermissionGroups.get(p.info.group);// Warn for a permission in an unknown group.if (p.info.group != null && p.group == null) {Slog.w(TAG, "Permission " + p.info.name + " from package "+ p.info.packageName + " in an unknown group " + p.info.group);}}// 从Settings中获得权限管理集合,如果该权限是一个Permission-tree,// 那就返回mSettings.mPermissionTrees;否则,返回mSettings.mPermissions!ArrayMap<String, BasePermission> permissionMap =p.tree ? mSettings.mPermissionTrees: mSettings.mPermissions;// 尝试获得上次安装时该权限对应的BasePermission对象!BasePermission bp = permissionMap.get(p.info.name);// Allow system apps to redefine non-system permissions// 允许系统应用来重新定义非系统权限!// 如果上次安装时的BasePermission不为 null,但是当前解析的package不是上次安装时该权限的定义者!if (bp != null && !Objects.equals(bp.sourcePackage, p.info.packageName)) {// 判断上一次安装时,定义该权限的package是否是系统应用!// 如果是currentOwnerIsSystem为true!final boolean currentOwnerIsSystem = (bp.perm != null&& isSystemApp(bp.perm.owner));if (isSystemApp(p.owner)) {// 如果当前解析的定义了该权限的package是系统app,那么进入这里// 如果上次安装时,该权限是一个BasePermission.TYPE_BUILTIN系统权限,且bp.perm为null,// 即:拥有者未知,那么这里我们将这个system package分配给这个权限!if (bp.type == BasePermission.TYPE_BUILTIN && bp.perm == null) {// It's a built-in permission and no owner, take ownership nowbp.packageSetting = pkgSetting;bp.perm = p;bp.uid = pkg.applicationInfo.uid;bp.sourcePackage = p.info.packageName;p.info.flags |= PermissionInfo.FLAG_INSTALLED;} else if (!currentOwnerIsSystem) {// 判断上一次安装时,定义该权限的package不是系统应用,而定义相同权限的// 本次解析的package是系统应用,那么该权限会被系统应用重新定义!String msg = "New decl " + p.owner + " of permission  "+ p.info.name + " is system; overriding " + bp.sourcePackage;reportSettingsProblem(Log.WARN, msg);bp = null;}}}// 因为bp为null,所谓我们会使用系统应用重新定义权限,如果是permission-tree,会被添加到 // mSettings.mPermissionTrees 中!if (bp == null) {bp = new BasePermission(p.info.name, p.info.packageName,BasePermission.TYPE_NORMAL);permissionMap.put(p.info.name, bp);}// 重新分配拥有者为该系统应用!if (bp.perm == null) {if (bp.sourcePackage == null|| bp.sourcePackage.equals(p.info.packageName)) {BasePermission tree = findPermissionTreeLP(p.info.name);if (tree == null|| tree.sourcePackage.equals(p.info.packageName)) {bp.packageSetting = pkgSetting;bp.perm = p;bp.uid = pkg.applicationInfo.uid;bp.sourcePackage = p.info.packageName;p.info.flags |= PermissionInfo.FLAG_INSTALLED;if ((policyFlags&PackageParser.PARSE_CHATTY) != 0) {if (r == null) {r = new StringBuilder(256);} else {r.append(' ');}r.append(p.info.name);}} else {Slog.w(TAG, "Permission " + p.info.name + " from package "+ p.info.packageName + " ignored: base tree "+ tree.name + " is from package "+ tree.sourcePackage);}} else {Slog.w(TAG, "Permission " + p.info.name + " from package "+ p.info.packageName + " ignored: original from "+ bp.sourcePackage);}} else if ((policyFlags&PackageParser.PARSE_CHATTY) != 0) {if (r == null) {r = new StringBuilder(256);} else {r.append(' ');}r.append("DUP:");r.append(p.info.name);}if (bp.perm == p) {// 如果上次安装时的权限的定义者,就是本次解析的package,设置protectionLevel!bp.protectionLevel = p.info.protectionLevel;}}if (r != null) {if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Permissions: " + r);}N = pkg.instrumentation.size();r = null;//  注册pkg里面的instrumentation到PackageManagerService的mInstrumentation中// Instrumentation用来跟踪本应用内的application及activity的生命周期for (i=0; i<N; i++) {PackageParser.Instrumentation a = pkg.instrumentation.get(i);a.info.packageName = pkg.applicationInfo.packageName;a.info.sourceDir = pkg.applicationInfo.sourceDir;a.info.publicSourceDir = pkg.applicationInfo.publicSourceDir;a.info.splitSourceDirs = pkg.applicationInfo.splitSourceDirs;a.info.splitPublicSourceDirs = pkg.applicationInfo.splitPublicSourceDirs;a.info.dataDir = pkg.applicationInfo.dataDir;a.info.deviceProtectedDataDir = pkg.applicationInfo.deviceProtectedDataDir;a.info.credentialProtectedDataDir = pkg.applicationInfo.credentialProtectedDataDir;a.info.nativeLibraryDir = pkg.applicationInfo.nativeLibraryDir;a.info.secondaryNativeLibraryDir = pkg.applicationInfo.secondaryNativeLibraryDir;mInstrumentation.put(a.getComponentName(), a);if ((policyFlags&PackageParser.PARSE_CHATTY) != 0) {if (r == null) {r = new StringBuilder(256);} else {r.append(' ');}r.append(a.info.name);}}if (r != null) {if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Instrumentation: " + r);}if (pkg.protectedBroadcasts != null) {// 处理该Package中的protectedBroadcasts信息N = pkg.protectedBroadcasts.size();for (i=0; i<N; i++) {mProtectedBroadcasts.add(pkg.protectedBroadcasts.get(i));}}// 更新 PackageSettings中的时间戳pkgSetting.setTimeStamp(scanFileTime);// Create idmap files for pairs of (packages, overlay packages).// Note: "android", ie framework-res.apk, is handled by native layers.// 处理overlay设置if (pkg.mOverlayTarget != null) {// This is an overlay package.if (pkg.mOverlayTarget != null && !pkg.mOverlayTarget.equals("android")) {if (!mOverlays.containsKey(pkg.mOverlayTarget)) {mOverlays.put(pkg.mOverlayTarget,new ArrayMap<String, PackageParser.Package>());}ArrayMap<String, PackageParser.Package> map = mOverlays.get(pkg.mOverlayTarget);map.put(pkg.packageName, pkg);PackageParser.Package orig = mPackages.get(pkg.mOverlayTarget);if (orig != null && !createIdmapForPackagePairLI(orig, pkg)) {createIdmapFailed = true;}}} else if (mOverlays.containsKey(pkg.packageName) &&!pkg.packageName.equals("android")) {// This is a regular package, with one or more known overlay packages.createIdmapsForPackageLI(pkg);}}Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);if (createIdmapFailed) {throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,"scanPackageLI failed to createIdmap");}return pkg;}

上述源码内容何其多,细节零碎一大推,整得我抓耳又挠腮!让我来理一理啊,细枝末节一边扔,我们只抓重点啊。上述逻辑走主干啊,可以归为如下步骤啊:

  • 第一步:检查被扫描的安装包的代码路径或者资源路径是否存在,两者如果不存在则抛出异常

  • 第二步:根据policyFlags设置package以及其中applicationInfo等成员信息(policyFlags的值来源于前面的parseFlags)

  • 第三步:处理CustomResolver情况,即客户定制化ResolverActivity的情况

  • 第四步:单独处理framework-res.apk信息,并且如果没有调用setUpCustomResolverActivity方法,配置相应mResolveActivity

  • 第五步:如果这个被扫描到的安装包的包名存在已经安装的列表中,说明该APP已经安装了,则不能重复安装,则抛出异常

  • 第六步:初始化包的安装目录(代码目录与资源目录)

  • 第七步:检查并处理系统安装包被重命名的情况

  • 第八步:处理非系统应用共享库情况

    在进行系统应用安装目录扫描时,不会进入此分支的处理逻辑的。

  • 第九步:处理被扫描的安装包keySet更新和签名校验的情况

  • 第十步:检查安装包中的provider是不是和现在系统中已经存在包的provider冲突

  • 第十一步:检测当前安装包对其他包的所拥有的权限(比如系统应用)

  • 第十二步:设置应用安装包native so库相关情况

  • 第十三步:处理特权APK子包,以及处理系统APK更新时,链接库的改变

  • 第十四步:将扫描得到的Package信息填充到对应PackageSetting中,并且将其加入到PKMS的mPackages中进行管理

  • 第十五步:将上述的的安装包的内容从pkg里面映射到PKMS里面,主要就是向PKMS注册具体的相关组件,从而可以供第三方应用进行查询,其中的核心组件信息如下:

    • 获取pkg对应的provider,并注册到PKMS的变量mProviders里面
    • 获取pkg对应的service,并注册到PKMS的变量mServices里面
    • 获取pkg对应的receiver,并注册到PKMS的变量mReceivers里面
    • 获取pkg对应的activity,并注册到PKMS的变量mActivities里面
    • 获取pkg对应的grouppermission,并注册到PKMS的变量mPermissionGroups里面
    • 获取pkg对应的instrumentation,并注册到PKMS的变量mInstrumentation里面

至此我们基本完成了系统应用安装目录的扫描工作,并且完成了系统应用安装目录应用相关组件的注册工作。

这里我的工作到此结束了,至于读者你的吗,那就看读者想要掌握到什么情况了。

2.8.1 Settings.getPackageLPw(PackageParser.Package pkg …)

可以看到在该方法中它会调用重载的另外一个getPackageLPw方法。

// 【 Settings.java 】PackageSetting getPackageLPw(PackageParser.Package pkg, PackageSetting origPackage,String realName, SharedUserSetting sharedUser, File codePath, File resourcePath,String legacyNativeLibraryPathString, String primaryCpuAbi, String secondaryCpuAbi,int pkgFlags, int pkgPrivateFlags, UserHandle user, boolean add) {final String name = pkg.packageName;final String parentPackageName = (pkg.parentPackage != null)? pkg.parentPackage.packageName : null;List<String> childPackageNames = null;if (pkg.childPackages != null) {final int childCount = pkg.childPackages.size();childPackageNames = new ArrayList<>(childCount);for (int i = 0; i < childCount; i++) {String childPackageName = pkg.childPackages.get(i).packageName;childPackageNames.add(childPackageName);}}PackageSetting p = getPackageLPw(name, origPackage, realName, sharedUser, codePath,resourcePath, legacyNativeLibraryPathString, primaryCpuAbi, secondaryCpuAbi,pkg.mVersionCode, pkgFlags, pkgPrivateFlags, user, add, true /* allowInstall */,parentPackageName, childPackageNames);return p;}

此时我么要注意getPackageLPw方法,特别注意如下几个传入的参数的取值:

  • boolean add:传入的值为 false
  • boolean allowInstall:传入的值为 true

该方法传入的参数的是本次扫描解析获得的APK的数据!

// 【 Settings.java】
private PackageSetting getPackageLPw(String name, PackageSetting origPackage,String realName, SharedUserSetting sharedUser, File codePath, File resourcePath,String legacyNativeLibraryPathString, String primaryCpuAbiString,String secondaryCpuAbiString, int vc, int pkgFlags, int pkgPrivateFlags,UserHandle installUser, boolean add, boolean allowInstall, String parentPackage,List<String> childPackageNames) {// 尝试获得之前的安装数据!PackageSetting p = mPackages.get(name);UserManagerService userManager = UserManagerService.getInstance();// 如果p不为null,说明这个apk之前就存在!// 下面就要比较codePath!if (p != null) {p.primaryCpuAbiString = primaryCpuAbiString;p.secondaryCpuAbiString = secondaryCpuAbiString;if (childPackageNames != null) {p.childPackageNames = new ArrayList<>(childPackageNames);}if (!p.codePath.equals(codePath)) {if ((p.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0) {// 这是被更新的system app的情况,此时在system分区和app分区都会有该apk,我们只需要// 将高versionCode的apk显示出来即可!Slog.w(PackageManagerService.TAG, "Trying to update system app code path from "+ p.codePathString + " to " + codePath.toString());} else {// 这种情况是说明这个system apk的路径发生了变化!// 或者说这个应用是一个第三方应用!Slog.i(PackageManagerService.TAG, "Package " + name + " codePath changed from "+ p.codePath + " to " + codePath + "; Retaining data and using new");// 如果当前扫描的是system apk,并且其不是被覆盖安装的,即getDisabledSystemPkgLPr为null;// 更新所有用户下的安装状态为true!if ((pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0 &&getDisabledSystemPkgLPr(name) == null) {List<UserInfo> allUserInfos = getAllUsers();if (allUserInfos != null) {for (UserInfo userInfo : allUserInfos) {p.setInstalled(true, userInfo.id);}}}p.legacyNativeLibraryPathString = legacyNativeLibraryPathString;}}if (p.sharedUser != sharedUser) { // 如果共享uid不匹配,就需要创建新的PackageSetting替换以前的!PackageManagerService.reportSettingsProblem(Log.WARN,"Package " + name + " shared user changed from "+ (p.sharedUser != null ? p.sharedUser.name : "<nothing>")+ " to "+ (sharedUser != null ? sharedUser.name : "<nothing>")+ "; replacing with new");p = null;} else {// 如果共享uid匹配,或者没有使用共享,进入这里!// 如果我们扫描的是一个system分区的apk,  无论其之前是否在data分区存在!// 我们都会给上一次的安装信息设置FLAG_SYSTEM和PRIVATE_FLAG_PRIVILEGED标志位;p.pkgFlags |= pkgFlags & ApplicationInfo.FLAG_SYSTEM;p.pkgPrivateFlags |= pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;}}// 处理需要重新创建PackageSetting的情况!// 如果p为null,说明这个系统apk是通过OTA升级方式新添加的(也可能是第一次系统启动),或者shareUserId不匹配!// 如果有源包,就用源包的命名,创建新的PackageSetting对象,并源包的数据来初始化!// 没有源包,就用当前包的命名,创建新的PackageSetting对象!if (p == null) {if (origPackage != null) {//源包不为null的情况,进入这里,使用本次扫描的信息创建新的PackageSetting!p = new PackageSetting(origPackage.name, name, codePath, resourcePath,legacyNativeLibraryPathString, primaryCpuAbiString, secondaryCpuAbiString,null /* cpuAbiOverrideString */, vc, pkgFlags, pkgPrivateFlags,parentPackage, childPackageNames);if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, "Package "+ name + " is adopting original package " + origPackage.name);PackageSignatures s = p.signatures;p.copyFrom(origPackage);p.signatures = s;p.sharedUser = origPackage.sharedUser;p.appId = origPackage.appId;p.origPackage = origPackage;p.getPermissionsState().copyFrom(origPackage.getPermissionsState());// 重命名为源包的名字后,将新旧名字加入mRenamedPackages集合!mRenamedPackages.put(name, origPackage.name);name = origPackage.name;p.setTimeStamp(codePath.lastModified());} else {// 源包为null的情况,进入这里,使用本次扫描的信息创建新的PackageSetting!p = new PackageSetting(name, realName, codePath, resourcePath,legacyNativeLibraryPathString, primaryCpuAbiString, secondaryCpuAbiString,null /* cpuAbiOverrideString */, vc, pkgFlags, pkgPrivateFlags,parentPackage, childPackageNames);p.setTimeStamp(codePath.lastModified());p.sharedUser = sharedUser;// 系统apk不会进入这个分支!if ((pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) {if (DEBUG_STOPPED) {RuntimeException e = new RuntimeException("here");e.fillInStackTrace();Slog.i(PackageManagerService.TAG, "Stopping package " + name, e);}List<UserInfo> users = getAllUsers();final int installUserId = installUser != null ? installUser.getIdentifier() : 0;if (users != null && allowInstall) {for (UserInfo user : users) {final boolean installed = installUser == null|| (installUserId == UserHandle.USER_ALL&& !isAdbInstallDisallowed(userManager, user.id))|| installUserId == user.id;p.setUserState(user.id, 0, COMPONENT_ENABLED_STATE_DEFAULT,installed,true, // stopped,true, // notLaunchedfalse, // hiddenfalse, // suspendednull, null, null,false, // blockUninstallINTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED, 0);writePackageRestrictionsLPr(user.id);}}}// 设置系统 package 的 appid// 如果是共享用户 id,那 appId 就是共享用户 id;// 如果不是共享用户 id,就看系统 app 是否被覆盖更新过,如果有,就克隆更新前的旧数据,初始化 appId,权限等等// 如果系统 app 没有被覆盖更新过,就创建新的 uid!if (sharedUser != null) {p.appId = sharedUser.userId;} else {PackageSetting dis = mDisabledSysPackages.get(name);if (dis != null) {if (dis.signatures.mSignatures != null) {p.signatures.mSignatures = dis.signatures.mSignatures.clone();}p.appId = dis.appId;p.getPermissionsState().copyFrom(dis.getPermissionsState());List<UserInfo> users = getAllUsers();if (users != null) {for (UserInfo user : users) {int userId = user.id;p.setDisabledComponentsCopy(dis.getDisabledComponents(userId), userId);p.setEnabledComponentsCopy(dis.getEnabledComponents(userId), userId);}}// 将这个新的 PackageSetting 根据 uid 添加到 mUserIds 或者 mOtherUserIds 中!addUserIdLPw(p.appId, p, name);} else {// 分配一个新的 uid,并将映射关系保存到 mUserIds 中!p.appId = newUserIdLPw(p);}}}if (p.appId < 0) {PackageManagerService.reportSettingsProblem(Log.WARN,"Package " + name + " could not be assigned a valid uid");return null;}if (add) { // add为false,这里不添加,添加的操作在PKMS.scanPackageDirtyLI方法中!addPackageSettingLPw(p, name, sharedUser);}} else {if (installUser != null && allowInstall) {List<UserInfo> users = getAllUsers();if (users != null) {for (UserInfo user : users) {if ((installUser.getIdentifier() == UserHandle.USER_ALL&& !isAdbInstallDisallowed(userManager, user.id))|| installUser.getIdentifier() == user.id) {boolean installed = p.getInstalled(user.id);if (!installed) {p.setInstalled(true, user.id);writePackageRestrictionsLPr(user.id);}}}}}}// 返回查到或者是创建的新的系统 package 对应的 PackageSetting 对象!return p;
}

可以看到上述方法的处理逻辑就是获得当前扫描的这个package对应的packageSetting对象,如果已经存在就直接返回,不存在就创建!如果origPackage不为null,创建新的需要重命名为源包的名字!生成PackageSetting对象,对应的数据结构将序列化在/data/system/packages.xml文中。

2.8.2 PKMS.shouldCheckUpgradeKeySetLP( …)

这里需要注意的是,只有当被扫描的应用以前被有安装过的历史记录时候,才会有可能进入此分支,否则此时她对应的PackageSettings里面的keySetData为空。这点需要特别注意。

这个方法主要检查被扫描应用对应的PackageSettings的密钥集合是否和Settings大管家中的一致,如果不一致则返回false,如果一致则返回true。

// 【 PackageManagerService.java 】private boolean shouldCheckUpgradeKeySetLP(PackageSetting oldPs, int scanFlags) {// 判断是否可以进行升级验证的条件if (oldPs == null || (scanFlags&SCAN_INITIAL) != 0 || oldPs.sharedUser != null|| !oldPs.keySetData.isUsingUpgradeKeySets()) {return false;}// 获取ksmsKeySetManagerService ksms = mSettings.mKeySetManagerService;// 获取keySet数组long[] upgradeKeySets = oldPs.keySetData.getUpgradeKeySets();// 遍历keySet数组,检查是否有对应的密钥集for (int i = 0; i < upgradeKeySets.length; i++) {// 如果部分的密钥集合没有对上,说明签名密钥有问题,则返回falseif (!ksms.isIdValidKeySetId(upgradeKeySets[i])) {Slog.wtf(TAG, "Package "+ (oldPs.name != null ? oldPs.name : "<null>")+ " contains upgrade-key-set reference to unknown key-set: "+ upgradeKeySets[i]+ " reverting to signatures check.");return false;}}// 如果所有的密钥都能对上,说明密钥没有问题,则返回truereturn true;}

2.8.3 PKMS.checkUpgradeKeySetLP( …)

在前面的方法中我们判断了是否需要检查应用安装包keyset的情况,在这个方法中就会对前面扫描到的应用公钥是否有原来已经安装的PackageSetting公钥匹配,如果匹配则返回true,否则返回false。

// 【 PackageManagerService.java 】private boolean checkUpgradeKeySetLP(PackageSetting oldPS, PackageParser.Package newPkg) {// Upgrade keysets are being used.  Determine if new package has a superset of the// required keys.// 如果升级KeySet,确保新的安装包是否有超集的keys// 获取旧版本的KeySet数组long[] upgradeKeySets = oldPS.keySetData.getUpgradeKeySets();KeySetManagerService ksms = mSettings.mKeySetManagerService;// 遍历KeySet数组for (int i = 0; i < upgradeKeySets.length; i++) {// 根据密钥获取公钥Set<PublicKey> upgradeSet = ksms.getPublicKeysFromKeySetLPr(upgradeKeySets[i]);if (upgradeSet != null && newPkg.mSigningKeys.containsAll(upgradeSet)) {// 如果对应上 则返回true,return true;}}// 遍历都没有符合的,则返回falsereturn false;}

2.9 系统应用安装目录扫描小结

至此系统应用安装目录扫描阶段就基本宣告结束了,虽然我的分析已经结束了,但是对于这块的逻辑如果说要达到掌握或者精通是远远不够的,剩下的就只能交给读者自行去打磨其中的各种小细节了。在这里还是非常的有必要对于系统应用安装目录扫描来个整体的总结:

2.9.1 扫描过程总结

该过程牵涉到许多调用,所以这里有必要通过图示直观的归纳总结一下我们前面分析的整理流程如下:

可以看到上述过程最多的就是通过包解析器解析应用安装包的逻辑了,这也是为什么前面我们花了三篇博客专门来分析它的原因了。

2.9.2 扫描生成数据关系总结

其实PKMS耗费这么多流程来扫描系统应用安装目录,就是为了将各种安装应用转换成为对应内存中的数据结构,然后管理各种安装应用并且对外提供相关的查询服务。总之经过上述一顿咔咔操作之后,我们可以得到如下的数据结构关系图(其中最最核心的就是Package类图了):

上述图,示意了一个包最终在内存中的数据结构Package,它包含很多属性,部分属性还是包解析器中的子数据结构。我们可以从设计的角度来理解这个类图:

  • 一个包中有很多组件,为此设计了一个高层的基类Component,所有具体的组件都是Component的子类。什么是组件呢?AndroidManifest.xml文件中所定义的的一些标签,就是组件,譬如<activity>,<service>,<provider>,<permission>等,这些标签分别对应到包解析器中的一个数据结构,它们各自有自身的属性。

  • 诸如<activity>,<service>标签,都可以配置<intent-filter>,来过滤其可以接收的Intent,这些信息也需要在包解析器中体现出来,为此组件Component依赖于IntentInfo这个数据结构。每一个具体的组件所依赖的IntentInfo不同,所以ComponentIntentInfo之间的依赖关系采用了桥接(Bridge)这种设计模式,通过泛型的手段实现。

  • 各种组件最终聚合到Package这个数据结构中,形成了最终包解析器的输出。当然,在解析的过程中,还有利用了一些数据结构来优化设计,PackageLiteApkLite就是一些很简单的数据封装。

至于这些数据结构之间更加详尽的关系是怎么关联的,请回头参见前面博客!


三.系统应用安装目录扫描收尾阶段

  通过前面我们一顿猛虎般的操作,系统应用安装目录扫描完成,该建立的数据结构已经建立,该注册的相关的组件已经注册OK,该保存的数据结构也已经保存在mPackages中去了。难道我们此时要马放南山,人去嗨吗!当然不是的,虽然我们此时整体流程处理结束了,但是一些相关的小细节还是需要我们特别处理,主要是一些存在升级和可能不存在的系统安装包的处理。我们先来整体看下,然后再细说:

            // Prune any system packages that no longer exist.// 删除任何不再存在的系统包,这类List表示的是有可能有升级包的系统应用// 用于收集可能已经被删掉的系统应用包final List<String> possiblyDeletedUpdatedSystemApps = new ArrayList<String>();          if (!mOnlyCore) {// 遍历上一次安装和前面扫描系统应用目录可能构建PackageSettingsIterator<PackageSetting> psit = mSettings.mPackages.values().iterator();while (psit.hasNext()) {PackageSetting ps = psit.next();/** If this is not a system app, it can't be a* disable system app.*/// 忽略掉非系统应用if ((ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0) {continue;}/** If the package is scanned, it's not erased.*/// 保存的是上面调用scanDirLI方法扫描目录得到的应用信息,不要和mSettings.mPackages弄混了final PackageParser.Package scannedPkg = mPackages.get(ps.name);if (scannedPkg != null) {/** If the system app is both scanned and in the* disabled packages list, then it must have been* added via OTA. Remove it from the currently* scanned package so the previously user-installed* application can be scanned.*///如果在disable列表中,那么,说明它是通过OTA方式进行升级更新添加的,因此,清除相应数据// 说明一定是通过覆盖更新的,移除之前扫描的结果,保证之前用户安装的应用能够被扫描!// “disable” 列表是package.xml中<update-package>标签标示的应用if (mSettings.isDisabledSystemPackageLPr(ps.name)) {logCriticalInfo(Log.WARN, "Expecting better updated system app for "+ ps.name + "; removing system app.  Last known codePath="+ ps.codePathString + ", installStatus=" + ps.installStatus+ ", versionCode=" + ps.versionCode + "; scanned versionCode="+ scannedPkg.mVersionCode);// 从扫描列表mPackages中移除removePackageLI(scannedPkg, true);// 详见章节 【 3.1 】// 放入mExpectingBetter列表,后面会进行处理的。mExpectingBetter.put(ps.name, ps.codePath);}//跳出循环,确保不会被删除continue;}// 注意此处的前提是scannedPkg为null// 运行到这里说明ps表示的应用不在被扫描列表mPackages中,也就是在系统中不存在 if (!mSettings.isDisabledSystemPackageLPr(ps.name)) {// 如果这个文件不属于<update-package>标识的应用,说明这个应用是残留在packages.xml中的,// 可能还有数据目录,因此要删除psit.remove();logCriticalInfo(Log.WARN, "System package " + ps.name+ " no longer exists; it's data will be wiped");// Actual deletion of code and data will be handled by later// reconciliation step} else {// 如果这个应用不在系统中,但是被标记了<update-package>在不可用列表中//  则加入到possiblyDeletedUpdatedSystemApps列表final PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(ps.name);if (disabledPs.codePath == null || !disabledPs.codePath.exists()) {possiblyDeletedUpdatedSystemApps.add(ps.name);}}}}//look for any incomplete package installations// 扫描并删除未安装成功的apk包ArrayList<PackageSetting> deletePkgsList = mSettings.getListOfIncompleteInstallPackagesLPr();for (int i = 0; i < deletePkgsList.size(); i++) {// Actual deletion of code and data will be handled by later// reconciliation stepfinal String packageName = deletePkgsList.get(i).name;logCriticalInfo(Log.WARN, "Cleaning up incompletely installed app: " + packageName);synchronized (mPackages) {mSettings.removePackageLPw(packageName);}}//delete tmp files//  删除临时文件deleteTempPackageFiles();// Remove any shared userIDs that have no associated packages// 删除掉Settings中的没有关联任何应用的SharedUserSetting对象mSettings.pruneSharedUsersLPw();

这里我们可以把上述的扫描系统应用安装收尾的工作整体上归纳为如下两部分:

  • 对于已经被扫描到的系统APP,如果发现它存在于被更新过的列表中,说明这个系统APP在data分区有更新的版本了,那就需要把之前扫描的从PKMS.mPackages暂时清除掉,然后将Package信息体添加到mExpectingBetter中进行保存,这样我们要确保在扫描data分区时,data分区的app能够有机会被扫描到。这里我们先看下mExpectingBetter它的定义如下:

     /*** Tracks new system packages [received in an OTA] that we expect to* find updated user-installed versions. Keys are package name, values* are package location.*/
    final private ArrayMap<String, File> mExpectingBetter = new ArrayMap<>();
    

    可以看到它是一个哈希表,以安装包的包名为key,以安装包路径为value。

    这里读者思考一下为啥要设计这么一个mExpectingBetter来存储被删除的系统应用安装包信息呢,这是因为虽然此时知道了系统应用安装包存在升级覆盖的位于data分区,但是由于data分区此时还没有被扫描到,不能确保它是否还真的存在,万一不存在了呢,那系统目录的就失去了恢复的机会了,所以此处保留了一招。

    我们通过前面的分析可知需要隐藏的系统APP都会被添加到 mExpectingBetter 列表中:
    1、系统APK被覆盖安装过,且system分区的versionCode的是不高于data分区的;
    2、之前已经有一相同包名的APK安装在data分区中了,这次OTA或者其它情况有同样的包名的app出现在了system分区, versionCode不高于data分区的

  • 对于上次启动存在但是在这次扫描中发现已经不存在于系统应用安装包目录中的应用,且它也不存在覆盖升级的情况,则说明这个应用是残留在packages.xml中的,可能还有数据目录,因此要删除

    譬如原来有个/systemapp/xxx/xxx.apk的系统应用上次启动被成功扫描,且它不存在覆盖升级的情况,此次OTA升级将其删除了,但是packages中还是残留着相关的信息,所以必须将残余信息和数据删除

  • 对于上次启动存在但是在这次扫描中发现已经不存在于系统应用安装包目录中的应用,但是它存在覆盖升级的情况,则先将这个信息先放入possiblyDeletedUpdatedSystemApps中待扫描完data分区目录再行处理

    譬如原来有个/systemapp/xxx/xxx.apk的系统应用上次启动被成功扫描,且它存在覆盖升级的情况在/data/app/,此次OTA升级将其删除了

好了,打卡收工,OVER!

3.1 PKMS.removePackageLI(…)

这里我们还是简单看下removePackageLI的处理逻辑,如下:

// 【 PackageManagerService.java 】private void removePackageLI(PackageParser.Package pkg, boolean chatty) {// Remove the parent package settingPackageSetting ps = (PackageSetting) pkg.mExtras;if (ps != null) {removePackageLI(ps, chatty);}// Remove the child package setting// 删除子Packagefinal int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;for (int i = 0; i < childCount; i++) {PackageParser.Package childPkg = pkg.childPackages.get(i);ps = (PackageSetting) childPkg.mExtras;if (ps != null) {removePackageLI(ps, chatty);}}}void removePackageLI(PackageSetting ps, boolean chatty) {if (DEBUG_INSTALL) {if (chatty)Log.d(TAG, "Removing package " + ps.name);}// writersynchronized (mPackages) {mPackages.remove(ps.name);//从mPackages中删除掉final PackageParser.Package pkg = ps.pkg;if (pkg != null) {cleanPackageDataStructuresLILPw(pkg, chatty);}}}

四.PKMS启动BOOT_PROGRESS_PMS_SYSTEM_SCAN_START阶段总结

至此PKMS启动BOOT_PROGRESS_PMS_START阶段就分析完成了,读者是感到意犹未尽呢,还是感觉到分析得想吐了呢。木有办法,我们还是得总结一下该流程的主要过程:

  • 通过扫描系统应用安装目录,得到目录下相关的应用对应的Packages信息

  • 将安装应用信息添加到PKMS中进行l管理,同时将其对应的相关组件注册到PKMS中,以供后续第三方查询和使用

当然实际远远不止如此,上述只是进行了最最简单和i最最核心的归纳而已。

而且此时的我发现,markdown由于内容太多编辑变得非常吃力了,所以先就到这吗!臣妾做不到,整不动了。

好了,到这里PackageManagerService启动详解(七)之BOOT_PROGRESS_PMS_SYSTEM_SCAN_START阶段流程分析就告一段落了,各位青山不改绿水长流,各位江湖见!当然各位读者的点赞和关注是我写作路上前进的最大动力了,如果有啥不对或者不爽的也可以踩一踩也无妨!你们的鼓励和批评是博主前进路上最大的动力。如果读者对该系列博客感兴趣的话,欢迎继续PKMS之旅的下一篇PKMS启动详解(八)之扫描data分区应用安装目录阶段流程分析详解。

PackageManagerService启动详解(七)之扫描系统应用安装目录阶段流程分析相关推荐

  1. PackageManagerService启动详解(五)之Android包信息体和解析器(中)

        PKMS启动详解(五)之Android包信息体和包解析器(中) Android PackageManagerService系列博客目录: PKMS启动详解系列博客概要 PKMS启动详解(一)之 ...

  2. PackageManagerService启动详解(三)之开始初始化阶段流程分析

      PKMS启动详解(三)之BOOT_PROGRESS_PMS_START阶段流程分析 Android PackageManagerService系列博客目录: PKMS启动详解系列博客概要 PKMS ...

  3. PackageManagerService启动详解(二)之怎么通过packages.xml对已安装应用信息进行持久化管理?

    PKMS启动详解(二)之怎么通过packages.xml对已安装应用信息进行持久化管理? Android PackageManagerService系列博客目录: PKMS启动详解系列博客概要 PKM ...

  4. IntelliJ IDEA使用教程(动图详解):Ubuntu 系统下安装 IntelliJ IDEA

    Ubuntu 系统下安装 IntelliJ IDEA 系统要求 系统支持:只要是支持 GNOME 或 KDE 桌面系统,建议是 Ubuntu(32位和64位都可以) JDK 版本:Oracle JDK ...

  5. 案例详解-如何在 Linux 系统中安装和使用 7zip 以及 7zip的脚本编程使用教程(非p7zip,而是官方版本7zip for linux)附deb包下载链接

    如何在 Linux 系统中安装和使用 7zip 李俊才(jcLee95) 的个人博客 邮箱 :291148484@163.com CSDN 主页:https://blog.csdn.net/qq_28 ...

  6. mysql数据刷盘过程详解_MySQL延迟问题和数据刷盘策略流程分析

    一.MySQL复制流程 官方文档流程如下: MySQL延迟问题和数据刷盘策略 1.绝对的延时,相对的同步 2.纯写操作,线上标准配置下,从库压力大于主库,最起码从库有relaylog的写入. 二.My ...

  7. Android中AMS工作原理,Android AMS启动详解

    启动 在Android系统启动流程中中我们提到过,AMS是在system_service中启动的, //frameworks/base/services/java/corri/android/serv ...

  8. 计算机无法进系统咋办,详解电脑无法进入系统怎么办

    原标题:详解电脑无法进入系统怎么办 随着计算机的普及,许多人使用计算机.方便了我们的生活,也带来了一些问题,现在一些朋友遇到电脑进不了系统.原因是什么?有解决办法吗?接下来,我将教你如何解决计算机无法 ...

  9. SSH服务详解(七)– SSH 连接 Github

    SSH 连接 Github SSH服务详解(一)–Linux SSH 服务器与客户端的安装与启动 SSH服务详解(二)–使用私钥登录 SSH 服务器(免密登录) SSH 服务详解 (三)-- 使用 S ...

最新文章

  1. 央广记者在自律联盟现场采访京都贷执行董事长
  2. Ubuntu安装BackExec Remote Agent for Linux
  3. 前端学习(3047):vue+element今日头条管理-使用table表格组件
  4. 前端学习(600):使用chorme devtools进行开发
  5. .NET : 如何理解字符串和它的字节表现形式
  6. Objective-C语法与Cocoa框架
  7. 专家程序员要了解的mysql_从程序员的角度深入理解MySQL
  8. 芯片级维修一台指示灯全不亮的小米路由器PRO
  9. Java 应用SAXReader 解析网络地址 XML
  10. PNP : Work Cound Frequence
  11. 获取当前时间,包括农历时间
  12. android 自定义数字软键盘,(笔记)Android自定义数字键盘
  13. 【十三】python面向对象之类和对象
  14. java.lang.IllegalStateException: Underflow in restore - more restores than saves
  15. Ansible#Typora-Ansible笔记
  16. Linux下玩转Dota2
  17. 固高机器人控制器开发笔记
  18. 超级详细利用Vmware部置XP虚拟机
  19. CAD2010软件安装资料及教程
  20. 微信小程序1.1: 报错page[pages/XXX/XXX] not found.

热门文章

  1. Cisco RV320/RV042/RV130产品搭建专网网络
  2. java Supplier接口与示例
  3. 云主机 安装 自己的系统 免费使用更大的硬盘空间
  4. 看IT巨头们如何应对金融危机
  5. 【ORCA】QM/QM2
  6. 初学嵌入式Linux系统文件命令第二弹
  7. “科创中国”开源产业科技服务团荣获2022年度中国科协优秀科技服务团
  8. PHP-身份证号码验证
  9. Flink state使用
  10. IIS+php环境下:上传文件出现:windows server PHP Warning: mkdir(): Permission denied 解决办法