查看原文
其他

Android从上帝视角来看PackageManagerService

鸿洋
2024-12-14

The following article is from 牛晓伟 Author 牛晓伟

前言
阅读该篇之前,建议先阅读下面的系列文章:
Android深入理解包管理--PackageManagerService和它的“小伙伴”
Android深入理解包管理--记录存储模块
Android深入理解包管理--共享库模块
Android深入理解包管理---apk信息

本文摘要

这是包管理系列的最后一篇文章,本文的标题是从上帝视角来看PackageManagerService,为啥要起这么“狂妄”的名字呢?其主要的原因是我希望从一个更全面、更高的、更清晰的视角来看明白PackageManagerService的每个模块之间是如何协作来保证PackageManagerService的关键工作顺利完成。通过本文您将了解到PackageManagerService被划分为哪些模块,模块之间是如何协作来保证各项工作的顺利完成。(文中代码基于Android13)

1模块划分


其实在Android深入理解包管理--PackageManagerService和它的“小伙伴”这篇文章已经介绍过PackageManagerService的各个模块了,但是我还是希望把它们重新“请”出来,以保证后面的内容能顺利连接起来。(当然增加了快照管理模块)
先简单介绍下PackageManagerService,它是运行于systemserver进程,systemserver进程中有很多很多的服务,比如大家熟知的ActivityManagerServiceWindowManagerService。而PackageManagerService也是一个服务,一个非常非常重要的服务。
下图展示了PackageManagerService的几个关键模块


主要模块有apk管理模块、权限管理模块、共享库模块、记录存储模块、所有apk信息模块、四大组件模块。PackageManagerService不可能只有上面的几个模块,它还有快照模块、对外接口模块、property模块等,只不过上面的模块较常见。

1.1 权限管理模块

既然是权限管理模块,那有必要先来介绍下权限,权限分为声明权限和请求权限。

1.1.1 声明权限

声明权限需要在AndroidManifest.xml文件中使用permission标签,如下例子:
<permission android:description="string resource"
            android:icon="drawable resource"
            android:label="string resource"
            android:name="string"
            android:permissionGroup="string"
            android:protectionLevel=["normal" | "dangerous" |
                                     "signature" | ...] />


每个apk都可以声明自己的权限,那当别的apk访问自己的一些关键信息时候就可以要求它具有某个声明的权限后才可以访问。

1.1.2 请求权限

请求权限就是在AndroidManifest中通过uses-permission标签来使用权限,如下代码:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

1.1.3 权限管理模块所做的事情

而权限管理模块所做的事情如下:
  1. 把所有的apk声明的权限收集并集中管理起来。
  2. 保存每个apk请求的权限和请求权限对应的状态,请求权限对应的状态是指比如某个apk的请求的权限是否被允许、是否拒绝、是否只是允许一次。
  3. 处理apk权限请求,当用户不管是点击了允许、拒绝等,都需要经过权限管理模块。
权限管理模块把上面这些事情全权交给了PermissionManagerService服务来处理,关于权限管理后面会有系列文章来介绍。

1.2 共享库模块

同样先来简单介绍下共享库,共享库首先它是一个库,库大家肯定非常熟悉了,库可以是一个jar文件 (jar包代表java库) 也可以是一个so文件 (so是二进制可执行文件代表native库),而它的形容词共享代表该库是可以被多个程序使用的。共享库也就是可以被多个程序使用的库。在Android中共享库除了上面的意思之外,还要再加一条就是共享库是由系统提供的可以被多个程序使用的系统库,在Android中共享库可以是一个jar、so、apk文件。
共享库也同样分为声明共享库和使用共享库,只有系统apk才能声明共享库。
共享库模块所做的事情如下:
  1. 把所有声明的共享库收集起来,若共享库之间存在依赖,则把它们的依赖也初始化。
  2. 若apk中使用了某个共享库,则会根据共享库名称、版本信息从共享库模块把共享库的信息查询出来 (如共享库文件路径) 交给该apk。
共享库模块把这些事情交给了SharedLibrariesImpl类来处理,关于共享库模块的详细介绍可以看Android深入理解包管理--共享库模块这篇文章。

1.3 记录存储模块

