首页 > 代码库 > [gitbook] Android框架分析系列之Android PackageManager

[gitbook] Android框架分析系列之Android PackageManager

请支持作者原创:

https://mr-cao.gitbooks.io/Android/content/android-traces.html点击打开链接


Android PackageManager

Table of Contents
  • 1. Android APK文件
    • 1.1. APK签名
  • 2. APK文件的安装
    • 2.1. PackageManagerService
    • 2.2. installd
    • 2.3. PackageInstaller
    • 2.4. pm 命令

本文章围绕着Android的包管理机制,着重分析Android的包格式(包括签名),以及应用程序的安装,升级以及卸载过程。

1. Android APK文件

Android的应用程序以后缀名为apk的文件发布。APK文件中包含应用程序的代码和资源数据,清单文件以及签名信息。APK文件格式扩展于JAR格式,而JAR文件格式又是扩展自ZIP格式。所以,可以使用ZIP解压工具,打开APK文件。

一个典型的APK文件内容如下:
├── AndroidManifest.xml    (1)
├── classes.dex            (2)
├── lib                    (3)
│ ├── armeabi
│ │   └── libhello-jni.so
│ ├── armeabi-v7a
│ │   └── libhello-jni.so
│ ├── mips
│ │   └── libhello-jni.so
│ └── x86
│     └── libhello-jni.so
├── META-INF               (4)
│   ├── CERT.RSA
│   ├── CERT.SF
│   └── MANIFEST.MF
└── resources.arsc         (5)

<1>:AndroidManifest.xml文件中包含了应用程序的组件,包名,版本等信息。

<2>:classes.dex是应用的可执行代码。

<3>:对于使用了ndk编程的应用来说,APK中还有lib文件夹,这个文件夹下面存放的是适配各个平台的共享库。

<4>:包含应用的签名和清单文件,这部分内容后面还会讲述。

<5>:对于可编译的资源,最后都编译好后,打包进了resources.arsc文件,比如string和style。对于编译好的APK文件,可以使用aapt工具查看资源表。

An Example aapt 用法
  • aapt d resources xxx.apk可打印出资源表

  • aapt d xmltree xxx.apk res/layout/main.xml 打印布局文件main.xml的内容

更多aapt的用法,参考aapt -help。

1.1. APK签名

与APK签名相关的文件都位于META-INF目录下。下面会对三个文件做一些简单的分析,关于加密解密算法的实现,超出了本文的范围,这里不会涉及到。

1.1.1. MANIFEST.MF

后缀名为.MF的文件,里面存放的是打包在APK中的文件的Message digest(消息摘要)。

MANIFEST.MF的内容
Manifest-Version: 1.0
Created-By: 1.0 (Android)

Name: AndroidManifest.xml
SHA1-Digest: bbFIUGA27QAiLBforTb0B+GeAk8=

Name: lib/armeabi-v7a/libhello-jni.so
SHA1-Digest: imJ95BzGT7rI57k7xMnRNBKysgo=

Name: lib/x86/libhello-jni.so
SHA1-Digest: /QM7n8FQYZ7gTjHAfsNzcoPZsnY=

Name: lib/mips/libhello-jni.so
SHA1-Digest: A2WeX1JI3csDX8v/PapWe97Gx9k=

Name: resources.arsc
SHA1-Digest: rzak75eaqS4baaz+03KawAATuNk=

Name: lib/armeabi/libhello-jni.so
SHA1-Digest: 3SGVYUG2+pMhNqB7K5m/T4iA+K8=

Name: classes.dex
SHA1-Digest: 8XJt/CggZUAQU6ULBavrzodpwok=

对于APK包中的文件先使用hash函数获取Message Digest,然后采用base64编码得到的结果作为文件的最终Message Digest。MANIFEST.MF中的每一条目都是由Name和SHA1-Digest组成。Name对应的是文件名,SHA1-Digest对应的文件的Message Digest。

可以使用如下的命令生成Message Digest:

$ openssl sha1 -binary HelloJni/classes.dex | openssl base64

1.1.2. CERT.SF文件

CERT.SF文件中存放的也是Message Digest。与上面MANIFEST.MF文件对应的CERT.SF文件内容如下:

