开篇
核心源码
| 关键类 | 路径 |
|---|---|
| PackageInstaller.java | frameworks/base/core/java/android/content/pm/PackageInstaller.java |
| InstallStart.java | packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallStart.java |
| InstallStaging.java | packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java |
| PackageInstallerActivity.java | packages/apps/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java |
PackageManager 简介
与 ActivityManager 和 AMS 的关系类似,PMS 也有一个对应的管理类 PackageManager ,用于向应用程序进程提供一些功能。
PackageManager 是一个抽象类,它的具体实现类为 ApplicationPackageManager ,ApplicationPackageManager 中的方法会通过 IPackageManager 与 PMS 进行进程间通信,因此 PackageManager 所提供的功能最终是由 PMS 来实现的,这么设计的主要用意是为了避免系统服务 PMS 直接被访问。
/**
* Class for retrieving various kinds of information related to the application
* packages that are currently installed on the device.
*
* You can find this class through {@link Context#getPackageManager}.
*/
public abstract class PackageManager {}
/** @hide */
public class ApplicationPackageManager extends PackageManager {}
PackageManager 提供了一些功能,主要有以下几点:
✨ 1、获取一个应用程序的所有信息(ApplicationInfo)。
✨ 2、获取四大组件的信息。
✨ 3、查询 permission 相关信息。
✨ 4、获取包的信息。
✨ 5、安装、卸载 APK。
APK 文件结构和安装方式
APK 是 AndroidPackage 的缩写,即 Android 安装包,它实际上是 zip 格式的压缩文件,一般情况下,解压后的文件结构如下表所示。
| 目录/文件 | 描述 |
|---|---|
| assert | 存放的原生资源文件,通过 AssetManager 类访问。 |
| lib | 存放库文件。 |
| META-INF | 保存应用的签名信息,签名信息可以验证 APK 文件的完整性。 |
| res | 存放资源文件。res 中除了 raw 子目录,其他的子目录都参与编译,这些子目录下的资源是通过编译出的 R 类在代码中访问。 |
| AndroidManifest.xml | 用来声明应用程序的包名称、版本、组件和权限等数据。 apk 中的 AndroidManifest.xml 经过压缩,可以通过 AXMLPrinter2 工具解开。 |
| classes.dex | Java 源码编译后生成的 Java 字节码文件。 |
| resources.arsc | 编译后的二进制资源文件。 |
APK 的安装场景
目前,安装 APK 的场景主要分为以下四种:
✨ 1、通过 adb 命令安装:adb 命令包括 adb push/install
✨ 2、用户下载的 Apk,通过系统安装器 packageinstaller 安装该 Apk。packageinstaller 是系统内置的应用程序,用于安装和卸载应用程序。
✨ 3、系统开机时安装系统应用。
✨ 4、电脑或者手机上的应用商店自动安装。
这 4 种方式最终都是由 PMS 来进行处理,在此之前的调用链是不同的,我会介绍第二种方式,因为对于大部分用户来说,这是比较常用的安装方式;对于开发者来说,这也是调用链比较长的安装方式(利于我们分析源码)。
PackageInstaller 初始化
从 Android 8.0 开始我们可以通过如下代码安装指定路径中的 APK:
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(xxxxx, "application/vnd.android.package-archive");
Intent 的 Action 属性为 ACTION_VIEW,Type 属性指定 Intent 的数据类型为 application/vnd.android.package-archive。能隐式匹配的 Activity 为 InstallStart。
<activity android:name=".InstallStart"
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
InstallStart 是 PackageInstaller 中的入口 Activity,其中 PackageInstaller 是系统内置的应用程序,用于安装和卸载应用。
当我们调用 PackageInstaller 来安装应用时会跳转到 InstallStart,并调用它的 onCreate 方法:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mIPackageManager = AppGlobals.getPackageManager();
Intent intent = getIntent();
String callingPackage = getCallingPackage();
... ...
/**
* public static final String ACTION_CONFIRM_PERMISSIONS = "android.content.pm.action.CONFIRM_PERMISSIONS";
* 判断 Intent 的 Action 是否为 CONFIRM_PERMISSIONS ;
*/
if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) {
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
Uri packageUri = intent.getData();
// 判断 Uri 的 Scheme 协议是否是 content
if (packageUri != null && (packageUri.getScheme().equals(ContentResolver.SCHEME_FILE)
|| packageUri.getScheme().equals(ContentResolver.SCHEME_CONTENT))) {
// 跳转到 InstallStaging (Android 8.0)
nextActivity.setClass(this, InstallStaging.class);
} else if (packageUri != null && packageUri.getScheme().equals(
PackageInstallerActivity.SCHEME_PACKAGE)) {
// 跳转到 PackageInstallerActivity(Android 7.0)
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
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();
}
InstallStaging
我们是基于 Android 8.0 的代码进行的分析,所以会走到 InstallStaging 分支,我们继续看源码:
@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 {
// 如果 File 类型的 mStagedFile 为 null,则创建 mStagedFile,mStagedFile 用于存储临时数据
mStagedFile = TemporaryFileManager.getStagedFile(this);
} catch (IOException e) {
showError();
return;
}
}
mStagingTask = new StagingAsyncTask();
// 启动 StagingAsyncTask 线程,并传入了 content 协议的 Uri
mStagingTask.execute(getIntent().getData());
}
}
StagingAsyncTask
接下来,我们看下这个线程所做的工作:
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;
}
// 将 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 e) {
Log.w(LOG_TAG, "Error staging apk from content URI", e);
return false;
}
return true;
}
@Override
protected void onPostExecute(Boolean success) {
if (success) {
// Now start the installation again from a file
Intent installIntent = new Intent(getIntent());
// 如果写入成功,跳转到 PackageInstallerActivity
installIntent.setClass(InstallStaging.this, PackageInstallerActivity.class);
// 并将 mStagedFile 传进去
installIntent.setData(Uri.fromFile(mStagedFile));
installIntent
.setFlags(installIntent.getFlags() & ~Intent.FLAG_ACTIVITY_FORWARD_RESULT);
installIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivityForResult(installIntent, 0);
} else {
showError();
}
}
}
doInBackground 方法中将 packageUri(content 协议的 Uri)的内容写入到 mStagedFile 中,如果写入成功,onPostExecute 方法中会跳转到 PackageInstallerActivity 中,并将 mStagedFile 传进去。
绕了一圈又回到了 PackageInstallerActivity,这里可以看出 InstallStaging 主要起了转换的作用,将 content 协议的 Uri 转换为 File 协议,然后跳转到 PackageInstallerActivity,接下来的安装流程就和 Android 7.0 一样了。
PackageInstallerActivity
从功能上来说,PackageInstallerActivity 才是应用安装器 PackageInstaller 真正的入口 Activity,我们查看它的 onCreate 方法:
onCreate
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
if (icicle != null) {
mAllowUnknownSources = icicle.getBoolean(ALLOW_UNKNOWN_SOURCES_KEY);
}
// 初始话 PackageManager 对象:用于向应用程序进程提供一些功能,最终的功能是由 PMS 来实现的;
mPm = getPackageManager();
// 初始话 IPackageManager 对象:一个AIDL的接口,用于和 PMS 进行进程间通信;
mIpm = AppGlobals.getPackageManager();
// 初始化 AppOpsManager 对象:用于权限动态检测,在,Android 4.3 中被引入;
mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
// 初始化 PackageInstaller 对象:提供安装、升级和删除应用程序功能;
mInstaller = mPm.getPackageInstaller();
// 初始化 UserManager 对象:用于多用户管理;
mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
... ...
final Uri packageUri;
if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) {
final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
final PackageInstaller.SessionInfo info = mInstaller.getSessionInfo(sessionId);
if (info == null || !info.sealed || info.resolvedBaseCodePath == null) {
Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
finish();
return;
}
mSessionId = sessionId;
packageUri = Uri.fromFile(new File(info.resolvedBaseCodePath));
mOriginatingURI = null;
mReferrerURI = null;
} else {
mSessionId = -1;
packageUri = intent.getData();
mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
}
... ...
// 根据 Uri 的 Scheme 进行预处理
boolean wasSetUp = processPackageUri(packageUri); // 💥 💥 💥 💥 💥 💥
if (!wasSetUp) {
return;
}
bindUi(R.layout.install_confirm, false);
// 判断是否是未知来源的应用,如果开启允许安装未知来源选项则直接初始化安装
checkIfAllowedAndInitiateInstall(); // 💥 💥 💥 💥 💥 💥
}
processPackageUri
我们首先来看看 processPackageUri 所做的工作:
/**
* Parse the Uri and set up the installer for this package.
*
* @param packageUri The URI to parse
*
* @return {@code true} iff the installer could be set up
*/
private boolean processPackageUri(final Uri packageUri) {
mPackageURI = packageUri;
// 得到 packageUri 的 Scheme 协议
final String scheme = packageUri.getScheme();
// 根据这个 Scheme 协议分别对 package 协议和 file 协议进行处理
switch (scheme) {
case SCHEME_PACKAGE: {
try {
mPkgInfo = mPm.getPackageInfo(packageUri.getSchemeSpecificPart(),
PackageManager.GET_PERMISSIONS
| PackageManager.MATCH_UNINSTALLED_PACKAGES);
} catch (NameNotFoundException e) {
}
... ...
} break;
case ContentResolver.SCHEME_FILE: {
// 根据 packageUri 创建一个新的 File
File sourceFile = new File(packageUri.getPath());
// 得到 sourceFile 的包信息
PackageParser.Package parsed = PackageUtil.getPackageInfo(this, sourceFile);
// Check for parse errors
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;
}
// 对 parsed 进行进一步处理得到包信息 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;
}
checkIfAllowedAndInitiateInstall
接下来我们看下 checkIfAllowedAndInitiateInstall 做所工作:
/**
* Check if it is allowed to install the package and initiate install if allowed. If not allowed
* show the appropriate dialog.
*/
private void checkIfAllowedAndInitiateInstall() {
// Check for install apps user restriction first.
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 {
// Check for unknown sources restriction
final int unknownSourcesRestrictionSource = mUserManager.getUserRestrictionSource(
UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, Process.myUserHandle());
// 如果管理员限制来自未知源的安装, 就弹出提示 Dialog 或者跳转到设置界面
if ((unknownSourcesRestrictionSource & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) {
showDialogInner(DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER);
} else if (unknownSourcesRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
finish();
} else {
handleUnknownSources();
}
}
}
initiateInstall
// 判断如果允许安装未知来源或者根据Intent判断得出该APK不是未知来源
private void initiateInstall() {
// 得到包名
String pkgName = mPkgInfo.packageName;
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;
}
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
private void startInstallConfirm() {
// 初始化界面相关代码
... ...
// 创建 AppSecurityPermissions,它会提取出 APK 中权限信息并展示出来,
// 这个负责展示的 View 是 AppSecurityPermissions 的内部类 PermissionItemView
AppSecurityPermissions perms = new AppSecurityPermissions(this, mPkgInfo);
final int N = perms.getPermissionCount(AppSecurityPermissions.WHICH_ALL);
if (mAppInfo != null) {
msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
? R.string.install_confirm_question_update_system
: R.string.install_confirm_question_update;
mScrollView = new CaffeinatedScrollView(this);
mScrollView.setFillViewport(true);
boolean newPermissionsFound = false;
if (!supportsRuntimePermissions) {
newPermissionsFound =
(perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW) > 0);
if (newPermissionsFound) {
permVisible = true;
// 调用 AppSecurityPermissions 的 getPermissionsView 方法来获取 PermissionItemView,
// 并将 PermissionItemView 添加到 CaffeinatedScrollView 中,
// 这样安装该APK需要访问的系统权限就可以全部的展示出来了
mScrollView.addView(perms.getPermissionsView(
AppSecurityPermissions.WHICH_NEW));
}
}
... ...
}
... ...
}
总结
PackageInstaller 初始化的过程:
✨ 1、根据 Uri 的 Scheme 协议不同,跳转到不同的界面,content 协议跳转到 InstallStart,其他的跳转到 PackageInstallerActivity。本文应用场景中,如果是 Android7.0 以及更高版本会跳转到 InstallStart。
✨ 2、InstallStart 将 content 协议的 Uri 转换为 File 协议,然后跳转到 PackageInstallerActivity。
✨ 3、PackageInstallerActivity 会分别对 package 协议和 file 协议的 Uri 进行处理,如果是 file 协议会解析 APK 文件得到包信息 PackageInfo。
✨ 4、PackageInstallerActivity 中会对未知来源进行处理,如果允许安装未知来源或者根据 Intent 判断得出该 APK 不是未知来源,就会初始化安装确认界面,如果管理员限制来自未知源的安装, 就弹出提示 Dialog 或者跳转到设置界面。