记录存储模块的主要工作是记录apk的安装及附加信息并且把这些信息存储到文件中。也就是要想知道Android设备上有没有安装某个apk,是可以从记录存储模块得知的。如果apk安装了,则记录存储模块会把安装信息记录下来。
关于该模块的详细介绍可以看Android深入理解包管理--记录存储模块这篇文章

1.4 所有apk信息模块

apk信息指的是apk的AndroidManifest.xml文件中配置的各种信息,比如apk版本信息、apk包名、声明了哪些四大组件、使用了哪些权限、声明了哪些权限、使用了哪些共享库等。
安装在Android设备上的apk (系统apk和非系统apk),都需要把它们的apk信息存储到内存中,以供使用者来查询 (比如某使用者想要根据包名知道某个apk的ApplicationInfo信息),而存放所有apk信息的地方被称为所有apk信息模块。关于apk信息的详细介绍可以看Android深入理解包管理---apk信息这篇文章。
下面是所有apk信息模块在PackageManagerService中属性的声明:
//下面属性位于PackageManagerService类

//key是包名,而AndroidPackage存储了解析出的AndroidManifest的信息
final WatchedArrayMap<String, AndroidPackage> mPackages = new WatchedArrayMap<>();


1.5 四大组件模块

四大组件模块在内存中存储了所有已安装apk的AndroidManifest中声明的四大组件,四大组件模块的事情是完全交给了ComponentResolver类。
常用于以下场景:
  1. ActivityTaskManagerService启动某个Activity时,需要从我这获取对应的Activity信息,获取到则返回;否则启动Activity失效。
  2. ActivityManagerService启动某个Service时,也需要从我这获取对应的Service信息。同理启动某个BroadcastReceiver、ContentProvider也需要从我哦这获取对应的信息。
下面是四大组件模块在PackageManagerService中属性的声明:
//下面属性位于PackageManagerService类

final ComponentResolver mComponentResolver;


1.6 apk管理模块

apk管理模块从上图可以看出它在所有模块中的地位是多么重要,它包含的功能有扫描所有apk、apk安装/更新/卸载、解析apk。在后面会详细介绍到它。对apk安装感兴趣可以查看apk安装之谜这篇文章。

1.7 快照管理模块

快照 (snapshot)可以理解为是数据的拷贝,在PackageManagerService的各个模块以及各个模块的属性中都充斥着快照,如下部分代码:
//Settings类

//获取Settings的快照
public Settings snapshot() {
    return mSnapshot.snapshot();
}

private Settings(Settings r) {
    //Settings的很多属性也都有自己的快照
    mPackages = r.mPackagesSnapshot.snapshot();
    mPackagesSnapshot  = new SnapshotCache.Sealed<>();
    mKernelMapping = r.mKernelMappingSnapshot.snapshot();
    mKernelMappingSnapshot = new SnapshotCache.Sealed<>();

    省略其他代码······

    // Do not register any Watchables and do not create a snapshot cache.
    mSnapshot = new SnapshotCache.Sealed();
}


上面代码只是截取了Settings (记录存储模块)和它的属性相关的快照,其他的各个模块和属性也都有相应的快照。我刚开始看PackageManagerService代码的时候,就被各种各样的快照震惊了,为啥要有快照呢,它的作用是啥呢?
快照的作用就是为了快速的检索数据,大家都知道PackageManagerService中有各种各样的数据,并且数据量都很大,为了保持数据的一致性,在更新/添加/删除/访问这些数据的时候是都加了各种各样的锁的,而如果没有快照的话,比如在访问这些数据的时候是需要获取相应的锁,如果没有获取到则需要等待,想想这个过程是非常影响查询速度的。
那为了解决以上问题快照就诞生了,每个模块及相关属性都有自己快照也就是拷贝,那当访问这些数据的时候就从快照中直接获取这速度是不是提升了很多 (其实就是以空间换时间罢了)。那当模块或者相关属性的数据发生变化了会做何种处理呢?答案是发生变化的模块或属性重新生成自己的快照。
而快照管理模块很显然就是管理了所有的快照,而它对应的是Computer类,该类是一个接口,它的实现类是ComputerEngine,快照管理模块还有另外一个作用就是各种数据的代理者,PackageManagerService它是一个binder server,它的使用者可以通过binder通信的方式从它获取各种数据,而获取的各种数据都是先要经过快照管理模块,而快照管理模块把各种快照数据组装起来返回给使用方。
PackageManagerService的快照Computer是最顶级的快照,它包含了各个模块的快照,而每个模块的快照又包含了自己相关属性的快照。那当某个模块或者模块属性发生变化的时候,该变化信息会传递到PackageManagerServicePackageManagerService开始重新收集所有的快照,收集过程只要相应的快照没有发生变化,则依然使用它,否则重新生成快照。
关于快照管理模块先暂时介绍到这。