CERT.SF 内容
Signature-Version: 1.0
Created-By: 1.0 (Android)
SHA1-Digest-Manifest: PDEMo/mMNPiPsuYop2qQpb9VjX0=1

Name: AndroidManifest.xml
SHA1-Digest: sbIUZUy5AB2lb4RMZzyqlf+JEuw=

Name: lib/armeabi-v7a/libhello-jni.so
SHA1-Digest: cP7n4f23m5CWostVb5+C65095Oo=

Name: lib/x86/libhello-jni.so
SHA1-Digest: vFuCcAZFIQgcEkc6a6L395gkbdQ=

Name: lib/mips/libhello-jni.so
SHA1-Digest: 98f4yVRHgXH5cKKnufCTK7EbUVs=

Name: resources.arsc
SHA1-Digest: OynBh/Tw+yCvo6RPdC5subo2+HE=

Name: classes.dex
SHA1-Digest: NTB2dedbG357wxDmiiD7dHTFp6E=

Name: lib/armeabi/libhello-jni.so
SHA1-Digest: mjzjxE6Pt0Lrz4k5PeKTONgSh5A=

CERT.SF中的第一条内容来自对MANIFEST.MF文件Message Digest的结果;其他的条目分别是对MANIFEST.MF中对应的条目做Message Digest.下面的命令展示了这一过程:

An Example CERT.SF文件内容生成
  • openssl sha1 -binary HelloJni/META-INF/MANIFEST.MF | openssl base64

    其结果为:PDEMo/mMNPiPsuYop2qQpb9VjX0=
  • echo -en "Name: lib/armeabi-v7a/libhello-jni.so\r\nSHA1-Digest: imJ95BzGT7rI57k7xMnRNBKysgo=\r\n\r\n" | openssl sha1 -binary | openssl base64

    其结果为:cP7n4f23m5CWostVb5+C65095Oo=

通过上面例子的结果,可以知道,CEART.SF中的内容来自于对MANIEST.MF文件以及文件中的条目做Message Digest的结果。

1.1.3. CERT.RSA 文件

CERT.RSA文件中存放着CERT.SF的数字签名,以及签名证书。如果存在多个.SF和.RSA文件,那么每一对的文件名都需要匹配。比如CERT1.SF对应着CERT1.RSA,CERT2.SF对应CERT2.RSA。

可以使用如下命令查看CERT.RAS文件中存放的证书.

openssl pkcs7 -inform DER -in HelloJni/META-INF/CERT.RSA -noout -print_certs -text

使用下面命令提取证书:

openssl pkcs7 -inform DER -print_certs -in HelloJni/META-INF/CERT.RSA -out cert.pem

使用如下命令打印证书内容:

openssl x509 -in cert.pem -noout -text

可以使用如下命令验证SF文件的有效性:

openssl smime -verify -in HelloJni/META-INF/CERT.RSA -inform DER -content HelloJni/META-INF/CERT.SF -CAfile cert.pem

2. APK文件的安装

APK的安装主要有三种方式:

  • 对于在/system/app和/data/app目录下的APK文件,在PackageManagerService的启动过程中,会扫描安装;

  • 通过PackageInstaller的方式安装;

  • pm命令行的方式安装。这种方式主要是用在开过过程中。

    1

安装成功的APK,PackageManagerService会生成”记账簿“来保存必要的信息。主要有以下两个文件:

  • /data/system/packages.list

  • /data/system/packages.xml

packages.list文件中保存着应用的包名,uid,所属的用户,以及“home”目录.因为Android中每一个应用都类似linux系统中user的概念,它们都有自己的home目录,用来保存应用特定的数据。

An Example packages.list文件

com.android.defcontainer 10002 0 /data/data/com.android.defcontainer

com.android.providers.contacts 10021 0 /data/data/com.android.providers.contacts

com.android.quicksearchbox 10027 0 /data/data/com.android.quicksearchbox

com.android.demo.notepad3 10011 0 /data/data/com.android.demo.notepad3

com.android.contacts 10021 0 /data/data/com.android.contacts

com.android.systemui 10013 0 /data/data/com.android.systemui

com.android.packageinstaller 10012 0 /data/data/com.android.packageinstaller

com.android.calendar 10023 0 /data/data/com.android.calendar

