APK,全称Android Application Package
,即Android应用程序包,是Android系统使用的一种应用程序包文件格式,它的作用是将Android程序
和资源
整合在一起,以便Android程序能在Android设备上正常运行。简单地说,就是一个Android应用程序的代码要想在Android设备上运行,必须先进行编译,然后被打包成一个被Android系统所能识别的文件才可以被运行,而这种能被Android系统识别并运行的文件格式就是"APK
",而文件后缀为".apk
"。
1. APK打包过程
1.1 APK文件结构
APK文件本质上是一个压缩文件,我们将一个APK文件进行解压,它主要包含以下文件:
assets目录
:存放原生的静态资源文件,如图片、JSON配置文件、二进制数据、HTML5等。系统在编译时不会编译该目录下的资源,因此不会像res目录样能被直接通过R.xxx.xxx进行访问,而是需要通过AssetManager以二进制流的形式来读取资源。注意:res/raw目录存储的也是原生的资源文件,但是它能够被Android映射成R.xxx.xxx资源,它们的不同之处在于assets目录下的文件结构支持树形结构目录,而res/raw不支持。另外,assets目录下的单个文件尽量在1MB以下,而res/raw目录下的单个文件可以任意MB。
// 访问assets目录下的资源
AssetManager manager = this.getAssets();
InputStream in = manager.open("image/logo.jpg");//注意路径
Bitmap bitmap = BitmapFactory.decodeStream(in);
// 访问res/raw目录下的资源
InputStream is = this.getResources().openRawResource(R.raw.logo);
Bitmap image = BitmapFactory.decodeStream(is);
lib目录
:存放应用程序依赖的不同架构(ABI)的.so文件。res目录
:存放应用的资源文件,包括图片、字符串、颜色、尺寸、动画文件、布局文件等资源。这个目录下的所有资源都会出现在资源清单文件R.java的索引中。MERA-INF目录
:保存应用程序的签名信息,签名信息可以验证APK文件的完整性。MANIFEST.MF
:该文件保存了APK包中每个文件得名字及其SHA1哈希值;CERT.SF
:该文件保存了MANIFEST.MF文件的哈希值及其文件中每一个哈希项的哈希值;CERT.RSA
:该文件保存了APK包的签名和证书的公钥信息;
AndroidManifest.xml
:Android 应用的配置文件,用于描述 Android 应用的整体情况。每个 Android 应用必须包含一个 AndroidManifest.xml 文件。AndroidManifest.xml 包含了 Android 四大组件的注册信息,权限声明以及 Android SDK 版本信息等,该文件将会被编译成二进制文件。classes.dex
:应用程序可执行文件,即Dalvik字节码文件。APK中可能包含多个dex文件,具体要看Android程序的所有方法数是否超过65535,如果超过就进行分包处理,就会出现多个dex文件的情况。Resources.arsc
:资源配置文件。该文件用于记录资源文件位置和资源ID之间的映射关系,以便系统能够根据ID去寻找对应的资源路径。该ID实际上与R.java中存储的是一样的,但是R.java只是便于开发者调用资源且保证编译程序不报错,而实际上在程序运行时系统需要根据这个ID从Resources.arsc文件保存的对应的路径中获取资源文件。
1.2 APK打包过程
一个Android项目主要由Java代码
、AIDL文件
、AndroidManifest.xml
、第三方库.class文件
以及res目录资源
文件(如图片、字符串、尺寸、布局…)等模块组成,因此对于APK的打包过程,主要就是对这些内容进行编译打包。下图是Google官方提供的详细的APK构建过程:
从上图可知,APK的构建过程主要经历七个阶段,其中浅色方框表示每个阶段的输入或输出内容,绿色椭圆框表示所用到的工具,这些工具大部分位于Android SDK目录的build-tools\版本号
目录下。下面我们对上述七个阶段作详细的解释:
- (1)
aapt.exe
,打包资源
该阶段的目的是打包res目录资源文件
和AndroidManifest.xml
,使用的工具是aapt.exe
,最终生成的中间文件为R.java类
、二进制的resource.arsc
、res文件夹
(包括二进制的xml、没被改变的图片和res/raw文件等)、二进制的AndroidManifest.xml文件
、没有改变的assets文件夹
。另外,aapt
在打包资源文件之前会检测AndroidManifest.xml、res目录下资源文件名的合法性,如果这两部分出现不符合要求情况话,Android Studio会直接报错无法编译。因此,如果我们遇到编译错误带有aapt信息时,基本上可以确定问题为AndroidManifest.xml配置错误,或者res目录下资源命名不合法或不存在。
- (2)
aidl.exe
,将.aidl文件转换为.java文件
该阶段的目的是将Android工程src/main/aidl目录下的所有.aidl
文件转换为.java
文件,生成的.java
文件位于build/generated/source/aidl目录下,使用的工具是aidl.exe
。在从Android6.0源码的角度剖析Binder工作原理一文中,我们曾介绍到AIDL
(全称Android Interface Definition Language,Android接口定义语言),是Android系统为了便于开发具备跨进程通信的应用,专门提供的且用于自动生成Java层Binder通信框架的技术。因此,转换得到的Java代码将用于进程间通信的C/S端。
- (3)
javac.exe
,编译Java源文件为字节码文件
该阶段的目的是将所有模块src/main/java目录下的java源文件、aapt生成的R.java文件、aidl生成的.java
文件以及BuildConfig.java文件编译成对应的.class字节码文件,使用的工具是jdk/bin/javac.exe。其中,BuildConfig.java文件是根据build.gradle配置自动生成的,我们可以使用它的DEBUG字段来控制日志的输出。BuildConfig.java源码如下:
//..\app\build\generated\source\buildConfig\...\BuildConfig.java
public final class BuildConfig {
// 该在开发中可控制日志输出
// 不需要自己手动添加Debug开发,系统会依据BUILD_TYPE自动设定
// BUILD_TYPE="release"时,DEBUG=false
public static final boolean DEBUG = Boolean.parseBoolean("true");
// 包名
public static final String APPLICATION_ID = "com.jiangdg.jjusbcamera";
// 当前编译类型,debug或release
public static final String BUILD_TYPE = "debug";
// 产品(渠道包的名称)
public static final String FLAVOR = "";
// 版本号
public static final int VERSION_CODE = 2;
// 版本名称
public static final String VERSION_NAME = "1.0.1.20191104";
}
- (4)
dx.bat
,将所有.class文件转换为.dex文件
该阶段的目的是将所有的.class字节码
文件(包括第三方库)转换为一个.dex字节码
文件,使用的工具是dx.bat。从Android性能优化(2)一文中可知,由于.class
文件存在较大的冗余、各个类相互独立且结构也不紧凑,不适合内存和处理器速度有限的系统,因此Android系统选用专门的虚拟机Dalvik或ART来执行专门的字节码文件,这类字节码文件即为.dex
文件,它是在.class
字节码文件的基础上经过DEX工具
压缩、优化后得到的,适用于内存和处理器速度有限的系统。.class
文件和.dex
文件结构对比图:
- (5)
apkbuilder
,打包生成APK
该阶段目的是将前几步生成的.dex文件、resources.arsc文件、被编译后的res目录(res/raw目录下的资源没有被编译)和AndroidManifest.xml以及其他资源文件(assets文件夹)打包生成一个.apk
文件,使用的工具是sdkpath/tools/sdklib-xxx.jar
。
- (6)
apksigner.bat
,对APK文件签名
该阶段是对打包生成的.apk
文件进行签名,得到的是被签名后的.apk
文件,使用的工具是apksigner.bat。签名是一个apk身份的证明,Android系统在安装apk的时候,首先会检验apk的签名,如果发现签名文件不存在或者校验签名失败,就会拒绝安装。对一个apk文件签名后,apk文件根目录下回增加META-INF目录,该目录下有三个文件:CERT.RSA、CERT.SF和MANIFEST.MF。
- (7)
zipalign.exe
,对齐优化
该阶段的目的是对签名后的.apk
文件进行对齐处理,得到的是对齐优化后的.apk
文件,使用的工具是zipalign.exe。需要注意的是,对齐优化存在于在release打包时,Zipalign是一个android平台上整理APK文件的工具,它对apk中未压缩的数据进行4字节对齐,对齐后就可以使用mmap函数读取文件,可以像读取内存一样对普通文件进行操作。如果没有4字节对齐,就必须显式的读取,这样比较缓慢并且会耗费额外的内存。
2. APK安装过程
在Android设备上安装一个APK,是从执行以下一段程序开始的,该段程序采用隐式Intent的方式,通过指定intent的type属性为"application/vnd.android.package-archive"
来启动Android系统中一个名为PackageInstaller
的系统应用,PackageInstaller
是系统内置的应用程序,用于安装和卸载应用。
// 启动PackageInstaller系统应用
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(Uri.fromFile(new File(apkPath)),
"application/vnd.android.package-archive");
context.startActivity(intent);
// Android8.0\packages\apps\PackageInstaller\AndroidManifest.xml
...
<activity android:name=".InstallStart"
android:exported="true"
android:excludeFromRecents="true">
<intent-filter android:priority="1">
<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:scheme="content" />
<data android:mimeType="application/vnd.android.package-archive" />
</intent-filter>
<intent-filter android:priority="1">
<action android:name="android.intent.action.INSTALL_PACKAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" />
<data android:scheme="package" />
<data android:scheme="content" />
</intent-filter>
<intent-filter android:priority="1">
<action android:name="android.content.pm.action.CONFIRM_PERMISSIONS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
...
从PackageInstaller
的AndroidManifest.xml可知,在PackageInstaller
中能够隐式匹配application/vnd.android.package-archive
的Activity为InstallStart
,也就是说InstallStart
是PackageInstaller
应用的入口。InstallStart
并没有显示任何界面,而是直接启动了一个名为InstallStaging
的Activity,它首先会将content协议的Uri转换为File协议,然后再调起PackageInstallerActivity
进入安装流程。因为对于Android7.0以上系统来说,会使用FileProvider来处理URI ,FileProvider会隐藏共享文件的真实路径,将路径转换成content://Uri路径。PackageInstallerActivity
就是我们安装APK时弹出来的询问是否安装对话框,当点击安装或"OK"时,它会启动名为InstallInstalling
的Activity,该Activity就是我们看到的安装进度对话框。InstallInstalling
主要用于向包管理器发送安装包的信息并处理包管理的回调,即通过IO流的形式将APK的信息写入到PackageInstaller.Session中,PackageInstaller.Session可理解为PackageInstallerSession代理对象,真正的实现位于PackageInstallerSession对象中,也就是说,这个发送APK信息的过程实质上是一个基于Binder机制的跨进程通信。至此,APK安装执行流程进入进入Framework层,PackageInstaller进程的初始化工作就执行完毕了。在PackageInstallerSession中,它会最终将APK信息交给PackageManagerService(PMS)处理,PMS通过向PackageHandler发送消息来驱动APK的拷贝和安装工作,其中APK的拷贝由DefaultContainerService
完成,而安装由PackageManagerService
完成。PackageInstaller进程初始化过程时序图如下:
这里总结下PackageInstaller进程初始化工作有哪些:
- 向用户提供APK安装的交互界面,比如安装确认、安装进度;
- 将content协议的Uri转换为File协议,以获取APK存储的真实路径;
- 将APK文件以IO流的形式提交给PMS,进入下一步处理;
2.1 拷贝APK
APK拷贝过程时序图如下:
从上述时序图可知,APK的拷贝过程是从PMS的installStage方法
开始的,该方法会向PackageHandler发送一个INIT_COPY
消息并传输一个InstallParams对象(这个对象后面有用
),PackageHandler是PMS的一个内部类,专门用于处理PMS中发送过来的消息,而接收处理消息的方法PackageHandler的handleMessage
,该方法会继续调用doHandleMessage
方法实现具体的处理操作。PackageHandler接收并处理INIT_COPY消息源码如下:
// PackageManagerService.installStage
void installStage(String packageName, File stagedDir, String stagedCid,
IPackageInstallObserver2 observer, PackageInstaller.SessionParams
sessionParams,String installerPackageName, int installerUid, UserHandle user,
Certificate[][] certificates) {
...
final Message msg = mHandler.obtainMessage(INIT_COPY);
final int installReason = fixUpInstallReason(installerPackageName, installerUid,
sessionParams.installReason);
// 创建InstallParams
final InstallParams params = new InstallParams(..);
msg.obj = params;
// 发送拷贝消息INIT_COPY
mHandler.sendMessage(msg);
}
// PackageManagerService.PackageHandler内部类
class PackageHandler extends Handler {
...
private boolean connectToService() {
if (DEBUG_SD_INSTALL) Log.i(TAG, "Trying to bind to" +
" DefaultContainerService");
// 绑定启动com.android.defcontainer.DefaultContainerService服务
// 并注册连接事件监听器mDefContainerConn
// 该服务的目的是拷贝APK
Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
if (mContext.bindServiceAsUser(service, mDefContainerConn, // 注释3
Context.BIND_AUTO_CREATE, UserHandle.SYSTEM)) {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
mBound = true;
return true;
}
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
return false;
}
...
// 处理Handler消息
public void handleMessage(Message msg) {
try {
doHandleMessage(msg);
} finally {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
}
}
void doHandleMessage(Message msg) {
switch (msg.what) {
// INIT_COPY=5
// 处理拷贝消息
case INIT_COPY: {
HandlerParams params = (HandlerParams) msg.obj;
int idx = mPendingInstalls.size();
// mBound服务绑定状态,默认为false
if (!mBound) { // 注释1
// 连接到服务
if (!connectToService()) { // 注释2
...
}
}
}
...
}
}
在PackageHandler的doHandleMessage方法中,当INIT_COPY消息到来时,会直接进入INIT_COPY消息处理流程。首先,注释1
处会判断标志位mBound的状态,这个标志位代表绑定到DefaultContainerService服务的状态,默认值为false表示未绑定。如果绑定成功,mBound会被置true,这里我们只考虑未绑定的情况。然后,注释2
处为当mBound为false时,会去PackageHandler的connectToService方法,该方法会去绑定启动DefaultContainerService服务并将mBound置true,如注释3
处。由于APK拷贝是一个比较耗时的操作,因此这个DefaultContainerService运行在一个单独的进程之中,将由它来完成APK的检查与拷贝工作。另外,PMS为了监听绑定DefaultContainerService服务的启动状态,在调用bindServiceAsUser时注册了一个DefaultContainerConnection对象,该对象继承于ServiceConnection,当绑定服务成功后,其onServiceConnected方法会被调用。DefaultContainerConnection源码如下:
// PackageManagerService.DefaultContainerConnection
class DefaultContainerConnection implements ServiceConnection {
// 与DefaultContainerService建立连接后被调用
// 向mHandler发送MCS_BOUND消息
public void onServiceConnected(ComponentName name, IBinder service) {
if (DEBUG_SD_INSTALL) Log.i(TAG, "onServiceConnected");
// DefaultContainerService的Binder代理对象
final IMediaContainerService imcs = IMediaContainerService.Stub
.asInterface(Binder.allowBlocking(service));
mHandler.sendMessage(mHandler.obtainMessage(MCS_BOUND, imcs));
}
public void onServiceDisconnected(ComponentName name) {
if (DEBUG_SD_INSTALL) Log.i(TAG, "onServiceDisconnected");
}
}
当PMS绑定启动DefaultContainerService服务后,DefaultContainerConnection连接事件监听器的onServiceConnected方法会被回调,这就意味着SystemManager进程(PMS服务由此进程启动
)和DefaultContainerService进程之间基于Binder机制的跨进程通信链路建立完毕。在onServiceConnected方法中,它首先获取IMediaContainerService.Stub这个实体Binder代理对象imcs,然后再向PackageHandler发送一个MCS_BOUND
消息。接下来,我们看PackageHandler接收到MCS_BOUND
消息后,做了些什么。
// PackageManagerService.PackageHandler.doHandleMessage
void doHandleMessage(Message msg) {
switch (msg.what) {
case MCS_BOUND: {
// 取出IMediaContainerService.Stub的Binder代理对象
if (msg.obj != null) {
mContainerService = (IMediaContainerService) msg.obj;
}
// mContainerService不为null
// 发起远程拷贝
if (mContainerService == null) {
...
} else if (mPendingInstalls.size() > 0) {
HandlerParams params = mPendingInstalls.get(0);
if (params != null) {
// 调用HandlerParams的startCopy方法
if (params.startCopy()) {
...
}
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
break;
}
}
}
// PackageManagerService.HandlerParams.startCopy
final boolean startCopy() {
// InstallParams继承于HandlerParams
// 调用InstallParams的handleStartCopy
handleStartCopy();
// 调用InstallParams的handleReturnCode,开始解析APK
handleReturnCode();
return res;
}
从上述源码可知,当PackageHandler接收到MCS_BOUND
消息后,会首先缓存IMediaContainerService.Stub实体Binder代理对象mContainerService,然后调用HandlerParams的startCopy方法,该方法主要执行两步操作,一是调用InstallParams的handleStartCopy,二是调用InstallParams的handleReturnCode继续拷贝完毕后的下一步工作,即解析APK。这里为什么是InstallParams?在前面我们提到过这个类,是之前传进来的且继承于HandlerParams。handleStartCopy等部分源码如下:
// InstallParams.handleStartCopy
public void handleStartCopy() throws RemoteException {
...
final InstallArgs args = createInstallArgs(this);
mArgs = args;
...
ret = args.copyApk(mContainerService, true);
}
// PackageManagerService.createInstallArgs
private InstallArgs createInstallArgs(InstallParams params) {
if (params.move != null) {
return new MoveInstallArgs(params);
} else if (installOnExternalAsec(params.installFlags) || params.isForwardLocked()) {
// 安装到SDCard
return new AsecInstallArgs(params);
} else {
// 安装到data分区(内部存储器)
return new FileInstallArgs(params);
}
}
// PackageManagerService.FileInstallArgs.copyAPK
int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyApk");
try {
return doCopyApk(imcs, temp);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
// PackageManagerService.FileInstallArgs.doCopyAPK
private int doCopyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
try {
final boolean isEphemeral = (installFlags &
PackageManager.INSTALL_INSTANT_APP) != 0;
//创建临时文件存储目录
// /data/app/vmdl1223.tmp
final File tempDir =
mInstallerService.allocateStageDirLegacy(volumeUuid, isEphemeral);
codeFile = tempDir;
resourceFile = tempDir;
} catch (IOException e) {
Slog.w(TAG, "Failed to create copy file: " + e);
return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
}
...
// 调用远程DefaultContainerService实体Binder的copyPackage
// 执行真正的拷贝操作
ret = imcs.copyPackage(origin.file.getAbsolutePath(), target);
if (ret != PackageManager.INSTALL_SUCCEEDED) {
Slog.e(TAG, "Failed to copy package");
return ret;
}
...
}
首先,InstallParams.handleStartCopy会获取一个InstallArgs对象,这个InstallArgs是一个抽象类,通过查看PMS的createInstallArgs方法得值,它有三个子类,即MoveInstallArgs、AsecInstallArgs、FileInstallArgs,其中,FileInstallArgs用于处理安装到非ASEC的存储空间的APK,也就是内部存储空间(Data分区),AsecInstallArgs用于处理安装到ASEC中(mnt/asec)即SD卡中的APK。由于现在大部分手机都直接将APK安装到内部存储器中,这里我们以分析FileInstallArgs为例。然后,InstallParams.handleStartCopy接着会调用FileInstallArgs的copyApk方法,该方法继续调用doCopyApk方法并在这个方法中完成临时文件存储目录的创建,和调用远程Binder(DefaultContainerService.mBinder
)的copyPackage方法,最终将拷贝任务交给远程DefaultContainerService服务进程处理。DefaultContainerService部分源码如下:
// DefaultContainerService.mBinder
private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() {
...
@Override
public int copyPackage(String packagePath, IParcelFileDescriptorFactory target) {
if (packagePath == null || target == null) {
return PackageManager.INSTALL_FAILED_INVALID_URI;
}
PackageLite pkg = null;
try {
final File packageFile = new File(packagePath);
// 轻度解析APK
// 首先,判断是单个APK还是APK目录;
// 然后,将一个APkLite(轻量级包信息)或多个ApkLite封装为一个PackageLite返回
pkg = PackageParser.parsePackageLite(packageFile, 0);
// 拷贝APK到指定目录
// /data/app/com.example/base.apk
// /data/app/com.example/split_foo.apk
return copyPackageInner(pkg, target);
} catch (PackageParserException | IOException | RemoteException e) {
Slog.w(TAG, "Failed to copy package at " + packagePath + ": " + e);
return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
}
}
}
最终,在DefaultContainerService服务中完成以下两个任务:
1)轻度解析APK,得到一个轻量级包信息PackageLite,为后续解析APK做准备工作。
2)拷贝APK到/data/app目录下。这里需要注意的是,为了解决65536上线以及APK安装包越来越大的问题,Android 5.0引入了Split APK机制,该机制允许将一个APK拆分成多个独立的APK。因此,拷贝APK需要分两种类型,即安装的是一个完整的APK,即base APK,Android称其为Monolithic;安装文件在一个文件目录中,这些APK由一个base APK和一个或多个split APK组成,Android称其为Cluster。比如:
/data/app/com.example/base.apk
/data/app/com.example/split_foo.apk
Android不同的目录存放不同类型的应用,如下所示:
/system/framwork:保存的是资源型的应用程序,它们用来打包资源文件。
/system/app:保存系统自带的应用程序。
/data/app:保存用户安装的应用程序。
/data/app-private:保存受DRM保护的私有应用程序。
/vendor/app:保存设备厂商提供的应用程序。
2.2 解析APK
APK解析过程时序图如下:
在1.3.1中我们谈到,当APK拷贝操作完成后会调用PackageManagerService.InstallParams的handleReturnCode方法进入APK解析流程。handleReturnCode方法很简单,只是调用了PMS的processPendingInstall方法,从该方法源码可知,由于APK安装解析是一个耗时的操作,因此它通过异步任务的形式将首先在安装前删除一些残留文件,然后开始解析APK,再然后删除安装后残留文件,最后向PackageHandler发送一个POST_INSTALL
消息。PackageHandler接收到POST_INSTALL
会发送一个removed广播,并请求运行授权。PMS.processPendingInstall方法部分源码如下:
private void processPendingInstall(final InstallArgs args, final int currentStatus) {
...
mHandler.post(new Runnable() {
public void run() {
if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
// 1. 安装前操作,删除安装残留文件
args.doPreInstall(res.returnCode);
// 2 .对APK进行解析
synchronized (mInstallLock) {
installPackageTracedLI(args, res);
}
// 3. 删除安装后的残余文件
args.doPostInstall(res.returnCode, res.uid);
}
...
// 4. 发送信息POST_INSTALL
Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);
mHandler.sendMessage(msg);
}
}
}
接下来,我们通过PMS的installPackageTracedLI详细分析APK的解析安装过程。在PMS的installPackageTracedLI方法中,它的实现很简单,只是调用了PMS的installPackageLI方法,该方法很长很长,但主要做了如下工作:
- 解析AndroidManifest.xml文件得到应用信息、各组件信息和权限等,创建PackageParser.Package对象;
- 检查APK是否已经安装,并进行签名校验;
- 对dex文件进行优化,需要注意的是,不同的虚拟机操作会有所不同,如果是Davlik虚拟机,那么dex文件将被优化生成odex.dex文件,保存在/data/dalvik-cache目录下;如果是ART虚拟机,将生成oat文件;
- 将复制的APK文件目录,重命名以包名为目录的文件;
- 替换APK或者安装新的APK;
PMS.installPackageTracedLI源码如下:
private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {
...
// 1. 解析package信息,主要是AndroidManifest.xml
// 最终调用的是PackageParser.ParseBaseApkCommon方法
final PackageParser.Package pkg;
try {
pkg = pp.parsePackage(tmpPackageFile, parseFlags);
} catch (PackageParserException e) {
res.setError("Failed parse during installPackageLI", e);
return;
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
// 2. 签名校验
try {
verifySignaturesLP(signatureCheckPs, pkg);
} catch (PackageManagerException e) {
res.setError(e.error, e.getMessage());
return;
}
// 3. 对dex进行优化
mPackageDexOptimizer.performDexOpt(pkg, pkg.usesLibraryFiles,
null , false ,
getCompilerFilterForReason(REASON_INSTALL),
getOrCreateCompilerPackageStats(pkg),
mDexManager.isUsedByOtherApps(pkg.packageName));
// 4. 将/data/app/vmdl18300388.tmp/base.apk,重命名为/data/app/包名-1/base.apk。
if (!args.doRename(res.returnCode, pkg, oldCodePath)) {
res.setError(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed rename");
return;
}
// 5. 替换或新安装APK
if (replace) {
replacePackageLIF(pkg, parseFlags, scanFlags, args.user,
installerPackageName, res, args.installReason);
} else {
installNewPackageLIF(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES,
args.user, installerPackageName, volumeUuid, res, args.installReason);
}
}
在PMS.installNewPackageLIF方法中,主要做了如下工作:
- 调用PMS.scanPackageTracedLI方法重新扫描apk文件,该方法继续调用PMS的scanPackageLI方法,而scanPackageLI又调用了PMS的scanPackageDityLI,由它来完成向PSM注册四大组件信息等操作;
- 更新PackageSettings,该类记录了APK配置的动态信息;
- 安装完成后,为APP准备数据;
PMS.installNewPackageLIF方法部分源码如下:
private void installNewPackageLIF(PackageParser.Package pkg, final int policyFlags,
int scanFlags, UserHandle user, String
installerPackageName, String volumeUuid,
PackageInstalledInfo res, int installReason) {
String pkgName = pkg.packageName;
...
// 1. 调用scanPackageTracedLI扫描APK
PackageParser.Package newPackage = scanPackageTracedLI(pkg,policyFlags,
scanFlags,System.currentTimeMillis(), user);
// 2. 更新PackageSettings
updateSettingsLI(newPackage, installerPackageName, null,
res, user, installReason);
// 3.执行到这里说明APP已经安装完毕
// 准备APP数据
if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
prepareAppDataAfterInstallLIF(newPackage);
}
...
}
APK安装大致流程图: