首页 > 代码库 > [gitbook] Android框架分析系列之Android PackageManager
[gitbook] Android框架分析系列之Android PackageManager
请支持作者原创:
https://mr-cao.gitbooks.io/Android/content/android-traces.html点击打开链接
Android PackageManager
- 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文件。
├── 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工具查看资源表。
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-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文件内容如下:
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.下面的命令展示了这一过程:
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目录,用来保存应用特定的数据。
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
扫描过程分为两个步骤:
扫描APK安装包的AndroidManifest.xml文件和提取证书信息,以此信息构建一个PackageParser.Package对象;
进一步处理第一个步骤获得的PackageParser.Package对象:
创建Package对应的PackageSetting对象,这个对象记录了应用包的一些设置信息;
验证签名信息
安装应用程序,包括创建应用程序数据目录,动态库的提取,对科执行代码进行优化等
最后一个步骤就是将Package的组件(ContentProvider,Activity,Service,BroadcastReceiver)发布到PackageManagerService中,这样其他应用就可以通过使用公开的组件。比如通过Intent启动Activity或者Service。
下面是一张流程图,描述了系统第一次开机过程中对/data/app目录扫描的过程。因为扫描是个很复杂的过程,所以在流程图中省略了一些异常的case,主要集中在扫描安装这条主线上,对于升级等流程的判断,以及签名信息不符合的判断都进行了省略。
上图中标记红色的部分是扫描安装过程中几个关键的步骤。
在collectCertificate sLI方法中会对应用包的整数进行验证,检查应用包的完整性和合法性,防止应用程序包被篡改。
1mSetting.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