packages.xml文件中保存着应用程序的详细信息,包括包名,代码路径,jni库路径,应用标记,签名,权限等。

对于APK的三种安装方式,接下来的章节会详细的讲到。

2.1. PackageManagerService

PackageManagerService由system_server启动,它全面负责应用包的安装,卸载,权限检查等工作。在每次开机的时候,PackageManagerService都会在其构造函数中,对指定的目录的APK进行扫描。对于没有安装的APK文件会触发安装过程。 下面我们对PackageManagerService构造函数中出现的重点函数和重点流程展开介绍。

2.1.1. readPermissions 方法简介

PackageManagerService的readPermissions方法负责读取/etc/permission目录下面的配置文件。这些配置文件中保存的信息有:系统支持的硬件,比如是否支持wifi,gps等;权限映射关系。

描述系统支持的硬件特性的文件,一般满足这样的命名规范:android.hardware.XXX.xml,XXX代表硬件模块名。下面是 samsung manta 的nfc特性文件——android.hardware.nfc.xml的内容:

<?xml version="1.0" encoding="utf-8"?>
<permissions>
    <feature name="android.hardware.nfc" />
</permissions>

读取出来的feature保存在HashMap中:

// These are the features this devices supports that
// were read from the etc/permissions.xml file.
final HashMap<String, FeatureInfo> mAvailableFeatures =
       new HashMap<String, FeatureInfo>();

应用程序通过PackageManager类可以查询指定的feature系统是否支持,以及获得所有系统支持的feature.

frameworks/base/core/java/android/content/pm

public abstract FeatureInfo[] getSystemAvailableFeatures();
public abstract boolean hasSystemFeature(String name);

设备目录/etc/permissions下面的特性文件来自于哪里呢?它们实际上是在编译的时候打包到system.image文件中。比如上面的samsung manta 的nfc特性文件就是在manta的device.mk文件中将frameworks/native/data/etc/android.hardware.nfc.xml文件copy到system/etc/permissions/android.hardware.nfc.xml。

/etc/permissions目录下面还有一个非常重要的xml文件——platform.xml,这个文件中记录了Android APP权限与gid,uid的对应关系。这个文件在源码的位置:frameworks/base/data/etc。在这个目录下面还有一个Android.mk文件,负责将platform.xml编译到system镜像中:

LOCAL_PATH := $(my-dir)

########################
include $(CLEAR_VARS)

LOCAL_MODULE := platform.xml

LOCAL_MODULE_CLASS := ETC

# This will install the file in /system/etc/permissions
#
LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/permissions

LOCAL_SRC_FILES := $(LOCAL_MODULE)

include $(BUILD_PREBUILT)

上面的例子也给我们提供了另一种参考:如何将配置文件编译到system/ect/permissions中。

下面是platform.xml文件中的部分内容:

<?xml version="1.0" encoding="utf-8"?>
<permissions>
    <permission name="android.permission.BLUETOOTH_ADMIN" >
        <group gid="net_bt_admin" />
    </permission>

    <permission name="android.permission.BLUETOOTH" >
        <group gid="net_bt" />
    </permission>

    …………
    <assign-permission
        name="android.permission.WRITE_EXTERNAL_STORAGE"
        uid="shell" />

    <assign-permission
        name="android.permission.SEND_SMS"
        uid="shell" />

    …………


    <library name="android.test.runner"
        file="/system/framework/android.test.runner.jar" />

    <library name="javax.obex"
        file="/system/framework/javax.obex.jar"/>
</permissions>

platform.xml中主要有三块内容:

  • 将APP framework中的权限和底层的gid映射。当APP获得某个权限之后,会获得这个gid所具备的权限。

  • 将APP framework的权限赋予某个系统级别的进程。这样这个进程就可以获得操作APP framework资源的。

  • jar库文件的映射。APP中通过指定链接的jar库名,通过这层映射关系,可以在链接的找到正确的jar库。

总结:

readPermissions方法读取/ect/permissions目录下的xml文件,并为读取的结果生成相应的数据结构。

2.1.2. readLPw和writeLPr方法简介