1.8 小结

那简单总结下各个模块:
  1. 权限管理模块负责apk权限相关的事情,比如请求某个权限,apk权限状态存储,收集所有apk声明的权限。
  2. 共享库模块负责apk使用到的所有共享库。
  3. 记录存储模块会把apk相关的很多信息记录并且存储到文件中,比如apk安装后关于apk安装的信息会存储下来,这样就可以供其他使用者检索。
  4. 所有apk信息模块会收集所有已安装apk的AndroidManifest解析出来的信息,以供其他使用者检索。
  5. 四大组件模块为了加快检索四大组件的速度,会把所有已安装apk的四大组件信息收集起来。
  6. apk管理模块主要负责apk的安装/卸载/更新,它是根基模块,因为它的某个功能会对其他模块产生影响。
  7. 快照管理模块主要目的为加快访问PackageManagerService中的各种数据。

2模块的启动
Android深入理解包管理--PackageManagerService和它的“小伙伴”中介绍过模块的启动,但是我觉得介绍的有些“潦草”,故在此更详细的介绍下。
模块的启动主要是想展示给大家,在PackageManagerService的启动过程中,各个模块的启动都做了啥?为啥要这样做?
下图展示了PackageManagerService在启动过程中,每个模块所做的事情:


前几个模块的启动,其实都在为扫描所有apk做准备,而扫描所有apk是PackageManagerService启动过程中做的非常重要的一件事情,如果不扫描所有apk,那PackageManagerService就完全不知道当前Android设备上所有已安装apk的具体apk信息 (apk信息指的是apk的AndroidManifest.xml文件中配置的各种信息如apk版本信息、apk包名、声明了哪些四大组件等),不知道具体apk信息,PackageManagerService犹如一个废掉的服务,不能提供任何有用的服务。

2.1 共享库模块启动

共享库分为内置共享库和声明的共享库。
内置共享库很容易理解就是系统内置的共享库,内置共享库可以理解为静态的,因为它们的信息被读取到内存后是不会发生变化的。
而声明的共享库指由系统apk声明的共享库。而声明的共享库可以理解为是动态的,apk声明了共享库后,声明的共享库有可能被删除 (该apk被删除了后者apk删除了该共享库),也有可能被升级 (apk声明的共享库升级了)等。
正因为内置共享库和声明的共享库分别是静态的和动态的,也就导致内置共享库信息是可以提前从文件中读取到内存中,而声明的共享库它是动态的,它的任何变化都会影响到它的使用者,因此声明的共享库是不会存储在文件中的,需要在扫描所有apk阶段重新收集。
共享库模块的启动主要的工作是初始化内置共享库,共享库模块作为PackageManagerService启动的第一个模块,提前启动的主要原因是为扫描所有apk做准备的,在扫描所有apk的时候有可能某个apk使用了某个内置共享库,那这时候肯定需要从共享库模块查出该共享库的信息,因此共享库模块的启动要放在前面。
关于共享库模块可以查看共享库模块这篇文章
下面是相关代码,自行取阅:
//下面代码位于PackageManagerService构造方法中

  //从 SystemConfig.getInstance() 中把所有的内置共享库信息读取出来
  ArrayMap<String, SystemConfig.SharedLibraryEntry> libConfig = systemConfig.getSharedLibraries();

  final int builtInLibCount = libConfig.size();
  for (int i = 0; i < builtInLibCount; i++) {
      //依次把共享库信息添加到 mSharedLibraries 中
      mSharedLibraries.addBuiltInSharedLibraryLPw(libConfig.valueAt(i)); 
  }

  long undefinedVersion = SharedLibraryInfo.VERSION_UNDEFINED;
  //下面代码处理共享库之间的依赖
  for (int i = 0; i < builtInLibCount; i++) {
    String name = libConfig.keyAt(i);
    SystemConfig.SharedLibraryEntry entry = libConfig.valueAt(i);
    final int dependencyCount = entry.dependencies.length;
    for (int j = 0; j < dependencyCount; j++) {
      final SharedLibraryInfo dependency =
      computer.getSharedLibraryInfo(entry.dependencies[j], undefinedVersion);
      if (dependency != null) {
         computer.getSharedLibraryInfo(name, undefinedVersion).addDependency(dependency);
      }
    }
  }


