概述
PackageManagerService
作为Android系统中最常用的服务之一,是我们经常要与之打交道的。应用的安装、卸载、优化以及系统已安装应用信息的扫描和查询等应用管理工作都是 PMS 来完成的。
与其它系统服务的实现类似,应用管理也采用了经由 Binder
调用的远程服务机制。PackageManager
为暴露给用户的接口,PackageManagerService
为接口的底层实现。
PMS(下文会以此来代替PackageManagerService
)在启动时会扫描所有的 APK 文件和 Jar 包,然后把他们的信息读取出来,保存在内存中,这样系统运行时就能迅速找到各种应用和组建的信息。扫描中如果遇到没有优化过的文件还要进行优化工作(dex格式转换成oat格式(Android5.0以前是odex)),优化后的文件放在 /data/dalvik-cache/ 下面,可以看一下这篇文章。
PMS要扫描的应用分为系统应用和普通应用通常都在下面的三个目录中:/system/app,/system/priv-app,/data/app。
- 系统应用:安装在/system/app,/system/priv-app,/vendor/app或者/oem/app中。/system/app存放的是一些系统级的应用,比如电话和联系人等,/system/priv-app存放的是系统底层的应用,比如SystemUI,Setting和Laucher等。通常情况下这些应用是不能卸载的,可以升级的,升级的安装包放在/data/app下面
- 普通应用:一般指用户安装的第三方应用,位于/data/app中。
/data/dalvik-cache
目录下面保存的就是被优化过的APK文件和Jar文件。/data/data/<包名>/
保存的是应用的一些数据,包括数据库和一些设置文件。
本文的代码是基于Android6.0来进行介绍。
Android应用管理系统相关代码位置:
1 | frameworks/base/core/java/android/content/pm/ |
PackageManagerService及相关类介绍
下图列出了 PMS 及客户端的类关系:
ApplicationPackageManager
为什么首先介绍ApplicationPackageManager
呢?因为我们客户端和 PMS 打交道全靠它了。
不管我们是想安装还是卸载应用,都要首先调用的是Context
的getPackageManager()
来获取PackageManager
,这便是我们的主角ApplicationPackageManager
。
当我们通过如下代码来获取系统安装应用的信息:
1 | final PackageManager packageManager = getPackageManager(); |
便会调用到ApplicationPackageManager.queryIntentActivities(Intent intent,Intent intent,)
方法。ApplicationPackageManager
继承自 PackageManager
,它的初始化是在ContextImpl
中进行的:
1 |
|
PackageManager
类定义了应用可以操作PMS的接口。ApplicationPackageManager
可以称为PMS的代理对象,它通过它的成员变量mPM
来与PackageManagerService
进行进程间的通信,mPM
指向一个IPackageManager.Stub.Proxy
类型的对象。我们调用ApplicationPackageManager
的方法,方法内都是通过调用mPM
的相应方法来实现的。
IPackageManager
IPackageManager.java
是通过IPackageManager.aidl
来生成的,源文件在out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/content/pm/IPackageManager.java
,如果没有编译过代码的话,也可使用aidl工具单独处理IPackageManager.aidl
。IPackageManager
接口类中定义了服务端和客户端通信的业务函数,还定义了内部类Stub
,该类继承自Binder
并实现了IPackageManager
接口。Stub
类中定义了一个内部类Proxy
,该类有一个IBinder
类型(实际类型为BinderProxy
)的成员变量mRemote
,它用于和服务端PackageManagerService
通信。
PackageManagerService
PMS 继承自IPackageManager.Stub
,Stub
类从Binder
派生,因此 PMS 将作为服务端参与Binder
通信。
先来看几个重要的成员变量:
- mInstallerService:
PackageInstallerService
的实例,一个应用的安装时间比较长,Android就用PackageInstallerService
来管理应用的安装过程。在构造函数的最后创建。 - mInstaller:被
@GuardedBy
注解标记,它是Installer
的实例,用于和Deamon
进程installd
交互。实际上系统中进行APK格式转换、建立数据目录等工作都是由installd
进程来完成的,后面会有介绍。 - mSettings:
Settings
类的实例,保存一些PMS动态设置信息,后面会详细介绍; - mPackages:是被
@GuardedBy
注解标记的,代表系统中已经安装的package; - mExpectingBetter:被升级过的应用列表
- mOnlyCore:用于判断是否只扫描系统库
PMS的创建是在SystemServer
中的SystemServer().run()
->startBootstrapServices()
方法中进行的,它通过调用PackageManagerService.main
来实现:
1 | mPackageManagerService = PackageManagerService.main(mSystemContext, installer, |
在startCoreServices()
和startOtherServices()
中调用:
1 | mPackageManagerService.getUsageStatsIfNoPackageUsageInfo(); |
DefaultContainerService
DefaultContainerService
服务,这个服务执行一些针对的文件复制和删除等相关工作
相关adb命令
adb shell pm
1 | adb shell pm |
adb shell dumpsys package
1 | $ adb shell dumpsys package com.android.hq.ganktoutiao |
PackageManagerService 初始化
PMS 方法名LI、LP的含义
PMS中很多的方法都带有LI、LP、Lpr、LPw这样的后缀,它们代表着什么意思呢?首先进行一下这个释疑,以助于理解这些方法。
先看一下代码注释的解释:
1 | // Lock for state used when installing and doing other long running |
这下就比较容易理解了:
- LI:带LI后缀的方法名表示该函数被调用时需要持有
mInstallLock
锁 - LP:带LP后缀的方法名表示该函数被调用时需要持有
mPackages
锁 - LPr:表示读
- LPw:表示写
main函数和构造函数分析
1 | public static PackageManagerService main(Context context, Installer installer, |
我们可以看到main
函数其实很简单,就是调用了一下PMS的构造函数以及注册了 PMS,但是 PMS 的构造函数却做了很多工作,代码有将近500行之多,下面就详细分析一下 PMS 的构造函数。
构造函数的主要工作:包括读取配置文件、优化APK文件和Jar包、扫描系统中所有安装的应用以及提取这些应用的信息并保存起来。
下面就分析一些PMS的构造函数,主要做了以下的工作:
- 变量的初始化工作,包括mSettings,mInstaller,mPackageDexOptimizer等等
- 读取配制文件
- 扫描系统Package,包含Dex优化,
- 保存扫描信息
- 扫描非系统应用
- 更新数据
一些初始化工作
1 | public PackageManagerService(Context context, Installer installer, |
读取配制文件
1 | …… |
扫描系统Package
这部分工作主要是扫描系统应用以及Jar包
1 | // 设置模块来代替framework-res.apk中缺省的ResolverActivity |
保存扫描信息
将上面扫描得到的信息进行整理,并保存到相对应的配置文件中去。
1 | // 这个列表记录的是可能有升级包的系统应用 |
扫描非系统应用
1 | // 开始处理非系统应用 |
更新数据
将 Settings
的数据写入 packages.xml 中,实例化 mInstallerService
。
1 | // 更新所有应用的动态库路径 |
至此,构造函数分析结束,构造函数的执行过程就是先读取保存在packages.xml文件的扫描结果,保存在Settings中,然后扫描几个目录下的文件,并对比上次扫描结果更新packages.xml文件。