在读取完权限文件之后,PackageManagerService会在其构造函数中调用Settings的readLPw方法,读取应用包的设置文件。

  • /data/system/packages.xml

  • /data/system/packages-backup.xml

  • /data/system/packages.list

  • /data/system/users/userid/package-restrictions.xml

对于packages.xml和packages.list在之前已经简单的介绍过了,packages-backup.xml是packages.xml的备份文件。在每次写packages.xml文件的时候,都会将旧的packages.xml文件先备份,这样做是为了防止写文件过程中文件以外损坏,还能从旧的文件中恢复。package-restrictions.xml保存着受限制的APP的状态,比如某个APP处于disable状态,或者某个APP具有更高的优先级等。这里举一个例子:

$adb shell pm disable com.android.providers.drm

运行上述命令之后,package-restrictions.xml文件就会存在一条受限制的记录:

<pkg name="com.android.providers.drm" enabled="2" />

关于enable的含义可以参考:PackageManager.java中定义的常量:

public static final int COMPONENT_ENABLED_STATE_DEFAULT = 0;
public static final int COMPONENT_ENABLED_STATE_ENABLED = 1;
public static final int COMPONENT_ENABLED_STATE_DISABLED = 2;
public static final int COMPONENT_ENABLED_STATE_DISABLED_USER = 3;

readLPw方法负责读取packages.xml文件。它的逻辑是如果存在packages-backup.xml,就认为packages.xml已经损坏,将之删除。然后从packages-backup.xml中读取信息,用读取的信息构造一个PackageSetting对象,然后以包名为key,PackageSetting为value,保存在HashMap中。

final HashMap<String, PackageSetting> mPackages =
    new HashMap<String, PackageSetting>();

现在可以总结下readLPw的执行过程:

  • 如果系统为第一次开机,则调用readDefaultPreferredAppsLPw方法,读取/etc/preferred-apps目录下的xml文件。这个目录下面的xml文件提供了默认的优先APP的设置;

  • 读取packages.xml文件

  • 调用readPackageRestrictionsLPr方法,读取package-restrictions.xml文件

2.1.3. scanDirLI

在PackageManagerService的构造函数中,会调用此方法,对以下目录下的APK文件进行扫描:

  • /system/framework

  • /system/app

  • /data/app

扫描过程分为两个步骤:

  1. 扫描APK安装包的AndroidManifest.xml文件和提取证书信息,以此信息构建一个PackageParser.Package对象;

  2. 进一步处理第一个步骤获得的PackageParser.Package对象:

    • 创建Package对应的PackageSetting对象,这个对象记录了应用包的一些设置信息;

    • 验证签名信息

    • 安装应用程序,包括创建应用程序数据目录,动态库的提取,对科执行代码进行优化等

    • 最后一个步骤就是将Package的组件(ContentProvider,Activity,Service,BroadcastReceiver)发布到PackageManagerService中,这样其他应用就可以通过使用公开的组件。比如通过Intent启动Activity或者Service。

下面是一张流程图,描述了系统第一次开机过程中对/data/app目录扫描的过程。因为扫描是个很复杂的过程,所以在流程图中省略了一些异常的case,主要集中在扫描安装这条主线上,对于升级等流程的判断,以及签名信息不符合的判断都进行了省略。 技术分享

上图中标记红色的部分是扫描安装过程中几个关键的步骤。

  • 在collectCertificate sLI方法中会对应用包的整数进行验证,检查应用包的完整性和合法性,防止应用程序包被篡改。

    1

  • mSetting.getPackageLPw方法会为Package创建对应的PackageSetting对象。这个对象中保存的信息最后会通过writeLPr写入到/data/system/packages.xml文件中去。

  • createDataDirsLI方法会给installd发送消息,为应用程序创建对应的数据目录。

  • copyNativeLibrariesForInternalApp会将应用程序包的动态库复制到对应的动态库目录。

  • performDexOptLI方法也是调用installd发送消息,优化可执行代码。优化完毕的可执行代码存放在目录/data/dalvik-cache/目录下面。

上面提到的installd,将在下一个小节讲解。PackageManagerService扫描完APK之后,会在其内部建立复杂的数据结构。这里我们只以简单的一张类图感受下两个重要的类——PackageManagerService和Settings的关系。 技术分享

