Android包管理机制

framework

Posted by Cc1over on January 14, 2019

Android包管理机制

包其实就是一种文件格式,如apk包,jar包。Android中存在着很多包,所有的应用程序都是apk包,很多构成Android运行环境的都是jar包,还有一些以so为后缀的库文件,由于包的种类和数量繁多,就需要进行包管理,而包管理机制中的核心类就是PMS

包的数据结构

Android中的apk和Jjar包都以静态文件的形式分布在不同的硬件分区,而包管理是在内存中进行的,所以这个时候就需要一个工具类将这些包转换为内存中的数据结构,这个工具就是包解析器PackageParser

Android5.0以后,支持APK拆分,即一个APK可以分割成很多部分,位于相同的目录下,每一个部分都是一个单独的APK文件,所有的APK文件具备相同的签名,在APK解析过程中,会将拆分的APK重新组合成内存中的一个Package。对于一个完整的apk,Android称其为Monolithic;对于拆分后的APK,Android称其为Cluster

Android5.0之前,apk文件都是直接位于apppriv-app目录下,比如短彩信APK的目录就是/system/priv-app/Mms.apk;到了Android 5.0之后,多了一级目录结构,譬如短彩信APK的目录是/system/priv-app/Mms/Mms.apk,这是Android为了支持APK拆分而做的改动,如果要将短彩信APK进行拆分,那所有被拆出来的APK都位于/system/priv-app/Mms/即可,这样在包解析时,就会变成以Cluster的方式解析目录

一个包在内存中的数据结构就是package,本文中会忽略一些PackageParser的实现细节,重在梳理流程以及总结解析结果

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

  • Package中存在有许多组件,比如Activity、Provider、Permission等等,他们都继承自基类Component
  • 每个组件都包含一个info数据,比如Activity类中包含了成员变量ActivityInfo,这个ActivityInfo才是真正的Activity数据,这里用了桥接模式,通过泛型的方式实现,所有的info都实现了Parcelable接口,意图很明显,info是可以进行跨进程传递的
  • 四大组件的标签内可能包含来过滤Intent信息,因此IntentInfo来保存组件的intent信息,组件基类Compoent依赖于IntentInfo,IntentInfo有三个子类ActivityIntentInfo、ServiceIntentInfo和ProviderIntentInfo,不同组件依赖的IntentInfo会有所不同,比如Activity继承自`Component` ,Permission继承自`Component`

简单阐述解析过程中的调用的方法,具体逻辑不予分析,仅把关键的流程捋出来,如果读者对具体逻辑很感兴趣,可以去翻阅以下的系列博客

  • PackageParser.parsePackages()是包解析器的入口函数,它首先会判定给定的输入是否为一个目录,如果是目录,则以为着目录下可能存在多个拆分后的APK,这就需要以Cluster的方式进行解析;如果仅仅是一个apk文件,就以Monolithic的方式解析

  • 解析apk,需要先得到一个中间数据结构PacakgeLite,包名、版本、拆分包等信息都会保存在这个数据结构中;由于一个包可能有多个拆分的APK,所以PackageLite可能关联到多个apk,每一个apk都对应到ApkLite这个数据结构,也是一些基本信息的封装。之所以以Lite为后缀命名,是因为这两个数据结构都比较轻量,只保存APK中很少信息

  • 一个apk真正的信息都写在AndroidManifest.xml这个文件中,PackageParser.parseBaseApk()这个函数就是用来解析该文件。其解析过程与AndroidManifest.xml的文件结构一一对应,譬如先解析标签的内容,然后解析其下的,等标签

小结

至此,包解析器PackageParser就将一个静态的文件,转换成了内存中的数据结构Package,它包含了一个包的所有信息,如包名、包路径、权限、四大组件等,其数据来源主要就是AndroidManifest.xml文件

包管理的服务

在管理所有包的同时,包管理者需要对外提供服务,诸如获取包的信息,安装或删除包,解析Intent等,都是包管理者的职能

PackageManager pm = context.getPackageManager();
PackageInfo pi = pm.getPackageInfo("xxxxx", 0);
  • 可以通过这种方式拿到PackageManager然后进行操作,而应用进程中获取的PackageManager对象,只是PMS在应用进程中的一个代理,不同的应用进程都不同的代理,意味着不同应用进程中的PackageManager对象是不同的,但管理者PMS只有一个

  • 运行在应用进程中的PackageManager要与运行在系统进程的PMS进行通信,通信的方式则是BInder机制,因此,会有一个IPackageManager.aidl文件,用于描述两者通信的接口,另外,应用进程中的XXXInfo对象就是由系统进程传递给应用进程的对象

