应用安装-点击apk安装

之前了解了通过adb进行应用安装的过程,现在了解在应用中点击apk进行应用安装的过程,代码基于Android P。安装流程的PKMS部分和通过adb安装类似,这里主要是了解下应用安装发起过程和PackageInstaller相关代码。

发起安装

该部分以DocumentsUI为例,了解应用安装发起的过程。打开Files应用,切换到内部存储,找到apk,点击安装。对应的代码如下:

1
2
3
4
5
6
7
8
9
10
11
private void onDocumentPicked(DocumentInfo doc, @ViewType int type, @ViewType int fallback) {
//……
// For APKs, even if the type is preview, we send an ACTION_VIEW intent to allow
// PackageManager to install it. This allows users to install APKs from any root.
// The Downloads special case is handled above in #manageDocument.
if (MimeTypes.isApkType(doc.mimeType)) {
viewDocument(doc);
return;
}
//……
}
1
2
3
4
5
6
7
8
9
10
11
private boolean viewDocument(DocumentInfo doc) {
//……
Intent intent = buildViewIntent(doc);
try {
mActivity.startActivity(intent);
return true;
} catch (ActivityNotFoundException e) {
mDialogs.showNoApplicationFound();
}
return false;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private Intent buildViewIntent(DocumentInfo doc) {
Intent intent = new Intent(Intent.ACTION_VIEW);
//doc.derivedUri = content://com.android.externalstorage.documents/document/primary%3ATurbo.apk(%3A代表:)
//doc.mimeType = APK_TYPE = "application/vnd.android.package-archive"
intent.setDataAndType(doc.derivedUri, doc.mimeType);

// Downloads has traditionally added the WRITE permission
// in the TrampolineActivity. Since this behavior is long
// established, we set the same permission for non-managed files
// This ensures consistent behavior between the Downloads root
// and other roots.
int flags = Intent.FLAG_GRANT_READ_URI_PERMISSION;
if (doc.isWriteSupported()) {
flags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
}
intent.setFlags(flags);

return intent;
}

上面代码中Intent的描述如下:

1
2
3
Intent { act=android.intent.action.VIEW dat=content://com.android.externalstorage.documents/document/primary:Turbo.apk typ=application/vnd.android.package-archive 
flg=0x800003
cmp=com.android.packageinstaller/.InstallStart }

满足条件的Activity为PackageInstaller中的com.android.packageinstaller.InstallStart.

安装开始

安装开始对应InstallStart这个类,这个类的onCreate()中没有调用setContentView()方法,没有可见的内容,用于选择安装过程中第一个可见的Activity,并把intent转给他。具体情况如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
    Intent nextActivity = new Intent(intent);
//多个Activity的值传递。ActivityA到达ActivityB再到达ActivityC,
//但ActivityB为过渡页可以finish了,此时ActivityC将值透传至ActivityA。
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);
//PackageInstallerSession已存在,此时Activity是从PackageInstallerSession的commitLocked方法中启动的
if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) {
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
Uri packageUri = intent.getData();
//从Android 7开始不能把file://类型的url传递给其他应用了
if (packageUri != null && (packageUri.getScheme().equals(ContentResolver.SCHEME_FILE)
|| packageUri.getScheme().equals(ContentResolver.SCHEME_CONTENT))) {
// Copy file to prevent it from being changed underneath this process
nextActivity.setClass(this, InstallStaging.class);
} else if (packageUri != null && packageUri.getScheme().equals(
PackageInstallerActivity.SCHEME_PACKAGE)) {
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
//……
}
}

if (nextActivity != null) {
startActivity(nextActivity);
}

在这里走的是packageUri.getScheme()为content这个分支 ,所以下个Activity为InstallStaging。

将apk暂存到临时文件

InstallStaging用于见apk复制到/data分区下的临时目录,然后将url进行转换并跳到DeleteStagedFileOnResult这个Activity。

转化后的url如下:

1
Intent { act=android.intent.action.VIEW dat=file:///data/user_de/0/com.android.packageinstaller/no_backup/package5143412883397554305.apk flg=0x2000000 cmp=com.android.packageinstaller/.DeleteStagedFileOnResult (has extras) }

此时的界面是这个样子的:

staging

临时文件的路径如下:

1
/data/user_de/0/com.android.packageinstaller/no_backup/package3066602231324109028.apk

DeleteStagedFileOnResult只是一个跳板用于跳转到PackageInstallerActivity,并在onActivityResult中删除临时文件。

用户确认 & 安装初始化

关于PackageInstallerActivity这个类,注释上是这么说的:当通过sideload(关于sideload的概念可以wiki一下)的方式安装应用时会启动这个类。首先对package进行解析,如果解析出错会弹框通知用户,也就是常见的包解析错误。解析成功后通知用户打开“安装未知应用”的设置,如果应用已存在,会通知用户确认是否替换当前应用。

看下该类的onCreate方法里的关键代码:

1
2
3
4
5
6
7
8
9
10
11
12
 protected void onCreate(Bundle icicle) {
//……
boolean wasSetUp = processPackageUri(packageUri);
if (!wasSetUp) {
return;
}

// load dummy layout with OK button disabled until we override this layout in
// startInstallConfirm
bindUi(R.layout.install_confirm, false);
checkIfAllowedAndInitiateInstall();
}

首先是处理packageUri,根据uri解析apk,得到PackageInfo,并获取AppSnippet,其中包含title和图标的信息。然后根据AppSnippet更新界面。之后检查是否允许安装应用并初始化安装过程,检查过程暂不关心,来看下安装的初始化过程,也就是initiateInstall方法的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private void initiateInstall() {
String pkgName = mPkgInfo.packageName;
// Check if there is already a package on the device with this name
// but it has been renamed to something else.
String[] oldName = mPm.canonicalToCurrentPackageNames(new String[] { pkgName });
if (oldName != null && oldName.length > 0 && oldName[0] != null) {
pkgName = oldName[0];
mPkgInfo.packageName = pkgName;
mPkgInfo.applicationInfo.packageName = pkgName;
}
// Check if package is already installed. display confirmation dialog if replacing pkg
try {
// This is a little convoluted because we want to get all uninstalled
// apps, but this may include apps with just data, and if it is just
// data we still want to count it as "installed".
mAppInfo = mPm.getApplicationInfo(pkgName,
PackageManager.MATCH_UNINSTALLED_PACKAGES);
if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
mAppInfo = null;
}
} catch (NameNotFoundException e) {
mAppInfo = null;
}

startInstallConfirm();
}

主要是检测是该应用是否已安装,然后进行安装确认。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  private void startInstallConfirm() {
// We might need to show permissions, load layout with permissions
if (mAppInfo != null) {
bindUi(R.layout.install_confirm_perm_update, true);
} else {
bindUi(R.layout.install_confirm_perm, true);
}
//……
if (mScrollView == null) {
// There is nothing to scroll view, so the ok button is immediately
// set to install.
mOk.setText(R.string.install);
mOkCanInstall = true;
} else {
mScrollView.setFullScrollAction(new Runnable() {
@Override
public void run() {
mOk.setText(R.string.install);
mOkCanInstall = true;
}
});
}
}

大部分是进行UI的设置,根据是安装新的应用还是更新已有应用界面会有所不同,bindUi()除了设置contentView外还会设置主要按键的listener,安装按键的事件处理如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (v == mOk) {
if (mOk.isEnabled()) {
if (mOkCanInstall || mScrollView == null) {
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, true);
finish();
} else {
startInstall();
}
} else {
mScrollView.pageScroll(View.FOCUS_DOWN);
}
}
}

如果安装正在进行则结束该安装,否则调用startInstal()进行安装。

1
2
3
4
5
6
7
8
9
10
11
12
 private void startInstall() {
// Start subactivity to actually install the application
Intent newIntent = new Intent();
newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
mPkgInfo.applicationInfo);
newIntent.setData(mPackageURI);
newIntent.setClass(this, InstallInstalling.class);
//……
newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
startActivity(newIntent);
finish();
}

主要是在进行intent的构建,主要包含applicationInfo和URI等信息。然后跳转到InstallInstalling,这里又看到了FLAG_ACTIVITY_FORWARD_RESULT这个flag,所以InstallInstalling的结果会传给PackageInstallerActivity前一个activity,也就是DeleteStagedFileOnResult。

安装进行中

InstallInstalling负责将package的信息传递给package manager并处理package manager传回的结果,如果安装成功启动InstallSuccess,失败则跳转到InstallFailed.

先来看下InstallInstalling的onCreate()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.install_installing);

ApplicationInfo appInfo = getIntent()
.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
mPackageURI = getIntent().getData();

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.initSnippetForNewApp(this, PackageUtil.getAppSnippet(this, appInfo,
sourceFile), R.id.app_snippet);//获取应用label

if (savedInstanceState != null) {
//……
} else {
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
params.installFlags = PackageManager.INSTALL_FULL_APP;
params.referrerUri = getIntent().getParcelableExtra(Intent.EXTRA_REFERRER);
params.originatingUri = getIntent()
.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
params.originatingUid = getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
UID_UNKNOWN);
params.installerPackageName =
getIntent().getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME);