简单的介绍下上面的图:

  • Package对象中的数据是从APK文件中扫描而得到,主要来源于AndroidManifest.xml文件

  • PackageSetting的父类是PackageSettingBase,这个类中的数据除了第一次开机以外,都是从packages.xml或者packages-backup.xml

  • Settings中以包名为key保存着所有的Package对应的设置信息;同时也保存着SharedUser相关的信息。应用程序的“记账簿”在开机过程中都会被读入到内存中,而这就是由Settings负责。

2.2. installd

上面讲到,PackageManagerService在扫描过程中会给installd发送消息,请求创建应用程序的数据目录和执行代码优化。那这个installd是什么东西呢?为什么需要创建这么个模块?

installd的代码路径:

frameworks/base/cmds/installd

在应用程序的管理工作中,有时候需要对存储设备做一些操作,比如创建目录修改目录权限等,这些操作有的是需要特权级的权限。PackageManagerService存活在system_server进程中,这个进程的用户为system,它是没有特权级的权限的,所以我猜想,出于安全的角度考虑,Android单独将一部分需要特权的工作,转交给installd进程去完成。

在installd的入口函数中,首先是做一些初始化的工作,然后放弃自己的root用户身份,改变了自己的用户类型和组用户类型,但同时它保留了设置目录权限,以及修改目录属主的权限:

static void drop_privileges() {
    if (prctl(PR_SET_KEEPCAPS, 1) < 0) {
        ALOGE("prctl(PR_SET_KEEPCAPS) failed: %s\n", strerror(errno));
        exit(1);
    }

    if (setgid(AID_INSTALL) < 0) {
        ALOGE("setgid() can‘t drop privileges; exiting.\n");
        exit(1);
    }

    if (setuid(AID_INSTALL) < 0) {
        ALOGE("setuid() can‘t drop privileges; exiting.\n");
        exit(1);
    }

    struct __user_cap_header_struct capheader;
    struct __user_cap_data_struct capdata[2];
    memset(&capheader, 0, sizeof(capheader));
    memset(&capdata, 0, sizeof(capdata));
    capheader.version = _LINUX_CAPABILITY_VERSION_3;
    capheader.pid = 0;

    capdata[CAP_TO_INDEX(CAP_DAC_OVERRIDE)].permitted |= CAP_TO_MASK(CAP_DAC_OVERRIDE);
    capdata[CAP_TO_INDEX(CAP_CHOWN)].permitted        |= CAP_TO_MASK(CAP_CHOWN);
    capdata[CAP_TO_INDEX(CAP_SETUID)].permitted       |= CAP_TO_MASK(CAP_SETUID);
    capdata[CAP_TO_INDEX(CAP_SETGID)].permitted       |= CAP_TO_MASK(CAP_SETGID);

    capdata[0].effective = capdata[0].permitted;
    capdata[1].effective = capdata[1].permitted;
    capdata[0].inheritable = 0;
    capdata[1].inheritable = 0;

    if (capset(&capheader, &capdata[0]) < 0) {
        ALOGE("capset failed: %s\n", strerror(errno));
        exit(1);
    }
}

installd也算是个服务端了,它是通过socket来接收客户端的消息,然后进行处理。在main函数中,通过for循环从socket中读取消息,然后调用execute处理消息。

installd是通过init创建,在init.rc文件中有配置它的socket名:

service installd /system/bin/installd
    class main
    socket installd stream 600 system system

所以installd进程创建的socket名为:/dev/socket/installd, 600表示socket的用户权限为可读可写,system表示用户和用户组。

installd可以处理的消息为:

struct cmdinfo {
    const char *name;
    unsigned numargs;
    int (*func)(char **arg, char reply[REPLY_MAX]);
};

struct cmdinfo cmds[] = {
    { "ping",                 0, do_ping },
    { "install",              3, do_install },
    { "dexopt",               3, do_dexopt },
    { "movedex",              2, do_move_dex },
    { "rmdex",                1, do_rm_dex },
    { "remove",               2, do_remove },
    { "rename",               2, do_rename },
    { "fixuid",               3, do_fixuid },
    { "freecache",            1, do_free_cache },
    { "rmcache",              2, do_rm_cache },
    { "getsize",              5, do_get_size },
    { "rmuserdata",           2, do_rm_user_data },
    { "movefiles",            0, do_movefiles },
    { "linklib",              3, do_linklib },
    { "mkuserdata",           3, do_mk_user_data },
    { "rmuser",               1, do_rm_user },
    { "cloneuserdata",        3, do_clone_user_data },
};