Intent的解析

Android中,使用Intent来表达意图,最终会有一个响应者,而PMS作为所有包信息管理者的中枢,它包揽了解析Intent的任务

Intent的结构

标识一个Intent身份的信息由两部分构成

  • 主要信息:Action和Data。Action用于表明Intent所要执行的操作,譬如ACTION_VIEW,ACTION_EDIT; Data用于表明执行操作的数据,譬如联系人数据,数据是以URI来表达的。再举两个Action和Data成对出现的例子

  • 次要信息:除了主要的标记信息,Intent还可以附加很多额外的信息,Category,Type,Component和Extra

    • Category表示Intent的类别,譬如CATEGORY_LAUNCHER表示要对属于桌面图标这一类的对象执行操作

    • Type表示Intent所操作的数据类型,就是MIMEType,譬如要操作PNG图片,那Type就可以设置为png

    • Component表示Intent要操作的对象

    • Extra表示Intent所传递的数据,这些数据都实现了Parcelable接口

匹配方式
  • 显式: 明确指名需要谁来响应Intent。这一类Intent的解析过程比较简单。
  • 隐式:由系统找到合适的目标来响应Intent。这一类Intent的解析过程比较复杂,由于目标不明确,所以需要经过层层筛选才能找到最合适的响应者。

之所以Intent有主次之分,主要信息是最能表达意图的,而次要信息则是解析规则的一个补充

之所以Intent有显隐之分,是因为解析Intent的方式不同

Intent最后的响应者是一个Android组件,Android的组件都可以定义IntentFilter,每一个Component类中都有一个IntentInfo对象的数组,而IntentInfo则是IntentFilter的子类,匹配关系如下所示

多个IntentFilter之间是或的关系,哪怕其他所有的IntentFilter都匹配失败,只要有一个IntentFilter通过,最终Intent还是找到了可以响应的组件

每一个IntentFilter就像是一个定义了白名单规则的过滤器,只有满足白名单的要求才会放行。IntentFilter的过滤规则,其实就是针对Intent的身份信息的匹配规则,当Intent的身份信息与IntentFilter所规定的要求匹配上,则允许通过;否则,Intent就被过滤掉了。IntentFilter的过滤规则包含以下三个方面

  • Action: 每一个IntentFilter可以定义零个或多个标签,如果Intent想要通过这个IntentFilter,则Intent所辖的Action需要匹配其中至少一个

  • Category: 每一个IntentFilter可以定义零个或多个标签,如果Intent想要通过这个IntentFilter,则Intent所辖的Category必须是IntentFilter所定义的Category的子集,才能通过IntentFilter。譬如Intent设置了两个Category:CATEGORY_LAUNCHER和CATEGORY_MAIN,只有那些至少定义了这两项Category的IntentFilter,才会放行该Intent

  • Data:每一个IntentFilter可以定义零个或多个,数据可以通过类型(MIMEType)和位置(URI)来描述,如果Intent想要通过这个IntentFilter,则Intent所辖的Data需要匹配其中至少一个

解释流程

PMS中有四大组件的Intent解析器,分别是ActivityIntentResolve用于解析发往Activity或Broadcast的Intent,ServiceIntentResolver用于解析发往Service的Intent,ProviderIntentResolver用于解析发往Provider的Intent,系统每收到一个Intent的解析请求时,就会使用应的解析器,它们都是IntentResolver的子类

IntentResolver的职能就是解析Intent,它包含了所有的IntentFilter,同时有一个重要的成员函数queryIntent(),接收Intent作为参数,返回查询结果:一个ResolveInfo对象的数组。因为可能有多个组件来响应一个Intent,所以返回结果是一个数组。可想而知,该函数就是针对输入的Intent,按照前文所述的过滤规则,逐个与IntentFilter进行匹配,直到找到最终的响应者,便加入返回结果的列表

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

前文中提到包查询服务的形式,应用进程通过PackageManager提供的接口,发起跨进程调用,最终接口实现是在系统进程的PMS中。下面我们就分析PMS.queryIntentActivities()函数

public List<ResolveInfo> queryIntentActivities(Intent intent,
        String resolvedType, int flags, int userId) {
    if (!sUserManager.exists(userId)) return Collections.emptyList();
    enforceCrossUserPermission(Binder.getCallingUid(), userId, false, false, "query intent activities");
    // 1. 解析“显式”的Intent
    ComponentName comp = intent.getComponent();
    if (comp == null) {
        if (intent.getSelector() != null) {
            intent = intent.getSelector();
            comp = intent.getComponent();
        }
    }
    if (comp != null) {
        final List<ResolveInfo> list = new ArrayList<ResolveInfo>(1);
        final ActivityInfo ai = getActivityInfo(comp, flags, userId);
        if (ai != null) {
            final ResolveInfo ri = new ResolveInfo();
            ri.activityInfo = ai;
            list.add(ri);
        }
        return list;
    }
    // 2. 解析“隐式”的Intent
    synchronized (mPackages) {
        ... // 省略多用户情况下CrossProfileIntentFilter相关的代码
        List<ResolveInfo> result = mActivities.queryIntent(
                intent, resolvedType, flags, userId);
        ...
        return result;
    }
}
  • 解析“显式”的Intent,如果Intent中有设置Component,则说明已经显式的指名由谁来响应Intent。根据Component可以获取到对应的ActivityInfo,再在封装成ResolveInfo便可作为结果返回了
  • 解析“隐式”的Intent,该处省略了大段与多用户相关的Intent解析逻辑,仅展示了核心的函数调用mActivities.queryIntent(),mActivities是ActivityIntentResolver类型的对象,它负责完成对Intent的解析

其他类似的查询有queryIntentReceivers(), queryIntentServices(), queryIntentContentProviders()

apk安装过程

apk安装时一个比较耗时的操作,PMS将这项工作放到一个服务进程com.android.defcontainer,通过消息传递和跨进程调用的方式来驱动整个安装过程

运行在系统进程中的PMS控制了整个安装流程,具体的安装任务由运行在com.android.defconainer进程的DefaultContainerService来完成

安装情景
  • 通过adb命令安装:adb 命令包括adb push/install
  • 用户下载的Apk,通过系统安装器packageinstaller安装该Apk。packageinstaller是系统内置的应用程序,用于安装和卸载应用程序。
  • 系统开机时安装系统应用。
  • 电脑或者手机上的应用商店自动安装。
安装流程

而关于整个安装的过程,本文也只梳理了流程,若读者对于源码很感兴趣,可以翻阅Android应用安装流程分析

流程图如下所示:

安装过程:复制apk安装包到/data/app目录下,解压并扫描安装包,向资源管理器注入apk资源,解析AndroidManifest文件,并在/data/data目录下创建对应的应用数据目录,然后针对dalvik/art环境优化dex文件,保存到dalvik-cache目录,将AndroidManifest文件解析出的组件、权限注册到PackageManagerService,完成后发送广播

安装过程的时序图如下所示:

上面提到4种安装情景,实际最后也是通过一个进程发起调用,最终都是PMS来响应,跟着图中的时序阐述图流程图中拷贝过程方法调用链:

  • installPackageAsUser():该函数的逻辑较为简单,完成安装权限检查后,便开始构造APK安装信息的数据结构,发出INIT_COPY消息

  • INIT_COPY这个状态下,首先需要做的工作是连接DefaultContainerServcie,当连接成功后,便将APK加入安装队列mPendingInstalls,发出MCS_BOUND消息

  • 在MCS_BOUND状态下,会针一个待安装的APK发起HandlerParam.startCopy()

  • startCopy()方法主要就是做一些校验的操作,就是检查安装次数,若安装次数达到上限则放弃安装,然后调用HandlerParams的handleCopy()方法

  • handleCopy()方法中也存在Handler发送消息以及其他方法的嵌套调用,而整体而言,这个方法做了以下的工作:

    • 确定apk的安装位置,sd卡或者内部存储

    • 获取apk的少量信息,异于上面提到的info,这里用Lite这种数据结构进行封装

    • 判定安装位置

    • 根据安装位置创建InstallArgs对象

  • startCopy()方法完成了apk拷贝及so库的拷贝

至此,apk安装的拷贝过程就完成了,然后下面的解析工作就是上面提到的PackageParser.parsePackage方法,把静态包文件加载到内存中,然后解析完之后就通过调用PMS相应的方法依次进行创建应用数据目录,dexopt操作,四大组件注册,权限注册等操作,这些操作都是由PMS中的scanPackageLI方法完成,有兴趣的读者可以阅读Android应用安装流程分析一文详细阅读源码

总结

包管理机制涉及的数据结构非常多,分析源码的时候很容易过分深入,难以看清整个操作的流程主干,而笔者只是一名大二学生,目前学习的方向是Android应用开发,因此并没有在本文中过分深究源码,主要是从日常开发与包管理机制中联系较为紧密的部分做了分析

感谢:

  • http://liuwangshu.cn/framework/pms/
  • http://duanqz.github.io/2017-01-04-Package-Manage-Mechanism