前言
要研究APK的安装过程,还是从我们平时的使用开始说起。一般来说我们安装APK有3种方式:
- 下载APK到手机,点击APK文件安装
- 在手机自带的应用市场安装
- 使用adb命令安装
其实这几种方式大体的流程都是一样的,区别仅仅在前期对APK的一些检查项和UI上的区别。我们这里研究一下手动点击APK的安装流程即可,因为它的流程相对完整,更符合一般用户的使用习惯。
1.安装的准备工作
①
在Android中,通过发送Intent就可以启动应用的安装过程,比如:
Uri uri = Uri.fromFile(new File(apkFilePath));
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(uri,"application/vnd.android.package-archive");
startActivity(intent);
在Android的系统应用PackageInstaller中有一个InstallStart.java会响应这个Intent:
<activity android:name=".InstallStart"
android:theme="@style/Installer"
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>
......
</activity>
②
紧接着在InstallStart的onCreate方法中会进行各种Uri的判断,最终会跳转到一个叫做PackageInstallerActivity的界面。
Intent nextActivity = new Intent(intent);
nextActivity.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
// The the installation source as the nextActivity thinks this activity is the source, hence
// set the originating UID and sourceInfo explicitly
nextActivity.putExtra(PackageInstallerActivity.EXTRA_CALLING_PACKAGE, callingPackage);
nextActivity.putExtra(PackageInstallerActivity.EXTRA_ORIGINAL_SOURCE_INFO, sourceInfo);
nextActivity.putExtra(Intent.EXTRA_ORIGINATING_UID, originatingUid);
if (isSessionInstall) {
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
...
}
if (nextActivity != null) {
startActivity(nextActivity);
}
对于PackageInstallerActivity界面,主要做以下几件事:
-
解析Uri协议进行解析,包括file和package两种
-
对未知来源进行处理,并进行相应的UI提示
-
提取并在界面展示应用对应的权限
如果一切顺利,点击确认弹框之后,会执行startInstall()方法,如下:private void startInstall() { // Start subactivity to actually install the application Intent newIntent = new Intent(); ...... newIntent.setData(mPackageURI); newIntent.setClass(this, InstallInstalling.class); ...... startActivity(newIntent); finish(); }
③
上面的逻辑是跳转到InstallInstalling界面进行安装,我们看看这个Activity:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
......
if ("package".equals(mPackageURI.getScheme())) {
// 直接安装 package
try {
getPackageManager().installExistingPackage(appInfo.packageName);
launchSuccess();
} catch (PackageManager.NameNotFoundException e) {
launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
}
} else {
// 对于 file 安装,按照session的逻辑去走
// 这部分在onResume中开始
final File sourceFile = new File(mPackageURI.getPath());
......
if (savedInstanceState != null) {
mSessionId = savedInstanceState.getInt(SESSION_ID);
mInstallId = savedInstanceState.getInt(INSTALL_ID);
......
} else {
......
mSessionId = getPackageManager().getPackageInstaller().createSession(params);
......
// 创建应用安装状态的Observer
// 真正注册是在后面 Session 的 commitLocked 中
// InstallEventReceiver 这里是做了二次封装,方便进行持久化操作
InstallEventReceiver.addObserver(this, mInstallId,
this::launchFinishBasedOnResult);
}
......
}
}
@Override
protected void onResume() {
super.onResume();
// This is the first onResume in a single life of the activity
if (mInstallingTask == null) {
PackageInstaller installer = getPackageManager().getPackageInstaller();
PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId);
// 启动异步安装任务 InstallingAsyncTask 进行一系列的session操作
if (sessionInfo != null && !sessionInfo.isActive()) {
mInstallingTask = new InstallingAsyncTask();
mInstallingTask.execute();
}
......
}
}
整体过程如下:
- onCreate 中通过 PackageInstaller 创建Session 并返回 mSessionId
- onResume 中开启InstallingAsyncTask,把包信息写入mSessionId对应的session,然后提交。
- onCreate 中添加了 安装结果的监听,在收到安装结果的广播后 会调用此 跳转到对应结果页面。
其中核心步骤就是InstallingAsyncTask任务中的Session系列操作:一个应用完整的安装过程从这里开始
private final class InstallingAsyncTask extends AsyncTask<Void, Void,
PackageInstaller.Session> {
volatile boolean isDone;
@Override
protected PackageInstaller.Session doInBackground(Void... params) {
PackageInstaller.Session session;
......
session = getPackageManager().getPackageInstaller().openSession(mSessionId);
try {
File file = new File(mPackageURI.getPath());
try (InputStream in = new FileInputStream(file)) {
long sizeBytes = file.length();
try (OutputStream out = session.openWrite("PackageInstaller", 0, sizeBytes)) {
byte[] buffer = new byte[1024 * 1024];
while (true) {
......
out.write(buffer, 0, numRead);
......
}
}
}
return session;
}
......
}
@Override
protected void onPostExecute(PackageInstaller.Session session) {
if (session != null) {
......
session.commit(pendingIntent.getIntentSender());
......
} else {
getPackageManager().getPackageInstaller().abandonSession(mSessionId);
......
}
}
- 任务中做的第一个事情是:根据APK的Uri,将APK的信息通过IO流的形式写入到PackageInstaller.Session中
- 第二个事情就是PackageInstallerSession.commit()函数
session这个东西看来很重要,那么我们先来看看PackageInstallerSession。
Android通过PackageInstallerSession来表示一次安装过程,一个PackageInstallerSession包含一个系统中唯一的一个SessionId,如果一个应用的安装必须分几个阶段来完成,即使设备重启了,也可以通过这个ID来继续安装过程
PackageInstallerSession中保存了应用安装的相关数据,例如,安装包的路径、安装的进度、中间数据保存的目录等。
PackageInstallerService提供了接口createSession来创建一个Session对象:
@Override
public int createSession(SessionParams params, String installerPackageName, int userId) {
try {
return createSessionInternal(params, installerPackageName, userId);
} catch (IOException e) {
throw ExceptionUtils.wrap(e);
}
}
createSession()方法将返回一个系统唯一值作为Session ID。如果希望再次使用这个Session,可以通过接口openSession()打开它。openSession()方法的代码如下所示:
@Override
public IPackageInstallerSession openSession(int sessionId) {
try {
return openSessionInternal(sessionId);
} catch (IOException e) {
throw ExceptionUtils.wrap(e);
}
}
openSession()方法将返回一个IPackageInstallerSession对象,它是Binder服务PackageInstallerSession的IBinder对象。在PackageInstallerService中mSessions数组保存了所有PackageInstallerSession对象,定义如下:
@GuardedBy("mSessions")
private final SparseArray<PackageInstallerSession> mSessions = new SparseArray<>();
当PackageManagerService初始化时会创建PackageInstallerService服务,在这个服务的初始化函数中会读取/data/system目录下的install_sessions.xml文件,这个文件中保存了系统中未完成的Install Session。然后PackageInstallerService会根据文件的内容创建PackageInstallerSession对象并插入到mSessions中。
而对于上面提到的commit()函数,是真正触发PMS安装的函数,定义如下:
@Override
public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
......
mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
......
}
private final Handler.Callback mHandlerCallback = new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
......
case MSG_COMMIT:
synchronized (mLock) {
......
commitLocked();
......
}
break;
......
}
return true;
}
};
private void commitLocked()
throws PackageManagerException {
......
mRemoteObserver.onUserActionRequired(intent);
......
mPm.installStage(mPackageName, stageDir, localObserver, params,
mInstallerPackageName, mInstallerUid, user, mSigningDetails);
}
最后,安装过程走到了 PKMS 的 installStage()。
目前为止,只是通过 PackageInstaller 维持了安装 Session,把安装包写入到 Session中,真正的安装过程是 PMS 来执行。
2. 安装开始
2.1 复制安装文件
我们已经知道PackageInstallerSession通过调用PMS的installStage()方法开启了安装第一阶段,不过整个安装过程比较复杂,大致流程如图(高版本的系统源码没有DefaultContainerService这个类,拷贝操作均在FileInstallArgs中完成):
installStage()做了如下几件事情:
-
创建了一个InstallParams对象
- InstallParams是安装过程中的主要数据结构
- 一般情况,安装应用的时间通常比较长,因此Android把安装过程拆分,把调用过程的参数数据保存到InstallParams中
-
创建并发送了一个Message消息
- msg.what为INIT_COPY
- msg.obj为InstallParams对象
代码如下:
void installStage(ActiveInstallSession activeInstallSession) {
if (DEBUG_INSTANT) {
if ((activeInstallSession.getSessionParams().installFlags
& PackageManager.INSTALL_INSTANT_APP) != 0) {
Slog.d(TAG, "Ephemeral install of " + activeInstallSession.getPackageName());
}
}
final Message msg = mHandler.obtainMessage(INIT_COPY);
final InstallParams params = new InstallParams(activeInstallSession);
params.setTraceMethod("installStage").setTraceCookie(System.identityHashCode(params));
msg.obj = params;
Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "installStage",
System.identityHashCode(msg.obj));
Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
System.identityHashCode(msg.obj));
mHandler.sendMessage(msg);
}
经过一系列的调用,最终会调用到InstallParams的handleStartCopy(),这里面代码很长,这里主要做了如下几件事:
- 首先通过getMinimalPackageInfo()方法确认是否还有足够的安装空间,如果安装空间不够,会通过mInstaller.freeCache()来释放一部分
- 接下来通过createInstallArgs()创建InstallArgs对象,createInstallArgs()方法中会对InstallParams的move成员变量进行判断,决定后续调用FileInstallArgs还是MoveInstallArgs,我们这里的流程是调用FileInstallArgs。
- 接下来对apk进行校验,这个校验过程是通过发送Intent.ACTION_PACKAGE_NEEDS_VERIFICATION)广播给系统中所有可以接收该Intent的应用来完成的
接下来调用handleReturnCode,这里面会调用InstallArgs的copyApk(),其具体实现在FileInstallArgs里面。最终会调用到doCopyApk(),完成APK的拷贝操作,代码如下:
private int doCopyApk() {
if (origin.staged) {
if (DEBUG_INSTALL) Slog.d(TAG, origin.file + " already staged; skipping copy");
codeFile = origin.file;
resourceFile = origin.file;
return PackageManager.INSTALL_SUCCEEDED;
}
try {
final boolean isEphemeral = (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
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;
}
//这里拷贝APK文件到data/app下
int ret = PackageManagerServiceUtils.copyPackage(
origin.file.getAbsolutePath(), codeFile);
if (ret != PackageManager.INSTALL_SUCCEEDED) {
Slog.e(TAG, "Failed to copy package");
return ret;
}
final File libraryRoot = new File(codeFile, LIB_DIR_NAME);
NativeLibraryHelper.Handle handle = null;
try {
//这里APK里面的so库
handle = NativeLibraryHelper.Handle.create(codeFile);
ret = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,
abiOverride);
} catch (IOException e) {
Slog.e(TAG, "Copying native libraries failed", e);
ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
} finally {
IoUtils.closeQuietly(handle);
}
return ret;
}
执行完copyApk()方法后,安装第一阶段的工作就完成了,应用拷贝到了/data/app目录下。我们接下来看看handleReturnCode()中processPendingInstall()的内容,也就是第二阶段。
2.2 装载应用
装载应用从handleReturnCode()中的processPendingInstall()方法开始,代码如下:
private void processInstallRequestsAsync(boolean success,
List<InstallRequest> installRequests) {
mHandler.post(() -> {
if (success) {
for (InstallRequest request : installRequests) {
request.args.doPreInstall(request.installResult.returnCode);
}
//这里开始解析APK
synchronized (mInstallLock) {
installPackagesTracedLI(installRequests);
}
for (InstallRequest request : installRequests) {
request.args.doPostInstall(
request.installResult.returnCode, request.installResult.uid);
}
}
for (InstallRequest request : installRequests) {
restoreAndPostInstall(request.args.user.getIdentifier(), request.installResult,
new PostInstallData(request.args, request.installResult, null));
}
});
}
核心方法为installPackagesTracedLI(),它最终会调用到installPackagesLI(),代码如下:
private void installPackagesLI(List<InstallRequest> requests) {
...
try {
for (InstallRequest request : requests) {
final PrepareResult prepareResult;
//1.准备:分析当前安装状态,解析包并初始验证
prepareResult = preparePackageLI(request.args, request.installResult);
...
try {
//2.扫描:根据准备阶段解析的包信息上下文 进一步解析
final ScanResult result = scanPackageTracedLI(prepareResult.packageToScan, prepareResult.parseFlags,prepareResult.scanFlags, System.currentTimeMillis(),request.args.user, request.args.abiOverride);
...
//注册appId
createdAppId.put(packageName, optimisticallyRegisterAppId(result));
//保存version信息
versionInfos.put(result.pkgSetting.pkg.getPackageName(),getSettingsVersionForPackage(result.pkgSetting.pkg));
}
...
}
ReconcileRequest reconcileRequest = new ReconcileRequest(preparedScans, installArgs,installResults,prepareResults,mSharedLibraries,Collections.unmodifiableMap(mPackages), versionInfos,lastStaticSharedLibSettings);
CommitRequest commitRequest = null;
synchronized (mLock) {
Map<String, ReconciledPackage> reconciledPackages;
try {
//3.核对:验证扫描后的包信息和系统状态,确保安装成功
reconciledPackages = reconcilePackagesLocked(
reconcileRequest, mSettings.mKeySetManagerService);
} catch (ReconcileFailure e) {
for (InstallRequest request : requests) {
request.installResult.setError("Reconciliation failed...", e);
}
return;
}
try {
//4.提交:提交扫描的包、更新系统状态。这是唯一可以修改系统状态的地方,并且要对所有可预测的错误进行检测。
commitRequest = new CommitRequest(reconciledPackages,
mUserManager.getUserIds());
commitPackagesLocked(commitRequest);
success = true;
}
}
//安装成功的后续才做:准备app数据、编译布局资源、执行dex优化
executePostCommitSteps(commitRequest);
} ...
}
安装过程分四个阶段:
- 准备,分析当前安装状态,解析包 并初始校验: 在 preparePackageLI() 内使用 PackageParser.parsePackage() 解析AndroidManifest.xml,获取四大组件等信息;使用ParsingPackageUtils.getSigningDetails() 解析签名信息;重命名包最终路径 等。这里的代码量特别大,尤其是解析AndroidManifest.xml文件,涉及到大量的标签解析,所以就不展开了
- 扫描,根据准备阶段解析的包信息上下文调用scanPackageTracedLI() 进一步解析, 确认包名真实;根据解析出的信息校验包有效性(是否有签名信息等);搜集apk信息——PackageSetting、apk的静态库/动态库信息等。
- 核对,验证扫描后的包信息,确保安装成功:主要就是覆盖安装的签名匹配验证。
- 提交,提交扫描的包、更新系统状态:添加 PackageSetting 到 PMS 的 mSettings、添加 AndroidPackage 到 PMS 的 mPackages 、添加 秘钥集 到系统、应用的权限添加到 mPermissionManager、四大组件信息添加到 mComponentResolver 。这是唯一可以修改系统状态的地方,并且要对所有可预测的错误进行检测。
前三步主要是 解析和校验,第四步是把 包信息 提交到 PKMS 内存数据结构中。 其中解析和提交在之前的PMS初始化中 扫描apk目录后也是同样的过程。
将APK信息提交到PKMS后,调用了 executePostCommitSteps() 准备app数据、执行dex优化:
private void executePostCommitSteps(CommitRequest commitRequest) {
...
for (ReconciledPackage reconciledPkg : commitRequest.reconciledPackages.values()) {
...
//提供目录结构/data/user/用户ID/包名/cache(/data/user/用户ID/包名/code_cache)
prepareAppDataAfterInstallLIF(pkg);
...
//是否要dex opt
final boolean performDexopt =
(!instantApp || Global.getInt(mContext.getContentResolver(),
Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0)
&& !pkg.isDebuggable()
&& (!onIncremental);
if (performDexopt) {
...
DexoptOptions dexoptOptions = new DexoptOptions(packageName,REASON_INSTALL,flags);
...
//执行dex优化:dexopt 是对 dex 文件 进行 verification 和 optimization 的操作,其对 dex 文件的优化结果变成了 odex 文件,这个文件和 dex 文件很像,只是使用了一些优化操作码(譬如优化调用虚拟指令等)。
mPackageDexOptimizer.performDexOpt(pkg, realPkgSetting,
null /* instructionSets */,
getOrCreateCompilerPackageStats(pkg),
mDexManager.getPackageUseInfoOrDefault(packageName),
dexoptOptions);
}...
}
}
这里的prepareAppDataAfterInstallLIF和performDexOpt操作均由 Installer 对应的方法来操作,而 Installer 继承自 SystemService 也是一个系统服务,它就是installd。这两个操作需要installd来完成是因为它们需要root权限,而PKMS只有System权限。
到这里安装完成,再回到 PMS 的 processInstallRequestsAsync(),最后调用restoreAndPostInstall()进行 备份、可能的回滚、发送安装完成相关广播,更新Launcher的UI。