在PackageManagerService扫描安装应用的过程中,给installd先后发送了如下消息:

  • install

  • dexopt

这三个消息分别由do_install,do_dexopt函数来处理。

  • do_install函数为应用创建了以下目录,在install过程中,同时为创建的目录设置uid和gid。

    • 数据目录:/data/data/packageName/

    • lib目录:/data/app-lib/packageName

    • lib符号链接:/data/data/packageName/lib → /data/app-lib/packageName

  • do_dexopt中调用dexopt函数处理apk文件。主要流程为:

    • 首先判断apk所在目录下面是否存在同名的odex文件,如果存在就直接返回;

    • 以apk文件的路径为名创建dex路径。比如在debug版本中,/system/app/Settings.apk的dex文件将会保存在: /data/dalvik-cache/system@app@Settings.apk@classes.dex文件中。dex文件命名规则是/data/dalvik-cache字符串拼接apk的路径,再拼“/classs.dex"。然后将”/data/dalvik-cache/“字符串后面的‘/’替换为@字符。

    • 最后调用系统工具/system/bin/dexopt来提取dex文件。

2.3. PackageInstaller

PackageInstaller是Android提供的默认应用程序安装与卸载管理应用程序。应用包中有两个Activity:PackageInstallerActivity和UninstallerActivity分别用来管理应用程序的安装和卸载。这两个Activity可以通过发送Intent的方式启动。下面的代码片段指出了两个Activity能处理的Intent:

<activity android:name=".PackageInstallerActivity"
                android:configChanges="orientation|keyboardHidden|screenSize"
                android:excludeFromRecents="true">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <action android:name="android.intent.action.INSTALL_PACKAGE" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="file" />
                <data android:mimeType="application/vnd.android.package-archive" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.INSTALL_PACKAGE" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="file" />
                <data android:scheme="package" />
            </intent-filter>
 </activity>

<activity android:name=".UninstallerActivity"
                android:configChanges="orientation|keyboardHidden|screenSize"
                android:excludeFromRecents="true"
                android:theme="@android:style/Theme.DeviceDefault.Dialog.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.DELETE" />
                <action android:name="android.intent.action.UNINSTALL_PACKAGE" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="package" />
            </intent-filter>
</activity>

通过给PackageInstallerActivity发送"android.intent.action.VIEW"或者是"android.intent.action.INSTALL_PACKAGE" Intent就能启动对一个APK文件的安装过程。例如,我们假设有一个应用程序位于/mnt/sdcard/Test.apk,为了启动对这个应用程序的安装,可以通过文件浏览器点击这个应用包文件,同时也可以通过adb shell登陆机器,然后输入以下命令:

am start -a android.intent.action.VIEW -t application/vnd.android.package-archive -c android.intent.category.DEFAULT -d file:///mnt/sdcard/Test.apk

当输入上述命令后,首先是以Action等参数构造一个Intent,然后am调用ActivityManagerService的startActivity方法。最后的结果是启动PackageInstallerActivity,并将构造的Intent传递给启动的Activity。下面简单的描述下,PackageInstallerActivity安装应用的过程:

  • 首先PackageInstaller会检查应用程序的完成性,尝试着从应用包中提解析AndroidManifest.xml文件,构造ParckageParser.Package对象。如果成功,则继续安装;否则安装退出

  • 然后对发起安装请求的应用属性进行判断:如果是system应用,那么就进入初始化安装步骤;如果是非system应用,那么存在一个验证是否允许安装“unknown source”的步骤。

  • 如果上面的步骤通过,就进入了初始化安装过程。初始化过程主要是检测应用是否安装,以及提取相关的权限信息,用于界面显示。这一过程主要是UI界面相关,略过不表

  • 当用户在安装界面上点击“确定”按钮,PackageInstallerActivity就会启动InstallAppProgress,在此Activity会调用PackageManager的方法public abstract void installPackageWithVerificationAndEncryption(Uri packageURI, IPackageInstallObserver observer, int flags, String installerPackageName, VerificationParams verificationParams, ContainerEncryptionParams encryptionParams)真正进行安装,并显示安装进度。