2.2 记录存储模块启动
记录存储模块它的主要作用是把记录已安装apk、记录所有apk声明的权限等,其中记录已安装apk的信息包括apk的版本号、apk的包名、apk文件路径、apk的appid、apk签名信息、apk请求权限及权限状态等。也就是可以从记录存储模块知道哪个apk安装了,apk安装信息都有哪些等。
记录存储模块启动主要的工作就是**从已经存储的文件中把存储的信息读取出来交给对应的数据类 (PackageSetting)**,这样就可以知道安装了哪些apk了。
记录存储模块提前启动的主要原因除了为扫描所有apk做准备,还为权限管理模式初始化做准备,在扫描所有apk的时候肯定是需要知道某个系统apk是否已经安装,已经安装的话就需要用新apk与老apk进行一些比较 (比如签名信息是否一致)。为啥只是针对某个系统apk,因为普通apk的安装只能通过正常的用户手动触发,而系统apk的安装是在扫描阶段进行的。
关于记录存储模块可以看记录存储模块这篇文章
下面是相关代码,自行取阅:
//下面代码位于PackageManagerService构造方法中

//从 /data/system/packages.xml及其他文件中把保存的数据读出来
mFirstBoot = !mSettings.readLPw(computer,  
                    mInjector.getUserManagerInternal().getUsers(
                    /* excludePartial= */ true,
                    /* excludeDying= */ false,
                    /* excludePreCreated= */ false));


2.3 权限管理模块初始化
权限分为声明权限和使用权限,不管是apk声明的权限还是apk使用的权限都是在记录存储模块中存储着的,当记录存储模块启动的时候,这些信息都会被读取出来交给对应的数据类。
权限管理模块初始化就是使用记录存储模块读取到的权限信息来初始自己,初始化完毕后就可以从权限管理模块知道所有apk都声明了哪些权限,并且可以知道哪些apk使用了哪些权限,权限状态是啥 (拒绝、允许等)。
权限管理模块初始化的主要原因同样也是为了扫描所有apk做准备,关于权限管理模块后面有相关系列文章介绍。
下面是相关代码,自行取阅:
//下面代码位于PackageManagerService构造方法中

//把所有apk声明的权限交给mPermissionManager
mPermissionManager.readLegacyPermissionsTEMP(mSettings.mPermissions); 
//下面方法会使用每个apk的权限状态初始化自己
mPermissionManager.readLegacyPermissionStateTEMP();


2.4 扫描所有apk
扫描所有apk是归apk管理模块,上面一直都在提扫描所有apk,它是PackageManagerService启动过程中最重要的事情没有之一。为啥要叫扫描apk呢?
其实扫描apk是属于安装apk中的重要环节,扫描apk对于系统apk的话是一个安装或者升级的过程,对于普通apk来说就是一个“查漏补缺”的过程 (比如某个系统apk把声明的某个共享库删除了,则使用了该共享库的apk就需要做调整)。扫描apk所做的主要事情如下:
  1. 把apk信息从AndroidManifest文件中解析出来。
  2. 在依据解析出来的apk信息,去记录存储模块查询该apk是否已经安装,若安装的话进行升级方面的操作 (比如新老apk进行版本比较);若没有安装则进行安装方面的操作,比如为apk创建data目录等。
  3. 如果apk中AndroidManifest文件中的权限发生了则会通知权限管理模块进行增加/删除/更新对应权限;如果apk中声明了共享库 (只有系统apk才可以声明共享库),则会通知共享库模块增加相应的共享库等操作。
  4. 解析出来的apk信息经过步步验证后,最终会存放在所有apk信息模块和四大组件模块,这样就可以供其他使用者使用了。
