请支持作者原创:

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 resourcesxxx.apk可打印出资源表

  • aapt d xmltreexxx.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.0Created-By: 1.0 (Android)Name: AndroidManifest.xmlSHA1-Digest: bbFIUGA27QAiLBforTb0B+GeAk8=Name: lib/armeabi-v7a/libhello-jni.soSHA1-Digest: imJ95BzGT7rI57k7xMnRNBKysgo=Name: lib/x86/libhello-jni.soSHA1-Digest: /QM7n8FQYZ7gTjHAfsNzcoPZsnY=Name: lib/mips/libhello-jni.soSHA1-Digest: A2WeX1JI3csDX8v/PapWe97Gx9k=Name: resources.arscSHA1-Digest: rzak75eaqS4baaz+03KawAATuNk=Name: lib/armeabi/libhello-jni.soSHA1-Digest: 3SGVYUG2+pMhNqB7K5m/T4iA+K8=Name: classes.dexSHA1-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.0Created-By: 1.0 (Android)SHA1-Digest-Manifest: PDEMo/mMNPiPsuYop2qQpb9VjX0=1Name: AndroidManifest.xmlSHA1-Digest: sbIUZUy5AB2lb4RMZzyqlf+JEuw=Name: lib/armeabi-v7a/libhello-jni.soSHA1-Digest: cP7n4f23m5CWostVb5+C65095Oo=Name: lib/x86/libhello-jni.soSHA1-Digest: vFuCcAZFIQgcEkc6a6L395gkbdQ=Name: lib/mips/libhello-jni.soSHA1-Digest: 98f4yVRHgXH5cKKnufCTK7EbUVs=Name: resources.arscSHA1-Digest: OynBh/Tw+yCvo6RPdC5subo2+HE=Name: classes.dexSHA1-Digest: NTB2dedbG357wxDmiiD7dHTFp6E=Name: lib/armeabi/libhello-jni.soSHA1-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.xmlLOCAL_MODULE_CLASS := ETC# This will install the file in /system/etc/permissions#LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/permissionsLOCAL_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=/systemexport CLASSPATH=$base/framework/pm.jarexec app_process $base/bin com.android.commands.pm.Pm "$@"

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

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

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


更多相关文章

  1. Android 实现 选择文件对话框
  2. Android中获得上下文的静态方法
  3. Android attrs文件(自定义)属性详解
  4. Android 使用AsyncHttpClient文件上传与下载
  5. Android 打开浏览器的几种方法
  6. AndroidManifest文件中android属性
  7. Unity调用Android配置方法
  8. Android 的相关文件类型
  9. Android 滑动手势侦测方法介绍

随机推荐

  1. android service
  2. Android(安卓)弹出对话框Dialog
  3. Android(安卓)源码编译so,jar
  4. 【Android】基础1
  5. Android(安卓)SmartRecyclerView
  6. Android二级分类列表GirdView
  7. Android(安卓)ADT 20.0.0 发布
  8. android(9)_数据存储与访问3_scard_login
  9. android 用到的技巧集
  10. Android中Handler的作用和使用方法