2.3.1. installPackageWithVerificationAndEncryption

PackageManager的installPackageWithVerificationAndEncryption最终是一个binder调用,是在PackageManagerService中负责实现。在此方法中,获取一个Message对象,其id为INIT_COPY,其obj为InstallParams。其中InstallParams对象根据installPackageWithVerificationAndEncryption的方法参数构造。INIT_COPY消息是在PackageHandler中进行处理。对于此消息的处理流程为:

  • 首先通过connectToService方法绑定IMediaContainerService服务;在绑定成功之后,发送MCS_BOUND消息;IMeidaContainerService的实现是在frameworks/base/packages/DefaultContainerService/src/com/android/def/DefaultContainerService.java中。它负责原始apk文件的解密工作。

  • 在PackageHandler消息中再次对MCS_BOUND消息进行处理。这次就会调用InstallParams的startCopy方法,开启复制过程。startcopy方法又会调用handleStartCopy进行复制工作。主要做了如下工作:

    • 确定APK的安装位置

    • 检查磁盘空间是否足够

    • 以InstallParams构造一份InstallArgs对象。InstallArgs有两个子类:AsecInstallArgs和FileInstallArgs,对于安装于外部sd卡或者是有Forward Lock属性的应用,将会构造AsecInstallArgs,否则构造的是FileInstallArgs对象。

    • 检查APK是否需要验证(一般是不需要)

    • 调用InstallArgs对象的copyApk方法。

所以,handleStartCopy最重要的工作就是由InstallParams的copyApk来完成。假设在这个InstallParams引用是一个FileInstallArgs对象,那么其copy流程如下:

  • 创建临时文件。对于最终安装在/data/app目录下面的应用,其临时文件的共享名为:/data/app/vmdl-XXX.tmp。这个XXX是一个随机数

  • 调用IMediaContainerService的服务接口,从原始APK文件中提取出解密过的APK数据,写入到临时文件中

  • 拷贝jni动态库到相应的目录

在InstallParams的startCopy处理完handleStartCopy之后,会调用handleRetureCode方法,此方法中将会进行触发真正的安装流程。

void handleReturnCode() {
    if (mArgs != null) {
        processPendingInstall(mArgs, mRet);

        if (mTempPackage != null) {
            if (!mTempPackage.delete()) {
                Slog.w(TAG, "Couldn‘t delete temporary file: " +
                        mTempPackage.getAbsolutePath());
            }
        }
    }
}

在handleReturnCode中又调用了processPendingInstall方法,这个方法主要做了以下几件事情:

  • 调用FileInstallArgs对象的doPreInstall方法:如果之前的流程有错误,此方法将会执行清理动作

  • 调用PackageManagerService的installPackageLI方法,开始安装过程

  • 调用FileInstallArgs对象的doPostInstall方法:如果之前的流程有错误,此方法将会执行清理动作

installPackgeLI执行步骤如下:

  • 从临时文件中提取出Package对象:final PackageParser.Package pkg = pp.parsePackage(tmpPackageFile, null, mMetrics, parseFlags)

  • 提取证书信息,并验证

  • 将临时文件更名为正式apk文件,修改动态库目录名

    +

  • 调用installNewPackageLI安装正式apk文件

    • scanPackageLI方法负责安装apk

    • updateSettingsLI负责更新“记账簿”

2.4. pm 命令

APK的安装,在eng版本的开发中,还可以通过pm命令。pm实际上是一个脚本文件,脚本代码位于frameworks/base/cmds/pm/pm

base=/system
export CLASSPATH=$base/framework/pm.jar
exec app_process $base/bin com.android.commands.pm.Pm "$@"

从中可以看出,利用app_process程序,加载pm.jar包来执行具体的动作。

pm.jar的源码和pm脚本位于同一目录下面。 pm命令安装应用,实际上走的流程和PackageInstaller差不多,都是调用PackageManagerService的installPackageWithVerificationAndEncryption方法。

但是他们也有不同的地方,pm命令可以指定加密解密参数,而PackageInstaller无法指定。


[gitbook] Android框架分析系列之Android PackageManager