扫描apk最终的结果就是apk信息经过重重校验,apk信息最终存放在所有apk信息模块和四大组件模块,这样使用者就可以使用这些信息了,比如startActivity的时候ActivityTaskManagerService会从四大组件模块中拿到Activity的信息。同时扫描apk也会或多或少的影响到权限管理模块、共享库模块、记录存储模块。(到达如何影响的会在下面apk安装环节介绍)
扫描所有apk分为扫描所有系统apk和扫描所有普通apk,先进行扫描所有系统apk,后进行扫描所有普通apk,为啥是这样的顺序呢?原因是这样的只有系统apk才能声明共享库,因此需要先把所有的系统apk都扫描完后,共享库模块才能把所有声明的共享库收集起来,供普通apk来使用。
存放系统apk的目录主要是位于product、vendor、system、system_ext、apex分区下的overlay、app、priv-app、framework目录 (若在分区下存在相应的目录),而位于priv-app目录下的apk是拥有特权的系统apk,这里的特权是具有特殊权限的简称。存放普通apk的目录是data分区的app目录下。
扫描所有apk先扫描存放系统apk的目录,再扫描存放普通apk的目录,如下相关代码:
//下面代码位于PackageManagerService构造方法中

final int[] userIds = mUserManager.getUserIds();
//packageParser的作用是解析apk
PackageParser2 packageParser = mInjector.getScanningCachingPackageParser();
//扫描系统apk
mOverlayConfig = mInitAppsHelper.initSystemApps(packageParser, packageSettings, userIds,startTime); 
//扫描非系统apk
mInitAppsHelper.initNonSystemApps(packageParser, userIds, startTime); 
packageParser.close();


2.5 共享库模块再度登场
扫描所有apk是先扫描所有系统apk,然后在扫描所有普通apk。而在扫描所有系统apk的时候是没有把系统apk中使用共享库信息补全的 (如果该系统apk确实使用了某个共享库)。
先来解释下啥叫共享库信息补全,比如某系统apk在AndroidManifest.xml文件中使用uses-library标签使用了某个共享库,共享库信息补全就是从共享库模块把该apk使用的共享库信息 (共享库文件路径、名称等)查询出来,并且交给该apk。
那为啥没有把系统apk中使用共享库信息补全呢?其主要原因还是因为只有系统apk才能声明共享库,比如正在扫描某个系统apk,而它使用的共享库对应的系统apk还没有被扫描,则这时候该系统apk的共享库就没有补全。那针对这种情况该咋办呢?办法就是在扫描完所有apk后,在去把所有系统apk中使用的共享库信息一起补全 (如果该系统apk确实使用了某个共享库)。
下面是相关代码,请自行取阅:
//下面代码位于PackageManagerService构造方法中

//下面方法会把所有apk的共享库信息补全 ((如果该apk确实使用了某个共享库)
mSharedLibraries.updateAllSharedLibrariesLPw(nullnull, Collections.unmodifiableMap(mPackages)); 


2.6 小结
共享库模块、记录存储模块、权限管理模块三个模块的启动为扫描所有apk做准备,而扫描所有apk又分为扫描所有系统apk和扫描所有普通apk,扫描所有apk最终的结果是所有apk信息最终存放在了所有apk信息模块和四大组件模块,这样使用者就可以从PackageManagerService获取到某个apk的信息了,比如获取某个Activity的信息。

而扫描所有apk也会或多或少的影响共享库模块、记录存储模块、权限管理模块,比如扫描某个系统apk,该系统apk声明了新的权限、声明了新的共享库,则会把声明的共享库信息记录在共享库模块,而声明的权限则会通知权限管理模块增加此权限,同时记录存储模块也会把该权限存储到文件中。

3模块相互协作守护apk的安装
本节主要想介绍模块之间的相互协作来守护apk的安装,先假设要安装的apk,在它的AndroidManifest.xml文件中声明了权限、使用了某个权限、使用了某个共享库、声明了四大组件。
下图展示了安装该apk的过程:


那结合上图来进行介绍。

3.1 apk的安装

apk的安装首先需要把要安装的apk拷贝到/data/app目录下,首先先从AndroidManifest.xml中把apk的基础信息 (apk包名、版本号、签名信息等)解析出来;其次使用解析出来的基础信息进行apk完整性验证,主要验证apk有没有被改过;当然还有其他的步骤如果已经安装了该apk,则需要验证新老apk的版本信息,还需要验证新老apk的签名信息是不是一致;这些步骤都验证通过后,会从AndroidManifest.xml中把apk的所有信息 (四大组件、权限等等) 都解析出来;最后会使用apk信息经过准备、扫描、调和、提交阶段来保证apk最后的安装完成。
上面只是简单的介绍了apk的安装过程,关于apk安装更详细的介绍可以看apk安装之谜这篇文章。

3.2 各模块协作

在apk安装的提交阶段会做以下事情:
  1. 该apk的安装信息 (apk包名、版本信息、apk文件路径、appid、声明和使用的权限等) 以PackageSetting对象添加到记录存储模块,记录存储模块会把所有的PackageSetting对象重新写入到文件中。
  2. 该apk信息 (apk包名、声明的四大组件、版本信息、解析的权限等) 添加到所有apk信息模块。
  3. 该apk信息中的声明的四大组件信息添加到四大组件模块。
  4. 因为该apk使用了某个共享库,则会从共享库模块把该共享库信息查询出来交给PackageSetting对象。
  5. 因为该apk声明和使用了权限,则声明和使用的权限会被添加到权限管理模块。
注:对PackageSetting陌生,可以看下Android深入理解包管理--记录存储模块这篇文章

3.3 apk安装最后一步

apk安装的最后一步就是创建data目录和dex优化。创建data目录这样app在运行的时候就可以把私有数据存储在该目录下,dex优化可以让app运行更快。

3.4 小结

apk的安装过程,小结如下:
  1. 会在记录存储模块存储下该apk的安装信息 (apk包名、appid、apk路径等)、声明的权限 (若声明了权限)、使用的权限及状态 (若使用了权限),并且会把这些信息写入文件。
  2. apk信息会存放在所有apk信息模块。
  3. apk声明的四大组件会存放在四大组件模块。
  4. apk的权限 (若声明了权限或者使用了权限)会存放在 权限管理模块。

4模块相互协作守护app的运行
上面介绍了模块之间的相互协作守护apk的安装,那现在咱们来看下模块之间相互协作来保证app的运行。
大家都知道一个app开始运行,可以通过启动一个Activity或者启动一个Service或者启动一个ContentProvider或者启动一个BroadcastReceiver。那我就用启动一个Activity,来看PackageManagerService的各个模块是如何守护app运行的,这个过程用到的模块主要有快照管理模块、所有apk信息模块、四大组件模块、记录存储模块*这四个模块,那就从这三个模块讲起。

4.1 快照管理模块

在启动一个Activity的时候,ActivityTaskManagerService会通过Intent信息从PackageManagerService服务查询该Intent对应的Activity信息 (该信息会被封装为ActivityInfo对象),而该查询任务PackageManagerService会交给快照管理模块。
下面是相关代码,自行取阅:
//ComputerEngine类

public final @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
            String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
            @PackageManagerInternal.PrivateResolveFlags long privateResolveFlags,
            int filterCallingUid, int userId, boolean resolveForStart,
            boolean allowDynamicSplits) 
{
    省略代码······

}


4.2 四大组件模块
快照管理模块是没有真正的查询服务的,而真正的查询Activity信息需要从四大组件模块获取,四大组件模块会根据Intent中的ComponentName查询到相应的Activity信息。
如下是相关代码:
//ComputerEngine类

 protected ActivityInfo getActivityInfoInternalBody(ComponentName component,
            @PackageManager.ResolveInfoFlagsBits long flags, int filterCallingUid, int userId) 
{
    //四大组件模块的实现者就是mComponentResolver,调用它的getActivity方法查询到Activity信息
    ParsedActivity a = mComponentResolver.getActivity(component);

    省略代码······

 }


4.3 所有apk信息模块和记录存储模块
通过启动一个Activity来启动一个app,不仅仅只需要Activity的信息,还需要Application的信息 (该信息存放在ApplicationInfo对象),为啥还需要ApplicationInfo信息呢?
主要原因是当app第一次运行的时候,它需要知道apk文件路径、共享库文件路径 (若使用共享库)、包名、app的data目录等等。比如知道了apk文件路径、共享库文件路径 便可以把它们加入到自己的ClassLoader,这样app运行时就可以找到自己的类了;比如知道了app的data目录就可以知道当前app运行时候存放数据的私有目录在哪了。而以上这些信息都存放在ApplicationInfo对象,是需要提前获取到的,以便app从ActivityManagerService获取这些信息的时候可以顺利获取到。
而ApplicationInfo的获取是从所有apk信息模块和记录存储模块拿到的,记录存储模块存储了共享库信息 (若存在使用共享库)等。
如下代码:
//ComputerEngine类

 protected ActivityInfo getActivityInfoInternalBody(ComponentName component,
            @PackageManager.ResolveInfoFlagsBits long flags, int filterCallingUid, int userId) {
    //四大组件模块的实现者就是mComponentResolver,调用它的getActivity方法查询到Activity信息
    ParsedActivity a = mComponentResolver.getActivity(component);

    AndroidPackage pkg = a == null ? null : mPackages.get(a.getPackageName());

    if (pkg != null && mSettings.isEnabledAndMatch(pkg, a, flags, userId)) {
            PackageStateInternal ps = mSettings.getPackage(component.getPackageName());
            if (ps == nullreturn null;
            if (shouldFilterApplication(
                    ps, filterCallingUid, component, TYPE_ACTIVITY, userId)) {
                return null;
            }
            //调用generateActivityInfo方法
            return PackageInfoUtils.generateActivityInfo(pkg,
                    a, flags, ps.getUserStateOrDefault(userId), userId, ps);
        }
    省略代码······

 }

 //PackageInfoUtils类
  private static ActivityInfo generateActivityInfo(AndroidPackage pkg, ParsedActivity a,
            @PackageManager.ComponentInfoFlagsBits long flags,
            @NonNull PackageUserStateInternal state, @Nullable ApplicationInfo applicationInfo,
            @UserIdInt int userId, @Nullable PackageStateInternal pkgSetting) {
        省略代码······
        //构建ApplicationInfo
        if (applicationInfo == null) {
            applicationInfo = generateApplicationInfo(pkg, flags, state, userId, pkgSetting);
        }

        if (applicationInfo == null) {
            return null;
        }

        //构建ActivityInfo
        final ActivityInfo info = PackageInfoWithoutStateUtils.generateActivityInfoUnchecked(
                a, flags, applicationInfo);
        assignSharedFieldsForComponentInfo(info, a, pkgSetting, userId);
        return info;
    }


4.4 小结

不论是启动一个Activity或者启动一个Service或者启动一个ContentProvider或者启动一个BroadcastReceiver的方式来启动一个app,都需要去PackageManagerService查询相应的组件信息和ApplicationInfo信息,只有查询到正确的信息后,才能执行下一步操作;否则停止启动过程。

5总结


本文主要是通过“上帝的视角”来看清楚PackageManagerService的各模块是如何保证PackageManagerService的主要工作如何进行的,在PackageManagerService启动过程,每个模块都做了哪些提前的准备工作来保证扫描所有apk工作的顺利完成;在apk安装过程中,apk信息会存储在所有apk信息模块和四大组件模块,当然在安装过程中或多或少还会用到其他模块;在app运行时,需要从PackageManagerService的相关模块中获取对应组件和ApplicationInfo信息来保证后面启动流程的正确执行,关于包管理系列的文章就全部介绍完毕。


最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!


推荐阅读

深入探索 APKTool:Android 应用的反编译与重打包工具
Android自定义Lint的二三事儿
Gradle配置,Android应用签名详解



扫一扫 关注我的公众号

如果你想要跟大家分享你的文章,欢迎投稿~


┏(^0^)┛明天见!

继续滑动看下一个
鸿洋
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存