File file = new File(mPackageURI.getPath());
try {
PackageParser.PackageLite pkg = PackageParser.parsePackageLite(file, 0);
params.setAppPackageName(pkg.packageName);
params.setInstallLocation(pkg.installLocation);
params.setSize(
PackageHelper.calculateInstalledSize(pkg, false, params.abiOverride));
} catch (PackageParser.PackageParserException e) {
params.setSize(file.length());
} catch (IOException e) {
params.setSize(file.length());
}
try {
mInstallId = InstallEventReceiver
.addObserver(this, EventResultPersister.GENERATE_NEW_ID,
this::launchFinishBasedOnResult);
} catch (EventResultPersister.OutOfIdsException e) {
launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
}

try {
mSessionId = getPackageManager().getPackageInstaller().createSession(params);
} catch (IOException e) {
launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
}
}
//……
mSessionCallback = new InstallSessionCallback();
}
}

这里只关心安装新应用的情况,主要做了四件事:

  • 构建Session的参数PackageInstaller.SessionParams;
  • 添加EventResultObserver到InstallEventReceiver, this::launchFinishBasedOnResult为lambda表达式,表示一个在onResult()方法中调用launchFinishBasedOnResult()方法的EventResultObserver对象;
  • 创建会话mSessionId = getPackageManager().getPackageInstaller().createSession(params);
  • 创建回调mSessionCallback = new InstallSessionCallback();这个监听器中只提供了onProgressChanged()方法的具体实现,用于更新安装进度。

onStart()方法:

1
2
3
4
5
protected void onStart() {
super.onStart();

getPackageManager().getPackageInstaller().registerSessionCallback(mSessionCallback);
}

注册mSessionCallback,实际上是添加到PackageInstallerService的mCallbacks中,在上面创建PackageInstallerSession时PackageInstallerService的回调mCallbacks会传递给PackageInstallerSession,session的进度更新时会进行回调。这里看起来很简单是由一行代码,但是涉及到了binder通信和handler等内容,以后对binder有更多了解时可以对这里再深入了解下。

onResume方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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);

if (sessionInfo != null && !sessionInfo.isActive()) {
mInstallingTask = new InstallingAsyncTask();
mInstallingTask.execute();
} else {
// we will receive a broadcast when the install is finished
mCancelButton.setEnabled(false);
setFinishOnTouchOutside(false);
}
}
}

创建并执行InstallingAsyncTask,该任务在后台将apk复制到/data/app目录下,具体路径为:/data/app/vmdl685420279.tmp/PackageInstaller。onPostExecute()方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
protected void onPostExecute(PackageInstaller.Session session) {
if (session != null) {
Intent broadcastIntent = new Intent(BROADCAST_ACTION);
broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
broadcastIntent.setPackage(
getPackageManager().getPermissionControllerPackageName());
broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId);

PendingIntent pendingIntent = PendingIntent.getBroadcast(
InstallInstalling.this,
mInstallId,
broadcastIntent,
PendingIntent.FLAG_UPDATE_CURRENT);

session.commit(pendingIntent.getIntentSender());
mCancelButton.setEnabled(false);
setFinishOnTouchOutside(false);
} else {
getPackageManager().getPackageInstaller().abandonSession(mSessionId);

if (!isCancelled()) {
launchFailure(PackageManager.INSTALL_FAILED_INVALID_APK, null);
}
}
}

创建一个广播的PendingIntent并传递给PackageInstaller.Session的commit方法,用于接收应用安装的结果。

安装完成

在安装完成后发送广播给之前PendingIntent对应的组件。该组件为PackageInstaller中的InstallEventReceiver。而在其onReceive()方法中会调用SparseArray mObservers中EventResultObserver对象的onResult()方法,也就是前面onCreate()方法中注册的observer的onResult()方法,进而会调用到launchFinishBasedOnResult()方法:

1
2
3
4
5
6
7
private void launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage) {
if (statusCode == PackageInstaller.STATUS_SUCCESS) {
launchSuccess();
} else {
launchFailure(legacyStatus, statusMessage);
}
}

安装成功后跳转到InstallSuccess,失败后跳转到InstallFailed,这里只看success的情况:

1
2
3
4
5
6
7
8
private void launchSuccess() {
Intent successIntent = new Intent(getIntent());
successIntent.setClass(this, InstallSuccess.class);
successIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);

startActivity(successIntent);
finish();
}

InstallSuccess主要是提示用户安装成功,用户确认后由于FLAG_ACTIVITY_FORWARD_RESULT会一路回调到DeleteStagedFileOnResult的onActivityResult方法删除临时文件.

Powered by Hexo and Hexo-theme-hiker

Copyright © 2018 - 2022 得一 All Rights Reserved.

访客数 : | 访问量 :