开篇
PackageManagerService 系列文章如下(基于 Android 9.0 源码)
? Framework 核心服务之 PackageManagerService 钻研(1)- 启动流程
? Framework 核心服务之 PackageManagerService 钻研(2)- 构造函数
? Framework 核心服务之 PackageManagerService 钻研(3)- PackageManager
? Framework 核心服务之 PackageManagerService 钻研(4)- PackageInstaller
? Framework 核心服务之 PackageManagerService 钻研(5)- APK 安装流程(PackageInstaller)
? Framework 核心服务之 PackageManagerService 钻研(6)- APK 安装流程(PMS)
? Framework 核心服务之 PackageManagerService 钻研(7)- PackageParser
核心源码
关键类 | 路径 |
---|---|
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 简介
在详细讲解包管理机制如何安装 APK 之前,我们需要了解 PackageManager 。
与 ActivityManagerService(AMS) 和 ActivityManager 的关系类似,PackageManagerService(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}.
*/
// PackageManager 是一个抽象类
public abstract class PackageManager {}
/** @hide */
// ApplicationPackageManager 继承自 PackageManager
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。
✨ 3、系统开机时安装系统应用。
✨ 4、电脑或者手机上的应用商店自动安装。
其实,这4种方式最终都是由 PMS 来进行处理,只是在此之前的调用链是不同的。我们在接下来的分析中,会选择第二种方式,因为对于开发者来说,这是调用链比较长的安装方式(利于我们分析源码)。
我们看下这种安装方式的 实际操作图 :(后面我们分析的代码流程会紧密联系着这几张图,大家可以对比理解!)
APK 安装相关目录
目录/文件 | 描述 |
---|---|
/system/app | 系统自带的应用程序,获得 adb root 权限才能删除。 |
/data/app | 用户程序安装的目录,安装时把 apk 文件复制到此目录。 |
/data/data | 存放应用程序的数据。 |
/data/dalvik-cache | 将 apk 中的 dex 文件安装到 dalvik-cache 目录下(dex 文件是 dalvik 虚拟机的可执行文件,当然,ART-Android Runtime 的可执行文件格式为 .oat ,启动 ART 时,系统会执行 dex 文件转换至 oat 文件)。 |
/data/system | 该目录下的 packages.xml 文件类似于 Window 的注册表,这个文件是解析 apk 时由 writeLP() 创建的,里面记录了系统的 permissons ,以及每个 apk 的 name,codePath,flag,ts,version ,userid 等信息,这些信息主要通过 apk 的 AndroidManifest 解析获取,解析完 apk 后将更新信息写入这个文件并保存到 flash ,下次开机的时候直接从里面读取相关信息并添加到内存相关列表中。当有 apk 升级,安装或删除时会更新这个文件。 |
/data/system/package.xml | 包含了该应用申请的权限、签名和代码所在的位置等信息系,并且两者都有同一个userld。之所以每个应用都要一个userId,是因为Android在系统设计上把每个应用当做Linux系统上的一个用户对待,这样就可以利用已有的Linux用户管理机制来设计Android应用,比如应用目录,应用权限,应用进程管理等。 |
/data/system/package.list | 指定了应用的默认存储位置/data/data/com.xxx.xxx。 |
PackageInstaller 初始化
首先,我们需要指出的是:从 Android 8.0 开始系统是通过如下代码安装指定路径中的 APK:
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse("file://"
+ apkfile.toString()), "application/vnd.android.package-archive");
Intent 的 Action 属性为 ACTION_VIEW,Type 属性指定 Intent 的数据类型为 application/vnd.android.package-archive。
"application/vnd.android.package-archive" 是什么?
final String[][] MIME_MapTable={
//{后缀名,MIME类型}
{".3gp", "video/3gpp"},
{".apk", "application/vnd.android.package-archive"},
{".asf", "video/x-ms-asf"},
{".avi", "video/x-msvideo"},
... ...
我们发现 "application/vnd.android.package-archive" 其实就是文件类型,具体对应 apk 类型。
那么这个 Intent 隐式匹配的 Activity 是什么?这边我们直接告诉你:InstallStart!(其实,7.0能隐式匹配的 Activity 为 PackageInstallerActivity ,两者的区别我们后面会讲解到!)。
// packages/apps/PackageInstaller/AndroidManifest.xml
<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 来安装应用时会跳转到 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();
// 保证 packageUri 不为 null,判断 packageUri 的 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) {
// 启动相应的 Activity(InstallStaging、PackageInstallerActivity)
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) {
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());
}
}
我们可以看到, InstallStaging 主要做了两部分工作 :
✨ 1、判断 mStagingTask 是否为空,主要用于存储临时数据;
✨ 2、创建并启动 StagingAsyncTask 线程。
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)) {
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) {
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) {
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 !从功能上来说, PackageInstallerActivity 才是应用安装器 PackageInstaller 真正的入口 Activity 。
我们看下官方对于这个类的说明:
/**
* This activity is launched when a new application is installed via side loading
* The package is first parsed and the user is notified of parse errors via a dialog.
* If the package is successfully parsed, the user is notified to turn on the install unknown
* applications setting. A memory check is made at this point and the user is notified of out
* of memory conditions if any. If the package is already existing on the device,
* a confirmation dialog (to replace the existing package) is presented to the user.
* Based on the user response the package is then installed by launching InstallAppConfirm
* sub activity. All state transitions are handled in this activity
*/
/**
* 当通过渠道安装一个应用程序的时候,会启动这个 Activity。
* 如果在首次解析这个安装包的时候出现解析错误,会通过对话框的形式告诉用户。
* 如果首次解析安装包的时候,成功解析了,则会通知用户去打开"安装未知应用程序设置"。
* 在启动 Activity 的时候会进行内存检查,如果内存不足会通知用户。
* 如果这个应用程序已经在这个设备安装过了,则会向用户弹出一个对话框询问用户是否"替换现有应用程序的安装包"。
* 基于用户的回应,然后通过 InstallAppConfirm 的子类来安装应用程序。
* 所有状态的转换都是在这 Activity 中处理。
*/
public class PackageInstallerActivity extends OverlayTouchActivity implements OnClickListener {
... ...
}
了解完了官方说明,接下来我们查看它的 onCreate 方法:
onCreate
public class PackageInstallerActivity extends OverlayTouchActivity implements OnClickListener {
... ...
PackageManager mPm;
IPackageManager mIpm;
AppOpsManager mAppOpsManager;
PackageInstaller mInstaller;
UserManager mUserManager;
@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 对象:在该对象中包含了安装 APK 的基本信息;
*/
mInstaller = mPm.getPackageInstaller();
/**
* 初始化 UserManager 对象:用于多用户管理;
*/
mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
final Intent intent = getIntent();
... ...
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 {
// 如果是用户自己拉起来的安装,则默认 sessionId 为 -1,并且获取 packageUri
mSessionId = -1;
packageUri = intent.getData();
mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
}
// 返回 URI 解析错误
if (packageUri == null) {
Log.w(TAG, "Unspecified source");
setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
finish();
return;
}
// 如果设备为手表,则不支持
if (DeviceUtils.isWear(this)) {
showDialogInner(DLG_NOT_SUPPORTED_ON_WEAR);
return;
}
// 根据 Uri 的 Scheme 进行预处理
boolean wasSetUp = processPackageUri(packageUri); // ? ? ? 重点方法 1 ? ? ?
if (!wasSetUp) {
return;
}
bindUi(R.layout.install_confirm, false);
// 判断是否是未知来源的应用,如果开启允许安装未知来源选项则直接初始化安装
checkIfAllowedAndInitiateInstall(); // ? ? ? 重点方法 2 ? ? ?
}
... ...
}
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) {
// 处理 scheme 为 package 的情况
case SCHEME_PACKAGE: {
try {
/**
* PackageInfo mPkgInfo;
*
* 获取 package 对应的 Android 应用信息 PackageInfo 如:应用名称,权限列表等...
*/
mPkgInfo = mPm.getPackageInfo(packageUri.getSchemeSpecificPart(),
PackageManager.GET_PERMISSIONS | PackageManager.MATCH_UNINSTALLED_PACKAGES);
} catch (NameNotFoundException e) {
}
// 如果无法获取 PackageInfo ,弹出一个错误的对话框,然后直接退出安装
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;
}
// 创建 AppSnipet 对象,该对象封装了待安装 Android 应用的标题和图标
mAppSnippet = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo),
mPm.getApplicationIcon(mPkgInfo.applicationInfo));
} break;
// 处理 scheme 为 file 的情况
case ContentResolver.SCHEME_FILE: {
// 根据 packageUri 创建一个新的 File
File sourceFile = new File(packageUri.getPath());
// 创建 APK 文件的分析器 parsed
// ? ? ? 重点方法 ? ? ?
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;
}
/**
* 走到这边,说明解析成功
* 对 parsed 进行进一步处理得到包信息 PackageInfo,获取权限部分
* 这里面包含APK文件的相关信息
*
* PackageInfo mPkgInfo;
*/
mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
PackageManager.GET_PERMISSIONS, 0, 0, null, new PackageUserState());
/*
* private PackageUtil.AppSnippet mAppSnippet;
*
* 获取 PackageUtil.AppSnippet,AppSnippet 是 PackageUtil 的静态内部类
* 内部封装了 icon 和 label ;
* AppSnippet 中只有两个属性:lable(应用名称)、icon(应用图标)
*
* // getAppSnippet 返回的是 label 和 icon
* return new PackageUtil.AppSnippet(label, icon);
*/
mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
} break;
// 如果不是这两个协议就会抛出异常
default: {
throw new IllegalArgumentException("Unexpected URI scheme " + packageUri);
}
}
return true;
}
PackageUtil.getPackageInfo
/**
* Utility method to get package information for a given {@link File}
*/
public static PackageParser.Package getPackageInfo(Context context, File sourceFile) {
// new 了一个 PackageParser 对象
final PackageParser parser = new PackageParser();
parser.setCallback(new PackageParser.CallbackImpl(context.getPackageManager()));
try {
return parser.parsePackage(sourceFile, 0);
} catch (PackageParserException e) {
return null;
}
}
checkIfAllowedAndInitiateInstall
接下来我们看看 checkIfAllowedAndInitiateInstall 做所工作:
private void checkIfAllowedAndInitiateInstall() {
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;
}
/**
* isInstallRequestFromUnknownSource // 安装请求是否来自一个未知的源
*
* 判断如果允许安装未知来源或者根据 Intent 判断得出该 APK 不是未知来源
*/
if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) {
// 初始化安装
initiateInstall(); // ? ? ? 重点方法 1 ? ? ?
} else {
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(); // ? ? ? 重点方法 2 ? ? ?
}
}
}
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;
}
// Check if package is already installed. display confirmation dialog if replacing pkg
// 检查这个包是否真的被安装,如果要替换,则显示替换对话框
try {
// 获取设备上的残存数据,并且标记为 “installed” 的,实际上已经被卸载的应用
mAppInfo = mPm.getApplicationInfo(pkgName,
PackageManager.MATCH_UNINSTALLED_PACKAGES);
if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
// 如果应用是被卸载的,但是又是被标识成安装过的,则认为是新安装
mAppInfo = null;
}
} catch (NameNotFoundException e) {
mAppInfo = null;
}
// 列出权限列表,等待用户确认安装
startInstallConfirm();
}
我们可以看到, initiateInstall 主要做了三件事 :
✨ 1、检查设备是否是同名安装,如果是则后续是替换安装。
✨ 2、检查设备上是否已经安装了这个安装包,如果是,后面是替换安装。
✨ 3、调用 startInstallConfirm() 这个方法是安装的核心代码。
startInstallConfirm
下面我们就来看下 startInstallConfirm() 方法里面的具体实现:
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);
}
((TextView) findViewById(R.id.install_confirm_question))
.setText(R.string.install_confirm_question);
TabHost tabHost = (TabHost)findViewById(android.R.id.tabhost);
tabHost.setup();
ViewPager viewPager = (ViewPager)findViewById(R.id.pager);
TabsAdapter adapter = new TabsAdapter(this, tabHost, viewPager);
// If the app supports runtime permissions the new permissions will
// be requested at runtime, hence we do not show them at install.
// 根据 sdk 版本来判断 app 是否支持运行时权限
// 如果app支持运行时权限,这里会显示新的运行时权限
boolean supportsRuntimePermissions = mPkgInfo.applicationInfo.targetSdkVersion
>= Build.VERSION_CODES.M;
// 显示权限列表的变量,true:显示权限列表,false:未显示权限列表
boolean permVisible = false;
mScrollView = null;
mOkCanInstall = false;
int msg = 0;
// perms 这个对象包括了该应用的用户的 uid 以及相应的一些权限,以及权限组信息
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;
// 用来显示权限列表的 scrollview
mScrollView = new CaffeinatedScrollView(this);
// 如果显示的内容超过了 mScrollView ,则就会折叠可以滚动
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));
}
}
if (!supportsRuntimePermissions && !newPermissionsFound) {
// 如果既不支持可运行权限项也没有新权限发现,
// 则提示没有新权限(没有设置任何权限,只显示应用程序名称和图标)
LayoutInflater inflater = (LayoutInflater)getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
TextView label = (TextView)inflater.inflate(R.layout.label, null);
label.setText(R.string.no_new_perms);
mScrollView.addView(label);
}
adapter.addTab(tabHost.newTabSpec(TAB_ID_NEW).setIndicator(
getText(R.string.newPerms)), mScrollView);
}
// 如果至少设置了一个权限
if (!supportsRuntimePermissions && N > 0) {
permVisible = true;
LayoutInflater inflater = (LayoutInflater)getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
// 解析权限列表的视图
View root = inflater.inflate(R.layout.permissions_list, null);
if (mScrollView == null) {
mScrollView = (CaffeinatedScrollView)root.findViewById(R.id.scrollview);
}
// 添加到权限列表的视图
((ViewGroup)root.findViewById(R.id.permission_list)).addView(
perms.getPermissionsView(AppSecurityPermissions.WHICH_ALL));
adapter.addTab(tabHost.newTabSpec(TAB_ID_ALL).setIndicator(
getText(R.string.allPerms)), root);
}
if (!permVisible) {
// 如果不需要任何权限
if (mAppInfo != null) {
// 如果是更新安装包,并且没有任何权限要求
// 判断是否是系统应用来设置布局文件
msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
? R.string.install_confirm_question_update_system_no_perms
: R.string.install_confirm_question_update_no_perms;
} else {
// 是新安装的 app 并且没有权限列表
msg = R.string.install_confirm_question_no_perms;
}
// We do not need to show any permissions, load layout without permissions
// 设置相应的 UI
bindUi(R.layout.install_confirm, true);
mScrollView = null;
}
if (msg != 0) {
((TextView)findViewById(R.id.install_confirm_question)).setText(msg);
}
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,主要是将安装包分为新安装和更新安装,在更新安装里面又分为系统应用和非系统应用,然后根据不同的情况来显示不同的 UI,UI这块主要是通过 getPermissionsView 方法来获取不同的权限 View。
总结
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 或者跳转到设置界面。
后作
Android 9.0 源码_核心篇 -- 深入研究 PMS 系列(5)之 APK 安装流程(PI)
参考
01. http://liuwangshu.cn/framewor...
02. https://www.cnblogs.com/ouyan...