需求
实现静默安装和静默卸载功能
基于这个需求,首先需要了解的是PMS相关逻辑,本文从安装apk 为切入点,先了解界面跳转逻辑
源码位置
PackageInstaller 源码在线查看
MTK 平台位置,源码位置:
frameworks\base\packages\PackageInstaller\src\com\android\packageinstaller
相关资料博客推荐
PackageInstaller介绍
PackageManager简介
与ActivityManager和AMS的关系类似,PMS也有一个对应的管理类PackageManager,用于向应用程序进程提供一些功能。PackageManager是一个抽象类,它的具体实现类为ApplicationPackageManager,ApplicationPackageManager中的方法会通过IPackageManager与AMS进行进程间通信,因此PackageManager所提供的功能最终是由PMS来实现的,这么设计的主要用意是为了避免系统服务PMS直接被访问。PackageManager提供了一些功能,主要有以下几点:
- 获取一个应用程序的所有信息(ApplicationInfo)。
- 获取四大组件的信息。
- 查询permission相关信息。
- 获取包的信息。
- 安装、卸载APK.
Apk安装方式
APK的安装场景主要有以下几种:
- 通过adb命令安装:adb 命令包括adb push/install
- 用户下载的Apk,通过系统安装器packageinstaller安装该Apk。packageinstaller是系统内置的应用程序,用于安装和卸载应用程序。
- 系统开机时安装系统应用。
- 电脑或者手机上的应用商店自动安装。
这4种方式最终都是由PMS来进行处理,在此之前的调用链是不同的,本篇文章会介绍第二种方式,对于用户来说,这是比较常用的安装方式;对于开发者来说,这是调用链比较长的安装方式,能学到的更多内容。
PackageInstaller入口 源码分析
PackageInstaller 进入之前的安装代码
本地有apk 点击或者执行代码进行安装:
private String authority;
private static final String intentType = "application/vnd.android.package-archive";
String applicationID = mContext.getPackageName();
authority = applicationID + ".fileProvider";
Intent intent = new Intent(Intent.ACTION_VIEW);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Uri contentUri = FileProvider.getUriForFile(mContext, authority, apkFile);
intent.setDataAndType(contentUri, intentType);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else {
intent.setDataAndType(Uri.parse("file://" + apkFile.toString()), intentType);
}
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(xxxxx, "application/vnd.android.package-archive");
//FileProvider,FileProvider继承自ContentProvider ,使用它可以将file://Uri替换为content://Uri
PackageInstaller AndroidManifest.xml
packages/apps/PackageInstaller/AndroidManifest.xml
<activity android:name=".InstallStart"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
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="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>
核心点:
<data android:scheme="content" />
<data android:mimeType="application/vnd.android.package-archive" />
这里得出结论:
InstallStart是PackageInstaller中的入口Activity,其中PackageInstaller是系统内置的应用程序,用于安装和卸载应用。当我们调用PackageInstaller来安装应用时会跳转到InstallStart,并调用它的onCreate方法:
packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
PackageInstaller InstallStart
我们看onCreate 方法,添加了部分注释
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
/*
intent的action是否等于PackageInstaller.ACTION_CONFIRM_INSTALL来判断是否是
PackageInstaller session安装,如果是,会去intent中取出SESSION_ID,并且得到SessionInfo信息,并从中得到callingPackage、callingAttributionTag。当前Activity是运行在安装应用进程中,PackageInstaller session是在系统进程中。在系统进程中,如果检测到权限不够,需要用户来确认,这时,会启动InstallStart以便进入用户确认界面。
*/
final boolean isSessionInstall =
PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction());
// If the activity was started via a PackageInstaller session, we retrieve the calling
// package from that session
final int sessionId = (isSessionInstall
? intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1)
: -1);
if (callingPackage == null && sessionId != -1) {
PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
PackageInstaller.SessionInfo sessionInfo = packageInstaller.getSessionInfo(sessionId);
callingPackage = (sessionInfo != null) ? sessionInfo.getInstallerPackageName() : null;
callingAttributionTag =
(sessionInfo != null) ? sessionInfo.getInstallerAttributionTag() : null;
}
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);
if (targetSdkVersion < 0) {
Log.w(LOG_TAG, "Cannot get target sdk version for uid " + originatingUid);
// Invalid originating uid supplied. Abort install.
mAbortInstall = true;
} else if (targetSdkVersion >= Build.VERSION_CODES.O && !isUidRequestingPermission(
originatingUid, Manifest.permission.REQUEST_INSTALL_PACKAGES)) {
Log.e(LOG_TAG, "Requesting uid " + originatingUid + " needs to declare permission "
+ Manifest.permission.REQUEST_INSTALL_PACKAGES);
mAbortInstall = true;
}
}
if (mAbortInstall) {
setResult(RESULT_CANCELED);
finish();
return;
}
Intent nextActivity = new Intent(intent);
nextActivity.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT
| Intent.FLAG_GRANT_READ_URI_PERMISSION);
...
//若是是PackageInstaller session安装
if (isSessionInstall) {
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
Uri packageUri = intent.getData();
//如果得到的URI的getScheme()是ContentResolver.SCHEME_CONTENT,则会跳转到InstallStagin
if (packageUri != null && packageUri.getScheme().equals(
ContentResolver.SCHEME_CONTENT)) {
nextActivity.setClass(this, InstallStaging.class);
} else if (packageUri != null && packageUri.getScheme().equals(
PackageInstallerActivity.SCHEME_PACKAGE)) {
//如果得到的URI的getScheme()是PackageInstallerActivity.SCHEME_PACKAGE,则会跳转到PackageInstallerActivity中
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
//如果不是以上情况,则返回PackageManager.INSTALL_FAILED_INVALID_URI给调用者Activity。最后就是跳转,并且关闭InstallStart
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();
}
跳转逻辑:
判断是否勾选“未知来源”选项,若未勾选跳转到设置安装未知来源界面
对于大于等于 Android 8.0 版本,会先检查是否申请安装权限,若没有则中断安装
判断 Uri 的 Scheme 协议,若是 content 则调用 InstallStaging, 若是 package 则调用 PackageInstallerActivity
其中第二点就关联到了
Uri contentUri = FileProvider.getUriForFile(mContext, authority, apkFile);
FileProvider 本身就是Content 的子类,为什么这么说,分析如下:
FileProvider继承自ContentProvider ,使用它可以将file://Uri替换为content://Uri,具体怎么使用FileProvider并不是本文的重点,但是简要接着分析。
在跳转安装的时候,如上代码:
Uri contentUri = FileProvider.getUriForFile(mContext, authority, apkFile);
@Override
public Uri getUriForFile(File file) {
String path;
try {
path = file.getCanonicalPath();
} catch (IOException e) {
throw new IllegalArgumentException("Failed to resolve canonical path for " + file);
}
// Find the most-specific root path
Map.Entry<String, File> mostSpecific = null;
for (Map.Entry<String, File> root : mRoots.entrySet()) {
final String rootPath = root.getValue().getPath();
if (path.startsWith(rootPath) && (mostSpecific == null
|| rootPath.length() > mostSpecific.getValue().getPath().length())) {
mostSpecific = root;
}
}
if (mostSpecific == null) {
throw new IllegalArgumentException(
"Failed to find configured root that contains " + path);
}
// Start at first char of path under root
final String rootPath = mostSpecific.getValue().getPath();
if (rootPath.endsWith("/")) {
path = path.substring(rootPath.length());
} else {
path = path.substring(rootPath.length() + 1);
}
// Encode the tag and path separately
path = Uri.encode(mostSpecific.getKey()) + '/' + Uri.encode(path, "/");
return new Uri.Builder().scheme("content")
.authority(mAuthority).encodedPath(path).build();
}
返回的Uri 里面,构造了scheme:“content”
PackageInstaller InstallStaging
packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
copy uri 传递过来的apk 数据
@Override
protected void onResume() {
super.onResume();
// This is the first onResume in a single life of the activity
if (mStagingTask == null) {
// File does not exist, or became invalid
if (mStagedFile == null) {
// Create file delayed to be able to show error
try {
// 创建临时文件 mStagedFile 用来存储数据
mStagedFile = TemporaryFileManager.getStagedFile(this);
} catch (IOException e) {
showError();
return;
}
}
mStagingTask = new StagingAsyncTask();
// 启动 StagingAsyncTask,并传入了content协议的Uri
mStagingTask.execute(getIntent().getData());
}
}
StagingAsyncTask 开启了一个线程:
创建临时文件 mStagedFile 用来存储数据
启动 StagingAsyncTask,并传入了 content 协议的 Uri
private final class StagingAsyncTask extends AsyncTask<Uri, Void, Boolean> {
@Override
protected Boolean doInBackground(Uri... params) {
if (params == null || params.length <= 0) {
return false;
}
Uri packageUri = params[0];
try (InputStream in = getContentResolver().openInputStream(packageUri)) {
// Despite the comments in ContentResolver#openInputStream the returned stream can
// be null.
if (in == null) {
return false;
}
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 这个Activity是为了跳入PackageInstallerActivity,然后等待返回结果,收到返回结果之后,然后删除前面生成的安装文件。
这里可以看到在doInBackground(Uri… params)中实现了,文件的复制。
在onPostExecute(Boolean success)中,根据复制结果来做下一步。如果复制成功,会跳入DeleteStagedFileOnResult这个Activity中。在这里会设置跳转Intent的mData成员变量
进入DeleteStagedFileOnResult 这个Activity是为了跳入PackageInstallerActivity,然后等待返回结果,收到返回结果之后,然后删除前面生成的安装文件。
*/
// Now start the installation again from a file
Intent installIntent = new Intent(getIntent());
installIntent.setClass(InstallStaging.this, DeleteStagedFileOnResult.class);
installIntent.setData(Uri.fromFile(mStagedFile));
if (installIntent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
installIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
}
installIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivity(installIntent);
InstallStaging.this.finish();
} else {
showError();
}
}
}
doInBackground 方法中将 packageUri(content 协议的 Uri)的内容写入到 mStagedFile 中
PackageInstaller DeleteStagedFileOnResult
跳转PackageInstallerActivity 等待结果后删除生成的Path 对应的file
/**
* Trampoline activity. Calls PackageInstallerActivity and deletes staged install file onResult.
*/
public class DeleteStagedFileOnResult extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
Intent installIntent = new Intent(getIntent());
installIntent.setClass(this, PackageInstallerActivity.class);
installIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivityForResult(installIntent, 0);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
setResult(resultCode, data);
finish();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (isFinishing()) {
File sourceFile = new File(getIntent().getData().getPath());
new Thread(sourceFile::delete).start();
}
}
}
PackageInstaller PackageInstallerActivity
从功能上来说,PackageInstallerActivity才是应用安装器PackageInstaller真正的入口Activity,PackageInstallerActivity的onCreate方法如下所示。
packages/apps/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
主要任务
- 显示安装界面
- 初始化安装需要用的各种对象,比如
PackageManager、IPackageManager、AppOpsManager、UserManager、PackageInstaller
等等 - 根据传递过来的 Scheme 协议做不同的处理
- 检查是否允许、初始化安装
- 在准备安装的之前,检查应用列表判断该应用是否已安装,若已安装则提示该应用已安装,由用户决定是否替换
- 在安装界面,提取出 APK 中权限信息并展示出来
- 点击 OK 按钮确认安装后,会调用 startInstall 开始安装工作
onCreate
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 方法
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;
default: {
throw new IllegalArgumentException("Unexpected URI scheme " + packageUri);
}
}
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 方法
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();
}
}
}
主要检查安装应用程序的用户限制,当 APK 文件不对或者安装有限制则调用 showDialogInner 方法,弹出 dialog 提示用户,显示相应的错误信息,来看一下都有那些错误信息
如果用户允许安装未知来源,会调用 initiateInstall 方法
initiateInstall
private void initiateInstall() {
String pkgName = mPkgInfo.packageName;
// 检查设备上是否存在相同包名的APK
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;
}
// 检查package是否已安装, 如果已经安装则显示对话框提示用户是否替换。
try {
mAppInfo = mPm.getApplicationInfo(pkgName,
PackageManager.MATCH_UNINSTALLED_PACKAGES);
if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
mAppInfo = null;
}
} catch (NameNotFoundException e) {
mAppInfo = null;
}
// 初始化确认安装界面
startInstallConfirm();
}
根据包名获取应用程序的信息,调用 startInstallConfirm 方法初始化安装确认界面后,当用户点击确认按钮之后发生了什么,接着查看确认按钮点击事件
bindUi
private void bindUi() {
...
// 点击确认按钮,安装APK
mAlert.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.install),
(ignored, ignored2) -> {
if (mOk.isEnabled()) {
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, true);
finish();
} else {
// 启动Activity来完成应用的安装
startInstall();
}
}
}, null);
// 点击取消按钮,取消此次安装
mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.cancel),
(ignored, ignored2) -> {
// Cancel and finish
setResult(RESULT_CANCELED);
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, false);
}
finish();
}, null);
setupAlert();
mOk = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
mOk.setEnabled(false);
}
当用户点击确认按钮调用了 startInstall 方法,启动子 Activity 完成 APK 的安装
startInstall
private void startInstall() {
// 启动子Activity来完成应用的安
Intent newIntent = new Intent();
newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
mPkgInfo.applicationInfo);
newIntent.setData(mPackageURI);
newIntent.setClass(this, InstallInstalling.class);
...
if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI);
startActivity(newIntent);
finish();
}
总结
PackageInstaller初始化的过程
- 根据Uri的Scheme协议不同,跳转到不同的界面,content协议跳转到InstallStart,其他的跳转到PackageInstallerActivity。本文应用场景中,会跳转到InstallStart。
- InstallStart将content协议的Uri转换为File协议,然后跳转到PackageInstallerActivity。
- PackageInstallerActivity会分别对package协议和file协议的Uri进行处理,如果是file协议会解析APK文件得到包信息PackageInfo。
- PackageInstallerActivity中会对未知来源进行处理,如果允许安装未知来源或者根据Intent判断得出该APK不是未知来源,就会初始化安装确认界面,如果管理员限制来自未知源的安装,就弹出提示Dialog或者跳转到设置界面。
UI角度 下载安装流程
- 根据根据 Uri 的 Scheme 找到入口 InstallStart
- InstallStart 根据 Uri 的 Scheme 协议不同做不同的处理
- 都会调用 PackageInstallerActivity, 然后分别对package协议和 file 协议的 Uri 进行处理
- PackageInstallerActivity 检查未知安装源限制,如果安装源限制弹出提示 Dialog
点击 OK 按钮确认安装后,会调用 startInstall 开始安装工作
扩展分析
这里我们阅读了系统真正安装的前面部分的逻辑和UI跳转业务,后面我们关注安装的业务,如何安装成功apk的。