原创内容,转载请注明出处,多谢配合。
一、APK组成
在APK的安装流程,在此之前先简单了解下APK组成:
目录/文件 | 描述 |
---|---|
assert | 存放的原生资源文件,通过AssetManager类访问。 |
lib | 存放库文件。 |
META-INF | 保存应用的签名信息,签名信息可以验证APK文件的完整性。 |
res | 存放资源文件。res中除了raw子目录,其他的子目录都参与编译,这些子目录下的资源是通过编译出的R类在代码中访问。 |
AndroidManifest.xml | 用来声明应用程序的包名称、版本、组件和权限等数据。 apk中的AndroidManifest.xml经过压缩,可以通过AXMLPrinter2工具解开。 |
classes.dex | Java源码编译后生成的Java字节码文件。 |
resources.arsc | 编译后的二进制资源文件。 |
二、Apk安装方法
APK的安装场景主要有以下几种:
- 通过adb命令安装:adb 命令包括adb push/install
- 用户下载的Apk,通过系统安装器packageinstaller安装该Apk。packageinstaller是系统内置的应用程序,用于安装和卸载应用程序。
- 系统开机时安装系统应用。
- 电脑或者手机上的应用商店自动安装。
这里主要分析下PackageInstaller安装Apk流程:
按如上流程,挑比较重要的流程分析下:
packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallAppProgress.java
private void doPackageStage(PackageManager pm, PackageInstaller.SessionParams params) {
final PackageInstaller packageInstaller = pm.getPackageInstaller();
PackageInstaller.Session session = null;
try {
final String packageLocation = mPackageURI.getPath();
final File file = new File(packageLocation);
final int sessionId = packageInstaller.createSession(params);
final byte[] buffer = new byte[65536];
session = packageInstaller.openSession(sessionId);
final InputStream in = new FileInputStream(file);
final long sizeBytes = file.length();
final OutputStream out = session.openWrite("PackageInstaller", 0, sizeBytes);
try {
int c;
while ((c = in.read(buffer)) != -1) {
out.write(buffer, 0, c);
if (sizeBytes > 0) {
final float fraction = ((float) c / (float) sizeBytes);
session.addProgress(fraction);
}
}
session.fsync(out);
} finally {
IoUtils.closeQuietly(in);
IoUtils.closeQuietly(out);
}
// Create a PendingIntent and use it to generate the IntentSender
Intent broadcastIntent = new Intent(BROADCAST_ACTION + mInstallId);
PendingIntent pendingIntent = PendingIntent.getBroadcast(
InstallAppProgress.this /*context*/,
sessionId,
broadcastIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
session.commit(pendingIntent.getIntentSender());
...
}
将APK的信息通过IO流的形式写入到PackageInstaller.Session中。调用PackageInstaller.Session的commit方法,将APK的信息交由PMS处理。
到了PMS这边有如下的关键几步:
1) PackageHandler处理安装消息
PMS中doHandleMessage方法用于处理各个类型的消息:
void doHandleMessage(Message msg) {
switch (msg.what) {
case INIT_COPY: {
// 将新的请求加入到mPendingIntalls中,等待MCS_BOUND阶段处理
break;
}
case MCS_BOUND: {
//如果mPendingInstalls不为空,调用InstallParams.startCopy函数处理安装请求
满足条件触发startCopy()
break;
}
}
}
这里会起一个新的进程DefaultContainerService用于检查和复制可移动文件的服务。它运行在com.android.defcontainer进程,通过IMediaContainerService和PMS进行IPC通信。
final boolean startCopy() {
boolean res;
try {
if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ": " + this);
//尝试安装4次,超过次数就放弃这个安装请求
if (++mRetries > MAX_RETRIES) {
Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
mHandler.sendEmptyMessage(MCS_GIVE_UP);
handleServiceError();
return false;
} else {
handleStartCopy(); //复制APK
res = true;
}
} catch (RemoteException e) {
if (DEBUG_INSTALL) Slog.i(TAG, "Posting install MCS_RECONNECT");
mHandler.sendEmptyMessage(MCS_RECONNECT);
res = false;
}
handleReturnCode(); //安装APK
return res;
}
2) 复制APK
handleStartCopy()主要功能是获取安装位置信息以及复制apk到指定位置。抽象类InstallArgs中的copyApk负责复制APK文件,具体实现在子类FileInstallArgs和SdInstallArgs里面。最终通过IMediaContainerService跨进程调用DefaultContainerService的copyPackage方法,在DefaultContainerService所在的进程中将APK复制到临时存储目录,比如/data/app/vmdl18300388.tmp/base.apk。
3) 安装APK
private void processPendingInstall(final InstallArgs args, final int currentStatus) {
// Queue up an async operation since the package installation may take a little while.
mHandler.post(new Runnable() {
public void run() {
mHandler.removeCallbacks(this);
// Result object to be returned
PackageInstalledInfo res = new PackageInstalledInfo();
res.returnCode = currentStatus;
res.uid = -1;
res.pkg = null;
res.removedInfo = new PackageRemovedInfo();
if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
//预安装
args.doPreInstall(res.returnCode);
synchronized (mInstallLock) {
//安装
installPackageLI(args, res);
}
//结束安装
args.doPostInstall(res.returnCode, res.uid);
}
if (!doRestore) {
//如果完成安装的msg,package add的广播将在此处发送
Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);
mHandler.sendMessage(msg);
}
}
});
}
安装的核心方法是:installPackageLI
private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {
PackageParser pp = new PackageParser();
...
// 解析我们的package
pkg = pp.parsePackage(tmpPackageFile, parseFlags);
…
//
mPackageDexOptimizer.performDexOpt(pkg, pkg.usesLibraryFiles,
null /* instructionSets */, false /* checkProfiles */,
getCompilerFilterForReason(REASON_INSTALL),
getOrCreateCompilerPackageStats(pkg),
mDexManager.isUsedByOtherApps(pkg.packageName));
...
//安装package
if (replace) {
// 更新已经存在的packages
replacePackageLIF(pkg, parseFlags, scanFlags | SCAN_REPLACING, args.user,
installerPackageName, res);
} else {
// 安装新的packages
installNewPackageLIF(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES,
args.user, installerPackageName, volumeUuid, res);
}
}
...
}
先看PackageParser的parsePackage:
public Package parsePackage(File packageFile, int flags, boolean useCaches)
throws PackageParserException {
...
if (packageFile.isDirectory()) {
//多apk,内部会通过parseApkLite方法解析每个Mutiple APK,得到每个Mutiple APK对应的ApkLite轻量级APK信息
parsed = parseClusterPackage(packageFile, flags);
} else {
//单apk
parsed = parseMonolithicPackage(packageFile, flags);
}
...
return parsed;
}
注:
Android5.0引入了Split APK机制,这是为了解决65536上限以及APK安装包越来越大等问题。Split APK机制可以将一个APK,拆分成多个独立APK。
在引入了Split APK机制后,APK有两种分类:
- Single APK:安装文件为一个完整的APK,即base APK。Android称其为Monolithic。
- Mutiple APK:安装文件在一个文件目录中,其内部有多个被拆分的APK,这些APK由一个 base APK和一个或多个split APK组成。Android称其为Cluster。
之后调用parseBaseApk 以及 parseBaseApkCommon,主要的xml解析在parseBaseApkCommon:主要用来解>析APK的AndroidManifest中的各个标签,比如application、permission、uses-sdk、feature-group等等。
总结下installPackageLI过程:
- PackageParser$parsePackage,主要是解析APK的AndroidManifest.xml,将每个标签对应的信息添加到Package的相关列表中,如将下的信息添加到Package的activities列表等。
- 加载apk证书,获取签名信息。
- 检查目前安装的APK是否在系统中已存在:
- 已存在,则调用replacePackageLIF进行替换安装。
- 不存在,否则调用installNewPackageLIF进行安装。
后面的replacePackageLIF和installNewPackageLIF最终都会走scanPackageTracedLI。
参考:
http://liuwangshu.cn/tags/Android%E5%8C%85%E7%AE%A1%E7%90%86%E6%9C%BA%E5%88%B6/
https://maoao530.github.io/2017/01/18/package-install/