Android M PackageManagerService 启动过程分析
前言
在Android系统中,和用户关系最密切的service应该是PackageManager了。
一般来说,用户想要在Android设备上进行自己感兴趣的活动,都少不了apk的支持。
不论是打电话,上网,发短信还是玩一些自己喜欢的游戏,这些内容在android的世界里都是以apk的形式存在的。
所以,apk的安装,卸载是与每个用户是息息相关的。
我们今天的任务就是解析PackageManager的工作原理,apk的安装和卸载的过程。
PKMS服务启动过程
本篇涉及的代码及路径:
frameworks/base/services/java/com/android/server/SystemServer.java
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
frameworks/base/services/core/java/com/android/server/pm/Settings.java
frameworks/base/services/core/java/com/android/server/SystemConfig.java
frameworks/base/core/java/android/content/pm/PackageParser.java
frameworks/base/data/etc/ platform.xml
在SystemServer中,我们知道启动Android的系统关键服务的函数首先就是startBootstrapServices,pkms也就是在这个方法中启动的。
在启动PKMS之前,会先启动installer服务, Installer是pm路径下面的一个单独的类,主要用于通过InstallerConnection建立和installd的链接,然后Installd会进行创建一些系统关键目录的作用,所以我们要等待Installd的结束,才可以继续进行其它的创建,这里mOnlyCore 用于判断是否只扫描系统的目录,后面会用到。
启动PKMS是通过PackageManagerService的main函数进入到PackageManager的实际工作中的。
PackageManagerService的main函数其实很简单,创建一个Pkms对象,并将其注册到ServiceManager大管家中。
这里主要new了一个PacnakeManagerService对象,所以,所有的工作都是在构造函数里做的,其中,PackageManager的构造函数的主要功能为:
1. 扫描Android系统中几个目标文件夹中的apk,并且建立相应的数据结构去管理Package的信息,四大组件的信息,权限信息等内容
2. 解析物理的apk的文件,生成符合自己需求的数据结构。
所以,学习PackageManager最重要的就是学习保存各种信息的数据结构和他们之间的关系,以及控制的策略。
PKMS构造函数
PKMS对象的构造方法很长,分为以下几个阶段,每个阶段会输出相应的EventLog,除了阶段1的开头部分代码,后续代码都是同时持有同步锁mPackages和mInstallLock的过程中执行的。
下面分别从这五个阶段分析其构造过程。
BOOT_PROGRESS_PMS_START
第一阶段分两部分,先看前半部分。
说明:
- 初始化DisplayMetrics(设备显示屏相关)和Settings,将shareUserId加入到mSettings中保存;
- 保存installer对象,该对象和Native进程installd交互;
- 创建PackageDexOptimizer对象,后面用它进行dex优化;
- 通过SystemConfig对象解析系统xml文件,获取系统配置信息(permission-gids以及features,library等);
参数mDexOptLRUThresholdInMills用于决定执行dex优化操作的时间阈,这个参数用于后续的PKMS.performBootDexOpt()过程。
- 对于Eng版本,则只会对30分钟之内使用过的app执行dex优化;
- 对于非Eng版本,则会将用户最近一周内使用过的app执行dex优化;
接下来,在看后半部分,后半部分已经进了mInstallLock和mPackages的同步锁了。
说明:
创建一个ThreadHandler对象,实际就是创建一个带消息循环处理的线程,该线程的工作是:程序的安装和卸载等。
以ThreadHandler线程的消息循环(Looper对象)为参数创建一个PackageHandler,可知该Handler的handleMessage函数将运行在此线程上。
这个过程还涉及的几个重要变量:
变量 | 对应目录 | 说明 |
mAppDataDir | /data/data | App数据存放目录 |
mAppInstallDir | /data/app | 第三方app安装路径 |
mAppLib32InstallDir | /data/app-lib | App库目录 |
mAsecInternalPath | /data/app-asec | 付费app |
mUserAppDataDir | /data/user | 用户数据 |
mDrmAppPrivateInstallDir | /data/app-private | DRM保护的app |
1. Settings的构造
说明:
Settings的构造函数在data目录下新建了一个/system目录,然后新建了packages.xml, packages-backup.xml, packages.list,packages-stopped.xml等文件。
这些文件都有一些具体的作用。
文件 | 功能说明 |
packages.xml | 保存了系统所有的Package信息 |
packages-backup.xml | packages.xml的备份,防止在写packages.xml突然断电 |
packages.list | 保存了系统中已经安装的apk,以及对应的data/data/下面的对应关系 |
packages-stopped.xml | 用于记录系统中强制停止运行的Package信息 |
packages-stopped-backup.xml | 是packages-stopped.xml的备份,防止在写packages-stopped-backup的时候突然断电 |
2. SystemConfig的构造
说明:
通过readPermissions读取系统权限及配置,主要是解析目录下的所有xml文件。比如将标签<library>所指的动态库保存到 PKMS的成员变量mSharedLibraries。可见,SystemConfig创建过程是对以下这四个目录中的所有xml进行解析:
- /system/etc/sysconfig
- /system/etc/permissions
- /oem/etc/sysconfig
- /oem/etc/permissions
接着看readPermissions方法做了什么。
该方法是解析指定目录下所有的具有可读权限的xml文件。
可以看到platfrom.xml是最后才会解析的,解析这个文件建立android权限和gid的对应关系。将解析的结果mSystemPermissions,mSharedLibraries,mPermissions,mAvailableFeatures等几个集合中供系统查询和权限配置使用。 后面在扫描apk时,会由请求的权限找到对应的gid,并保存在Package类中。
3. platfrom.xml部分内容
说明:
- 权限名与gid的映射关系,比如WRITE_MEDIA_STORAGE权限对应的用户组是media_rw和sdcard_rw。
- permission和group用于建立Linux层gid和Android层pemission之间的映射关系。
- assign-permission用于向指定的uid赋予相应的权限。这个权限由Android定义,用字符串表示。当进程使用这个UID运行时,就具备了这个权限。
- library用于指定系统扩展库。当应用程序运行时,系统会自动为这些进程加载这些库。
其他的文件如xxx-hardware.xx.xml则用于描述一个手持终端(包括手机、平板电脑等)应该支持的硬件特性,例如支持camera、支持蓝牙等。系统每增加一个硬件,都要添加相应的feature。
注意:对于不同的硬件特性,还需要包含其他的xml文件。例如,要支持自动聚焦,还需要包含android.hardware.camera. autofocus.xml文件。这些文件内容大体一样,都通过feature标签表明自己的硬件特性。
1. mSettings.readLPw
判断 /data/system/packages.xm 文件是否存在,如果不存在则返回 false ,如果存在则进行解析,在系统第一次启动时 packages.xml 文件是不存在的,由 writeLP() 创建该文件,并将该文件写到设备里,下次开机会直接读取并解析这个文件。解析的过程即是按照 xml 定义的标签,将对应的属性和值添加到全局列表中。 packages.xml 文件中记录了系统安装的所有 apk 的属性权限的信息,当系统中的 apk 安装,删除或升级时,改文件就会被更新。PMS_SYSTEM_SCAN_START
Scan Start阶段代码太多,我就不贴了,整理下具体所做的工作吧:
1. alreadyDexOpted存放已经优化或不需要优化的文件
finalString bootClassPath = System.getenv("BOOTCLASSPATH");
finalString systemServerClassPath =System.getenv("SYSTEMSERVERCLASSPATH");
BOOTCLASSPATH=/system/framework/core-libart.jar:/system/framework/conscrypt.jar:/system/framework/okhttp.jar:/system/framework/core-junit.jar:/system/framework/bouncycastle.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/telephony-common.jar:/system/framework/voip-common.jar:/system/framework/ims-common.jar:/system/framework/apache-xml.jar:/system/framework/org.apache.http.legacy.boot.jar:/system/framework/com.askey.ktechandler.jar
SYSTEMSERVERCLASSPATH=/system/framework/services.jar:/system/framework/ethernet-service.jar:/system/framework/wifi-service.jar:/system/framework/realtek.jar
2. 对mSharedLibraries中的库调用mInstaller.dexopt进行优化,执行dex优化操作的文件有以下几类:
- mSharedLibraries:该共享库下的所有文件,是由SystemConfig构造函数中赋值的;
- /system/framework:该目录的所有apk和jar文件,去除位于alreadyDexOpted中的文件。 具体有哪些文件不包括呢?比如services.jar, framework-res.apk,core-libart.jar
PackageParser.parseBaseApk
来完成
AndroidManifest.xml
文件的解析,生成
Application, activity,service,broadcast, provider
等信息。
4. possiblyDeletedUpdatedSystemApps/ deletePkgsList删除不存在的系统包以及不完整的包,还有tmp文件。
接下来我们研究下PKMS的核心方法scanDirLI
在scanDirLI中,又继续调用的是scanPackageLI。scanPackageLI函数扫描一个特定的文件,返回值是PackageParser的内部类package。该类的实例代表一个apk文件,所以它就是和apk文件对应的数据结构。
方法说明:
- 创建一个PackageParser对象pp,调用parsePackage解析manifest文件,解析完成后返回PackageParser.Package pkg;
- 解析完后会分析pkg是否已经存在/更新/是否系统pkg等。
- 对解析的pkg的证书进行verify;
- 对解析的pkg的签名进行compare;
- 对pkg的applicationInfo进行归档;
- 最后在调用scanPackageLI的重载方法进行对pkg数据结构归档到PKMS的数据结构中以便统一管理;
parsePackage
parseClusterPackage用于解析文件夹,parseMonolithicPackage用于解析非文件夹,即普通apk文件。
方法说明:
- 创建一个AssetManager,他是android的资源器;
- 对apk文件调用parseBaseApk解析;
接着看parseBaseApk做了什么?
方法说明:
- 获取apk文件绝对路径;
- 将apk文件的资源加载到资源管理器中;
- Resources类的成员函数updateConfiguration首先是根据参数config和metrics来更新设备的当前配置信息, 例如,屏幕大小和密码、国家地区和语言、键盘配置情况等等, 接着再调用成员变量mAssets所指向的一个Java层的AssetManager对象的成员函数setConfiguration来将这些配置信息设置到与之关联的C++层的AssetManager对象中去;
- 构建XmlResourceParser对象;
- 调用parseBaseApk的重载方法解析apk文件,主要解析manifest;
- 解析完成归档数据结构到pkg中;
parseBaseApk重载方法
这个方法很长,分阶段讲解.
第一阶段:
解析manifest标签,得到pkgName,mVersionCode, shareUserId,installLocation,coreApp等属性,并将它们归档pkg数据结构。
比如Gallery3d的manifest标签:
Settings的manifest标签:
可以看到settings为coreApp,且共享Uid为system,权限较高。
第二阶段:
解析application标签,permission-group,permission,uses-permission,uses-feature,supports-screens一些列标签,其中,parseBaseApplication会去解析我们的四大组件。
在分析parseBaseApplication之前,先看PackageParser内部类package,后面解析出的四大组件包括前面解析的其他属性都会保存在这个pkg中。
到这里,apk的manifest就算是解析完了,最后,调用scanPackageLI的重载方法完成数据结构的归档,统一整理到PKMS的结构中,并通过install命令添加data/data/pkgName目录。
private PackageParser.PackagescanPackageLI(File scanFile, int parseFlags, int scanFlags, long currentTime,UserHandle user) ;
private PackageParser.Package scanPackageLI(PackageParser.Packagepkg, int parseFlags, int scanFlags, long currentTime, UserHandle user);
PKMS的成员对象如下:
一次将之前的pkg信息填充到PKMS的数据结构中。
主体流程图如下:
PMS_DATA_SCAN_START
到达Data Scan Start阶段时,开始处理非系统app。
mOnlyCore= false,则scanDirLI()收集如下目录中的apk
- data/app
- /data/app-private
PMS_SCAN_END
说明:
- 为app分配linux用户组ID,授予权限(第三部分会详细介绍);
- 设置默认浏览器;
- 通过Settings将数据写入packages.xml文件,以便下次开机后直接读取;
PMS_READY
到这里我们的两个同步锁中的内容就跑完了。PKMS初始化完成阶段,还会创建一个PackageInstaller服务。
启动流程小结
PKMS初始化过程,分为5个阶段:
- PMS_START阶段:
- 创建Settings对象;
- 将6类shareUserId到mSettings;
- 初始化SystemConfig;
- 创建名为“PackageManager”的handler线程mHandlerThread;
- 创建UserManagerService多用户管理服务;
- 通过解析4大目录中的xmL文件构造共享mSharedLibraries;
- PMS_SYSTEM_SCAN_START阶段:
- mSharedLibraries共享库中的文件执行dexopt操作;
- system/framework目录中满足条件的apk或jar文件执行dexopt操作;
- 扫描系统apk;
- PMS_DATA_SCAN_START阶段:
- 扫描/data/app目录下的apk;
- 扫描/data/app-private目录下的apk;
- PMS_SCAN_END阶段:
- 将上述信息写回/data/system/packages.xml;
- PMS_READY阶段:
- 创建服务PackageInstallerService;