我们平时会经常使用手机上的各种丰富的APP,有没有想过一个应用是如何安装到Android设备上的,安装的过程中发生了哪些事情。本文从源码角度分析记录一下Android设备上应用安装过程。
一. APP常见的安装方式有哪些
1.从应用商店下载一个apk,直接点击安装,此时会跳转到一个安装提示页面。这是最常见的安装方式了。
2.通过adb 命令安装。
3.往system/app或者system/priv-app 目录下push一个apk。
、、、
二. 应用安装流程
2.1 安装申请
当我们某个应用要安装一个APP或者点击一个apk文件的时候通常是以下面的方式去申请安装一个apk。
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
/*
* 自Android N开始,是通过FileProvider共享相关文件,但是Android Q对公
* 有目录 File API进行了限制,只能通过Uri来操作
*/
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
// filePath是通过ContentResolver得到的
intent.setDataAndType(Uri.parse(filePath) ,"application/vnd.android.package-archive");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri contentUri = FileProvider.getUriForFile(mContext, "com.dhl.file.fileProvider", file);
intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
} else {
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
}
startActivity(intent);
// 需要在AndroidManifest添加权限
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
可以看到其实就是将要安装的apk参数放到intent内,并且打开了一个activity。这个activity其实是系统一个内置应用PackageInstaller里的一个页面。
<activity android:name=".InstallStart"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:launchMode="singleTask"
android:exported="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="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="package" />
<data android:scheme="content" />
</intent-filter>
<intent-filter android:priority="1">
<action android:name="android.content.pm.action.CONFIRM_INSTALL" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
2.2 InstallStart 页面
主要工作:
1.判断是否勾选“未知来源”选项,若未勾选跳转到设置安装未知来源界面
2.对于大于等于 Android 8.0 版本,会先检查是否申请安装权限,若没有则中断安装
3.判断 Uri 的 Scheme 协议,若是 content 则调用 InstallStaging, 若是 package 则调用 PackageInstallerActivity
[InstallStart.java]
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
final boolean isSessionInstall =
PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction());
...
final ApplicationInfo sourceInfo = getSourceInfo(callingPackage);
final int originatingUid = getOriginatingUid(sourceInfo);
boolean isTrustedSource = false;
// 判断是否勾选“未知来源”选项
if (sourceInfo != null
&& (sourceInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
isTrustedSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false);
}
if (!isTrustedSource && originatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {
final int targetSdkVersion = getMaxTargetSdkVersionForUid(this, originatingUid);
// 如果targetSdkVerison小于0中止安装
if (targetSdkVersion < 0) {
Log.w(LOG_TAG, "Cannot get target sdk version for uid " + originatingUid);
mAbortInstall = true;
// 如果targetSdkVersion大于等于26(8.0), 且获取不到REQUEST_INSTALL_PACKAGES权限中止安装
} else if (targetSdkVersion >= Build.VERSION_CODES.O && !declaresAppOpPermission(
originatingUid, Manifest.permission.REQUEST_INSTALL_PACKAGES)) {
Log.e(LOG_TAG, "Requesting uid " + originatingUid + " needs to declare permission "
+ Manifest.permission.REQUEST_INSTALL_PACKAGES);
mAbortInstall = true;
}
}
...
// 如果设置了ACTION_CONFIRM_PERMISSIONS,则调用PackageInstallerActivity。
if (isSessionInstall) {
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
Uri packageUri = intent.getData();
// 判断Uri的Scheme协议是否是content
if (packageUri != null && packageUri.getScheme().equals(
ContentResolver.SCHEME_CONTENT)) {
// [IMPORTANT] This path is deprecated, but should still work.
// 这个路径已经被起用了,但是仍然可以工作
// 调用InstallStaging来拷贝file/content,防止被修改
nextActivity.setClass(this, InstallStaging.class);
} else if (packageUri != null && packageUri.getScheme().equals(
PackageInstallerActivity.SCHEME_PACKAGE)) {
// 如果Uri中包含package,则调用PackageInstallerActivity
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
// Uri不合法
Intent result = new Intent();
result.putExtra(Intent.EXTRA_INSTALL_RESULT,
PackageManager.INSTALL_FAILED_INVALID_URI);
setResult(RESULT_FIRST_USER, result);
nextActivity = null;
}
}
if (nextActivity != null) {
startActivity(nextActivity);
}
finish();
}
根据 Uri 的 Scheme 协议,若是 content 则调用 InstallStaging,如果scheme是 PackageInstallerActivity.SCHEME_PACKAGE就打开PackageInstallerActivity。先看下InstallStaging。
[InstallStaging.java]
@Override
protected void onResume() {
super.onResume();
if (mStagingTask == null) {
if (mStagedFile == null) {
// 创建临时文件 mStagedFile 用来存储数据
try {
mStagedFile = TemporaryFileManager.getStagedFile(this);
} catch (IOException e) {
showError();
return;
}
}
// 启动 StagingAsyncTask,并传入了content协议的Uri
mStagingTask = new StagingAsyncTask();
mStagingTask.execute(getIntent().getData());
}
}
1.创建临时文件 mStagedFile 用来存储数据
2.启动 StagingAsyncTask,并传入了 content 协议的 Uri
[InstallStaging.java]
private final class StagingAsyncTask extends AsyncTask<Uri, Void, Boolean> {
@Override
protected Boolean doInBackground(Uri... params) {
...
Uri packageUri = params[0];
try (InputStream in = getContentResolver().openInputStream(packageUri)) {
...
// 将packageUri(content协议的Uri)的内容写入到mStagedFile中
try (OutputStream out = new FileOutputStream(mStagedFile)) {
byte[] buffer = new byte[1024 * 1024];
int bytesRead;
while ((bytesRead = in.read(buffer)) >= 0) {
// Be nice and respond to a cancellation
if (isCancelled()) {
return false;
}
out.write(buffer, 0, bytesRead);
}
}
} catch (IOException | SecurityException | IllegalStateException e) {
Log.w(LOG_TAG, "Error staging apk from content URI", e);
return false;
}
return true;
}
@Override
protected void onPostExecute(Boolean success) {
if (success) {
// 如果写入成功,调用DeleteStagedFileOnResult
Intent installIntent = new Intent(getIntent());
installIntent.setClass(InstallStaging.this, DeleteStagedFileOnResult.class);
installIntent.setData(Uri.fromFile(mStagedFile));
...
startActivity(installIntent);
InstallStaging.this.finish();
} else {
showError();
}
}
}
doInBackground 方法中将 packageUri(content 协议的 Uri)的内容写入到 mStagedFile 中。如果写入成功,调用 DeleteStagedFileOnResult 的 OnCreate 方法。DeleteStagedFileOnResult 的OnCreate 又打开了PackageInstallerActivity页面。
2.3 PackageInstallerActivity
主要工作:
1.显示安装界面
2.初始化安装需要用的各种对象,比如 PackageManager、IPackageManager、AppOpsManager、UserManager、PackageInstaller 等等
3.根据传递过来的 Scheme 协议做不同的处理
4.检查是否允许、初始化安装
5.在准备安装的之前,检查应用列表判断该应用是否已安装,若已安装则提示该应用已安装,由用户决定是否替换
6.在安装界面,提取出 APK 中权限信息并展示出来
7.点击 OK 按钮确认安装后,会调用 startInstall 开始安装工作
PackageInstallerActivity 才是应用安装器 PackageInstaller 真正的入口 Activity。
[PackageInstallerActivity .java]
protected void onCreate(Bundle icicle) {
getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
super.onCreate(null);
// 初始化安装需要用到的对象
mPm = getPackageManager();
mIpm = AppGlobals.getPackageManager();
mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
mInstaller = mPm.getPackageInstaller();
mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
// 根据Uri的Scheme做不同的处理
boolean wasSetUp = processPackageUri(packageUri);
if (!wasSetUp) {
return;
}
// 显示安装界面
bindUi();
// 检查是否允许安装包,如果允许则启动安装。如果不允许显示适当的对话框
checkIfAllowedAndInitiateInstall();
}
主要做了对象的初始化,解析 Uri 的 Scheme,初始化界面,安装包检查等等工作,接着查看一下 processPackageUri 方法
private boolean processPackageUri(final Uri packageUri) {
mPackageURI = packageUri;
final String scheme = packageUri.getScheme();
// 根据这个Scheme协议分别对package协议和file协议进行处理
switch (scheme) {
case SCHEME_PACKAGE: {
try {
// 通过PackageManager对象获取指定包名的包信息
mPkgInfo = mPm.getPackageInfo(packageUri.getSchemeSpecificPart(),
PackageManager.GET_PERMISSIONS
| PackageManager.MATCH_UNINSTALLED_PACKAGES);
} catch (NameNotFoundException e) {
}
if (mPkgInfo == null) {
Log.w(TAG, "Requested package " + packageUri.getScheme()
+ " not available. Discontinuing installation");
showDialogInner(DLG_PACKAGE_ERROR);
setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
return false;
}
mAppSnippet = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo),
mPm.getApplicationIcon(mPkgInfo.applicationInfo));
} break;
case ContentResolver.SCHEME_FILE: {
// 根据packageUri创建一个新的File
File sourceFile = new File(packageUri.getPath());
// 解析APK得到APK的信息,PackageParser.Package存储了APK的所有信息
PackageParser.Package parsed = PackageUtil.getPackageInfo(this, sourceFile);
if (parsed == null) {
Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
showDialogInner(DLG_PACKAGE_ERROR);
setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
return false;
}
// 根据PackageParser.Package得到的APK信息,生成PackageInfo
mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
PackageManager.GET_PERMISSIONS, 0, 0, null,
new PackageUserState());
mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
} break;
、、、
}
return true;
}
主要对 Scheme 协议分别对 package 协议和 file 协议进行处理
SCHEME_PACKAGE:
在 package 协议中调用了 PackageManager.getPackageInfo 方法生成 PackageInfo,PackageInfo 是跨进程传递的包数据(activities、receivers、services、providers、permissions等等)包含 APK 的所有信息
SCHEME_FILE:
在 file 协议的处理中调用了 PackageUtil.getPackageInfo 方法,方法内部调用了 PackageParser.parsePackage() 把 APK 文件的 manifest 和签名信息都解析完成并保存在了 Package,Package 包含了该 APK 的所有信息
调用 PackageParser.generatePackageInfo 生成 PackageInfo
接着往下走,都解析完成之后,回到 onCreate 方法,继续调用 checkIfAllowedAndInitiateInstall 方法
private void checkIfAllowedAndInitiateInstall() {
// 首先检查安装应用程序的用户限制,如果有限制并弹出弹出提示Dialog或者跳转到设置界面
final int installAppsRestrictionSource = mUserManager.getUserRestrictionSource(
UserManager.DISALLOW_INSTALL_APPS, Process.myUserHandle());
if ((installAppsRestrictionSource & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) {
showDialogInner(DLG_INSTALL_APPS_RESTRICTED_FOR_USER);
return;
} else if (installAppsRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
finish();
return;
}
// 判断如果允许安装未知来源或者根据Intent判断得出该APK不是未知来源
if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) {
initiateInstall();
} else {
// 检查未知安装源限制,如果有限制弹出Dialog,显示相应的信息
final int unknownSourcesRestrictionSource = mUserManager.getUserRestrictionSource(
UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, Process.myUserHandle());
final int unknownSourcesGlobalRestrictionSource = mUserManager.getUserRestrictionSource(
UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, Process.myUserHandle());
final int systemRestriction = UserManager.RESTRICTION_SOURCE_SYSTEM
& (unknownSourcesRestrictionSource | unknownSourcesGlobalRestrictionSource);
if (systemRestriction != 0) {
showDialogInner(DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER);
} else if (unknownSourcesRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
startAdminSupportDetailsActivity(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
} else if (unknownSourcesGlobalRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
startAdminSupportDetailsActivity(
UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY);
} else {
// 处理未知来源的APK
handleUnknownSources();
}
}
}
检查条件都通过后会初始化安装提示页面,当点击确认按钮后会通过点击事件的回调打开InstallInstalling页面开始安装。
2.4 InstallInstalling
[InstallInstalling.java]
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
// 判断安装的应用是否已经存在
if ("package".equals(mPackageURI.getScheme())) {
try {
getPackageManager().installExistingPackage(appInfo.packageName);
launchSuccess();
} catch (PackageManager.NameNotFoundException e) {
launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
}
} else {
final File sourceFile = new File(mPackageURI.getPath());
PackageUtil.AppSnippet as = PackageUtil.getAppSnippet(this, appInfo, sourceFile);
、、、
// 注册InstallEventReceiver,并在launchFinishBasedOnResult会接收到安装事件的回调
mInstallId = InstallEventReceiver
.addObserver(this, EventResultPersister.GENERATE_NEW_ID,this::launchFinishBasedOnResult);
、、、
}
protected void onResume() {
super.onResume();
if (mInstallingTask == null) {
PackageInstaller installer = getPackageManager().getPackageInstaller();
// 根据mSessionId 获取SessionInfo, 代表安装会话的详细信息
PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId);
if (sessionInfo != null && !sessionInfo.isActive()) {
mInstallingTask = new InstallingAsyncTask();
mInstallingTask.execute();
} else {
// 安装完成后会收到广播
mCancelButton.setEnabled(false);
setFinishOnTouchOutside(false);
}
}
}
InstallingAsyncTask
/**
* Send the package to the package installer and then register a event result observer that
* will call {@link #launchFinishBasedOnResult(int, int, String)}
*/
private final class InstallingAsyncTask extends AsyncTask<Void, Void,
PackageInstaller.Session> {
volatile boolean isDone;
@Override
protected PackageInstaller.Session doInBackground(Void... params) {
PackageInstaller.Session session;
try {
session = getPackageManager().getPackageInstaller().openSession(mSessionId);
} catch (IOException e) {
return null;
}
session.setStagingProgress(0);
try {
File file = new File(mPackageURI.getPath());
//打开一个流以将 APK 文件写入会话。
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) {
int numRead = in.read(buffer);
if (numRead == -1) {
session.fsync(out);
break;
}
if (isCancelled()) {
session.close();
break;
}
//将 APK 文件通过 IO 流的形式写入到 PackageInstaller.Session 中
out.write(buffer, 0, numRead);
if (sizeBytes > 0) {
float fraction = ((float) numRead / (float) sizeBytes);
session.addProgress(fraction);
}
}
}
}
return session;
} catch (IOException | SecurityException e) {
Log.e(LOG_TAG, "Could not write package", e);
session.close();
return null;
} finally {
synchronized (this) {
isDone = true;
notifyAll();
}
}
}
@Override
protected void onPostExecute(PackageInstaller.Session session) {
if (session != null) {
、、、
PendingIntent pendingIntent = PendingIntent.getBroadcast(
InstallInstalling.this,
mInstallId,
broadcastIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
//提交会话结果
session.commit(pendingIntent.getIntentSender());
、、、
} else {
getPackageManager().getPackageInstaller().abandonSession(mSessionId);
if (!isCancelled()) {
launchFailure(PackageManager.INSTALL_FAILED_INVALID_APK, null);
}
}
}
}
2.5 session.commit
[PackageInstaller.java]
/**
* Attempt to commit everything staged in this session. This may require
* user intervention, and so it may not happen immediately. The final
* result of the commit will be reported through the given callback.
* <p>
* Once this method is called, the session is sealed and no additional
* mutations may be performed on the session. If the device reboots
* before the session has been finalized, you may commit the session again.
* <p>
* If the installer is the device owner or the affiliated profile owner, there will be no
* user intervention.
*
* @param statusReceiver Called when the state of the session changes. Intents
* sent to this receiver contain {@link #EXTRA_STATUS}. Refer to the
* individual status codes on how to handle them.
*
* @throws SecurityException if streams opened through
* {@link #openWrite(String, long, long)} are still open.
*
* @see android.app.admin.DevicePolicyManager
*/
public void commit(@NonNull IntentSender statusReceiver) {
try {
mSession.commit(statusReceiver, false);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
mSession 就是 IPackageInstallerSession 。这说明要通过 IPackageInstallerSession 来进行进程间的通信,最终会调用PackageInstallerSession 的 commit 方法
[PackageInstallerSession.java]
public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private final Handler.Callback mHandlerCallback = new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case MSG_COMMIT:
handleCommit();
break;
、、、
}
return true;
}
};
@Override
public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
、、、
mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
}
private void handleCommit() {
、、、
// For a multiPackage session, read the child sessions
// outside of the lock, because reading the child
// sessions with the lock held could lead to deadlock
// (b/123391593).
List<PackageInstallerSession> childSessions = getChildSessions();
try {
synchronized (mLock) {
commitNonStagedLocked(childSessions);
}
} catch (PackageManagerException e) {
final String completeMsg = ExceptionUtils.getCompleteMessage(e);
Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg);
destroyInternal();
dispatchSessionFinished(e.error, completeMsg, null);
}
}
@GuardedBy("mLock")
private void commitNonStagedLocked(List<PackageInstallerSession> childSessions)
throws PackageManagerException {
final PackageManagerService.ActiveInstallSession committingSession =
makeSessionActiveLocked();
if (committingSession == null) {
return;
}
if (isMultiPackage()) {
List<PackageManagerService.ActiveInstallSession> activeChildSessions =
new ArrayList<>(childSessions.size());
boolean success = true;
、、、
if (!success) {
try {
mRemoteObserver.onPackageInstalled(
null, failure.error, failure.getLocalizedMessage(), null);
} catch (RemoteException ignored) {
}
return;
}
//mPm是PackageManagerService
mPm.installStage(activeChildSessions);
} else {
//mPm是PackageManagerService
mPm.installStage(committingSession);
}
}
}
此时InstallInstalling就通过session将应用的安装信息发送给了PackageManagerService,前面页面也注册了一些安装过程的回调监听,当PackageManagerService安装完成后会回调InstallEventReceiver,安装成功和失败,都会启动一个新的 Activity(InstallSuccess、InstallFailed)将结果展示给用户,然后 finish 掉 InstallInstalling。
三 push&adb 安装
当我们Push一个应用到system/app下的时候,此时并没有出发Install相关的页面打开,此时应用又是怎么安装的。我们知道当系统启动的时候会启动一系类的服务,其中包括PackageManagerService。
[PackageManagerService.java]
public static PackageManagerService main(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
// Self-check for initial settings.
PackageManagerServiceCompilerMapping.checkProperties();
PackageManagerService m = new PackageManagerService(context, installer,
factoryTest, onlyCore);
m.enableSystemUserPackages();
ServiceManager.addService("package", m);
final PackageManagerNative pmn = m.new PackageManagerNative();
ServiceManager.addService("package_native", pmn);
return m;
}
public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
、、、
// Collect privileged system packages.
final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
scanDirTracedLI(privilegedAppDir,
mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM_DIR,
scanFlags
| SCAN_AS_SYSTEM
| SCAN_AS_PRIVILEGED,
0);
// Collect ordinary system packages.
final File systemAppDir = new File(Environment.getRootDirectory(), "app");
scanDirTracedLI(systemAppDir,
mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM_DIR,
scanFlags
| SCAN_AS_SYSTEM,
0);
、、、
}
PMS 会去扫描各个系统目录并最终走到PMS的核心流程installPackagesLI。其实adb install的方式也是如此,只不过出发方式不同。
四 PMS
在2.5讲到InstallInstalling 页面通过一个session将安装信息发通过installStage方法送给了PMS。
4.1 PMS->installStage
void installStage(ActiveInstallSession activeInstallSession) {
、、、
final Message msg = mHandler.obtainMessage(INIT_COPY);
final InstallParams params = new InstallParams(activeInstallSession);
params.setTraceMethod("installStage").setTraceCookie(System.identityHashCode(params));
msg.obj = params;
mHandler.sendMessage(msg);
}
void doHandleMessage(Message msg) {
switch (msg.what) {
case INIT_COPY: {
HandlerParams params = (HandlerParams) msg.obj;
if (params != null) {
、、、
params.startCopy();
}
break;
、、、
}
、、、
}
final void startCopy() {
if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ": " + this);
//拷贝apk
handleStartCopy();
handleReturnCode();
}
@Override
void handleReturnCode() {
if (mVerificationCompleted && mEnableRollbackCompleted) {
、、、
processPendingInstall(mArgs, mRet);
}
}
installStage通过handler发送了一个INIT_COPY消息,将apk拷贝到指定的临时目录方便后续处理。processPendingInstall->processInstallRequestsAsync->installPackagesTracedLI->installPackagesLI.
我们发现最终都是进入了PMS的核心流程installPackagesLI。
4.2 installPackagesLI
[PackageManagerService.java]
/**
* Installs one or more packages atomically. This operation is broken up into four phases:
* <ul>
* <li><b>Prepare</b>
* <br/>Analyzes any current install state, parses the package and does initial
* validation on it.</li>
* <li><b>Scan</b>
* <br/>Interrogates the parsed packages given the context collected in prepare.</li>
* <li><b>Reconcile</b>
* <br/>Validates scanned packages in the context of each other and the current system
* state to ensure that the install will be successful.
* <li><b>Commit</b>
* <br/>Commits all scanned packages and updates system state. This is the only place
* that system state may be modified in the install flow and all predictable errors
* must be determined before this phase.</li>
* </ul>
*
* Failure at any phase will result in a full failure to install all packages.
*/
@GuardedBy("mInstallLock")
private void installPackagesLI(List<InstallRequest> requests) {
...
// Prepare 准备:分析任何当前安装状态,分析包并对其进行初始验证.检查SDK版本、静态库等;检查签名;设置权限;
prepareResult = preparePackageLI(request.args, request.installResult);
...
//Scan 扫描:prepare中收集的上下文,询问已分析的包。
final List<ScanResult> scanResults = scanPackageTracedLI(
prepareResult.packageToScan, prepareResult.parseFlags,
prepareResult.scanFlags, System.currentTimeMillis(),
request.args.user);
...
// Reconcile 调和:在彼此的上下文和当前系统状态中验证扫描的包,以确保安装成功。
ReconcileRequest reconcileRequest = new ReconcileRequest(preparedScans, installArgs,
installResults,
prepareResults,
mSharedLibraries,
Collections.unmodifiableMap(mPackages), versionInfos,
lastStaticSharedLibSettings);
...
// Commit 提交:提交所有扫描的包并更新系统状态。这是安装流中唯一可以修改系统状态的地方,必须在此阶段之前确定所有可预测的错误。
commitPackagesLocked(commitRequest);
...
// 完成APK的安装
executePostCommitSteps(commitRequest);
}
/**
* On successful install, executes remaining steps after commit completes and the package lock
* is released. These are typically more expensive or require calls to installd, which often
* locks on {@link #mPackages}.
*/
private void executePostCommitSteps(CommitRequest commitRequest) {
for (ReconciledPackage reconciledPkg : commitRequest.reconciledPackages.values()) {
final boolean instantApp = ((reconciledPkg.scanResult.request.scanFlags
& PackageManagerService.SCAN_AS_INSTANT_APP) != 0);
final PackageParser.Package pkg = reconciledPkg.pkgSetting.pkg;
final String packageName = pkg.packageName;
prepareAppDataAfterInstallLIF(pkg);
if (reconciledPkg.prepareResult.clearCodeCache) {
clearAppDataLIF(pkg, UserHandle.USER_ALL, FLAG_STORAGE_DE | FLAG_STORAGE_CE
| FLAG_STORAGE_EXTERNAL | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
}
if (reconciledPkg.prepareResult.replace) {
mDexManager.notifyPackageUpdated(pkg.packageName,
pkg.baseCodePath, pkg.splitCodePaths);
}
// Prepare the application profiles for the new code paths.
// This needs to be done before invoking dexopt so that any install-time profile
// can be used for optimizations.
mArtManagerService.prepareAppProfiles(
pkg,
resolveUserIds(reconciledPkg.installArgs.user.getIdentifier()),
/* updateReferenceProfileContent= */ true);
// Check whether we need to dexopt the app.
//
// NOTE: it is IMPORTANT to call dexopt:
// - after doRename which will sync the package data from PackageParser.Package and
// its corresponding ApplicationInfo.
// - after installNewPackageLIF or replacePackageLIF which will update result with the
// uid of the application (pkg.applicationInfo.uid).
// This update happens in place!
//
// We only need to dexopt if the package meets ALL of the following conditions:
// 1) it is not an instant app or if it is then dexopt is enabled via gservices.
// 2) it is not debuggable.
//
// Note that we do not dexopt instant apps by default. dexopt can take some time to
// complete, so we skip this step during installation. Instead, we'll take extra time
// the first time the instant app starts. It's preferred to do it this way to provide
// continuous progress to the useur instead of mysteriously blocking somewhere in the
// middle of running an instant app. The default behaviour can be overridden
// via gservices.
final boolean performDexopt =
(!instantApp || Global.getInt(mContext.getContentResolver(),
Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0)
&& ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0);
if (performDexopt) {
、、、
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
// Do not run PackageDexOptimizer through the local performDexOpt
// method because `pkg` may not be in `mPackages` yet.
//
// Also, don't fail application installs if the dexopt step fails.
DexoptOptions dexoptOptions = new DexoptOptions(packageName,
REASON_INSTALL,
DexoptOptions.DEXOPT_BOOT_COMPLETE
| DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE);
mPackageDexOptimizer.performDexOpt(pkg,
null /* instructionSets */,
getOrCreateCompilerPackageStats(pkg),
mDexManager.getPackageUseInfoOrDefault(packageName),
dexoptOptions);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
// Notify BackgroundDexOptService that the package has been changed.
// If this is an update of a package which used to fail to compile,
// BackgroundDexOptService will remove it from its blacklist.
// TODO: Layering violation
BackgroundDexOptService.notifyPackageChanged(packageName);
}
}
installPackagesLI()方法内部非常复杂,上面简单调了一些主流程拿出来。
1.Prepare准备:分析任何当前安装状态,分析包并对其进行初始验证。
在这一阶段首先是将apk文件解析出来,解析它的AndroidManifest.xml文件,将结果记录起来。我们平时在清单文件中声明的Activity等组件就是在这一步被记录到Framework中的,后续才能通过startActivity等方式启动来
然后是对签名信息进行验证
设置相关权限
生成安装包abi
dex优化,实际为dex2oat操作,将apk中的dex文件转换为oat文件
2.Scan扫描:考虑到prepare中收集的上下文,询问已分析的包
3.Reconcile调和:在彼此的上下文和当前系统状态中验证扫描的包,以确保安装成功
4.Commit提交:提交所有扫描的包并更新系统状态。这是安装流中唯一可以修改系统状态的地方,必须在此阶段之前确定所有可预测的错误
五 Settings
这里是安装的最后一步将安装信息更新到系统的/data/system/packages.xml文件中。
PMS的构造方法中初始化了一个Settings对象的实例,我们看一下Settings构造方法。
[Setting.java]
Settings(File dataDir, PermissionSettings permission,
Object lock) {
mLock = lock;
mPermissions = permission;
mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock);
mSystemDir = new File(dataDir, "system");
mSystemDir.mkdirs();
FileUtils.setPermissions(mSystemDir.toString(),
FileUtils.S_IRWXU|FileUtils.S_IRWXG
|FileUtils.S_IROTH|FileUtils.S_IXOTH,
-1, -1);
// data/system 目录下的packages.xml
mSettingsFilename = new File(mSystemDir, "packages.xml");
//packages.xml 一个备份文件,防止系统配置信息写入出错的。一旦写入失败,可以从这个文件恢复
mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml");
mPackageListFilename = new File(mSystemDir, "packages.list");
FileUtils.setPermissions(mPackageListFilename, 0640, SYSTEM_UID, PACKAGE_INFO_GID);
final File kernelDir = new File("/config/sdcardfs");
mKernelMappingFilename = kernelDir.exists() ? kernelDir : null;
// Deprecated: Needed for migration
mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml");
mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml");
}
Settings对象的构造过程很简单,就是创建一些目录和文件。首先创建/data/system/目录,然后分别创建以下几个文件:
/data/system/packages.xml
/data/system/packages-backup.xml
/data/system/packages.list
/data/system/packages-stopped.xml
/data/system/packages-stopped-backup.xml
Settings通过addSharedUserLPw函数添加向mSharedUsers预先添加SharedUserSetting对象
那应用的安装信息是如何写入到packages.xml文件的呢?在前面的流程中已经解析收集到了安装包的所有信息,并且保存到了Settings对象实例中,4.2中 PMS.commitPackagesLocked->PMS.updateSettingsLI->PMS.updateSettingsInternalLI->Settings.writeLPr写入到系统文件。
[Settings.java]
void writeLPr() {
// Keep the old settings around until we know the new ones have
// been successfully written.
if (mSettingsFilename.exists()) {
//如果备份文件不存在,就将当前系统文件重命名为packages-backup.xml。将新的信息写入到packages.xml
//如果备份文件存在,就删除packages.xml,后面重新写入
if (!mBackupSettingsFilename.exists()) {
if (!mSettingsFilename.renameTo(mBackupSettingsFilename)) {
Slog.wtf(PackageManagerService.TAG,
"Unable to backup package manager settings, "
+ " current changes will be lost at reboot");
return;
}
} else {
mSettingsFilename.delete();
Slog.w(PackageManagerService.TAG, "Preserving older settings backup");
}
}
mPastSignatures.clear();
try {
FileOutputStream fstr = new FileOutputStream(mSettingsFilename);
BufferedOutputStream str = new BufferedOutputStream(fstr);
//XmlSerializer serializer = XmlUtils.serializerInstance();
XmlSerializer serializer = new FastXmlSerializer();
serializer.setOutput(str, StandardCharsets.UTF_8.name());
serializer.startDocument(null, true);
serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
serializer.startTag(null, "packages");
、、、
serializer.startTag(null, "permission-trees");
mPermissions.writePermissionTrees(serializer);
serializer.endTag(null, "permission-trees");
serializer.startTag(null, "permissions");
mPermissions.writePermissions(serializer);
serializer.endTag(null, "permissions");
//写入新的值
for (final PackageSetting pkg : mPackages.values()) {
writePackageLPr(serializer, pkg);
}
for (final PackageSetting pkg : mDisabledSysPackages.values()) {
writeDisabledSysPackageLPr(serializer, pkg);
}
、、、
serializer.endTag(null, "packages");
serializer.endDocument();
//刷新一下
str.flush();
FileUtils.sync(fstr);
str.close();
// New settings successfully written, old ones are no longer
// needed.
mBackupSettingsFilename.delete();
FileUtils.setPermissions(mSettingsFilename.toString(),
FileUtils.S_IRUSR|FileUtils.S_IWUSR
|FileUtils.S_IRGRP|FileUtils.S_IWGRP,
-1, -1);
writeKernelMappingLPr();
writePackageListLPr();
writeAllUsersPackageRestrictionsLPr();
writeAllRuntimePermissionsLPr();
com.android.internal.logging.EventLogTags.writeCommitSysConfigFile(
"package", SystemClock.uptimeMillis() - startTime);
return;
} catch(java.io.IOException e) {
Slog.wtf(PackageManagerService.TAG, "Unable to write package manager settings, "
+ "current changes will be lost at reboot", e);
}
// Clean up partially written files
if (mSettingsFilename.exists()) {
if (!mSettingsFilename.delete()) {
Slog.wtf(PackageManagerService.TAG, "Failed to clean up mangled file: "
+ mSettingsFilename);
}
}
//Debug.stopMethodTracing();
}
六 总结
根据根据 Uri 的 Scheme 找到入口 InstallStart
InstallStart 根据 Uri 的 Scheme 协议不同做不同的处理
都会调用 PackageInstallerActivity, 然后分别对package协议和 file 协议的 Uri 进行处理
PackageInstallerActivity 检查未知安装源限制,如果安装源限制弹出提示 Dialog
点击 OK 按钮确认安装后,会调用 startInstall 开始安装工作
如果用户允许安装,然后跳转到 InstallInstalling,进行 APK 的安装工作
在 InstallInstalling 中,向包管理器发送包的信息,然后注册一个观察者 InstallEventReceiver,并接受安装成功和失败的回调
PMS 拷贝apk,Prepare->Scan->Reconcile->Commit,并把应用的信息写入到packages.xml
参考:https://juejin.cn/post/6844904078015725576
https://zhuanlan.zhihu.com/p/428859943
本文由mdnice多平台发布