Android T PackageInstaller解析
Apk安装方式
- 通过adb命令安装 adb install -r
- 用户下载的Apk,通过系统安装器packageinstaller安装该Apk
- 系统内置应用,开机时自动安装系统应用
执行到最后都是由PackageManagerService来进行处理.
解析系统安装器PackageInstaller安装Apk
InstallStart
InstallStart是PackageInstaller中的入口Activity,其中PackageInstaller是系统内置的应用程序,用于安装和卸载应用。
当我们调用PackageInstaller来安装应用时会跳转到InstallStart,并调用它的onCreate方法。
packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
...
Intent intent = getIntent();
...
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
//判断intent的action是否等于PackageInstaller.ACTION_CONFIRM_INSTALL,
//如果是,这次安装为PackageInstaller session安装 会去intent中取出SESSION_ID,
//并且得到SessionInfo信息,并从中得到callingPackage、callingAttributionTag。
//当前Activity是运行在安装应用进程中,PackageInstaller session是在系统进程中。
//在系统进程中,如果检测到权限不够,需要用户来确认,这时,会启动InstallStart以便进入用户确认界面。
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;
}
...
//判断是否勾选“未知来源”选项,若未勾选跳转到设置安装未知来源界面
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)) {
//对于大于等于 Android 8.0 版本,会先检查是否申请安装权限,若没有则中断安装
Log.e(LOG_TAG, "Requesting uid " + originatingUid + " needs to declare permission "
+ Manifest.permission.REQUEST_INSTALL_PACKAGES);
mAbortInstall = true;
}
}
...
Intent nextActivity = new Intent(intent);
nextActivity.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT
| Intent.FLAG_GRANT_READ_URI_PERMISSION);
...
//isSessionInstall 为true,是PackageInstaller session安装,直接跳转PackageInstallerActivity
if (isSessionInstall) {
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
Uri packageUri = intent.getData();
//如果得到的URI的getScheme()是ContentResolver.SCHEME_CONTENT,则会跳转到InstallStaging
if (packageUri != null && packageUri.getScheme().equals(
ContentResolver.SCHEME_CONTENT)) {
// [IMPORTANT] This path is deprecated, but should still work. Only necessary
// features should be added.
// Copy file to prevent it from being changed underneath this process
nextActivity.setClass(this, InstallStaging.class);
//如果得到的URI的getScheme()是PackageInstallerActivity.SCHEME_PACKAGE,
//则会跳转到PackageInstallerActivity中
} else if (packageUri != null && packageUri.getScheme().equals(
PackageInstallerActivity.SCHEME_PACKAGE)) {
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
//如果不是以上情况,则返回PackageManager.INSTALL_FAILED_INVALID_URI给调用者Activity。
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 协议,若是 SCHEME_CONTENT 则调用 InstallStaging。
若是 SCHEME_PACKAGE 则调用 PackageInstallerActivity。
PS:file协议的Uri通常以"file://"开头,用于标识本地文件系统上的文件路径。
而content协议的Uri则以"content://"开头,用于访问通过内容提供者(Content Provider)暴露的数据。
Android7.0以及更高版本我们会使用FileProvider来处理URI ,FileProvider会隐藏共享文件的真实路径,将路径转换成content://Uri路径,
这样就会跳转到InstallStaging::onResum()方法内,大致流程如下:
packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
/** Currently running task that loads the file from the content URI into a file */
private @Nullable StagingAsyncTask mStagingTask;
/** The file the package is in */
private @Nullable File mStagedFile;
@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 = TemporaryFileManager.getStagedFile(this);
} catch (IOException e) {
showError();
return;
}
}
mStagingTask = new StagingAsyncTask();
mStagingTask.execute(getIntent().getData());
}
}
mStagedFile用于存储临时数据,mStagingTask拿到了content协议的Uri,追踪StagingAsyncTask拿到这个Uri后会做啥?
StagingAsyncTask是InstallStaging的内部类。
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) {
// 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中,
如果写入成功,onPostExecute方法中会跳转到PackageInstallerActivity中,并将mStagedFile传进去。
绕了一圈又回到了PackageInstallerActivity,这里可以看出InstallStaging主要起了转换的作用,将content协议的Uri转换为File协议,
然后跳转到PackageInstallerActivity,这样就可以像此前版本(Android7.0之前)一样启动安装流程了。
PackageInstallerActivity
PackageInstallerActivity才是应用安装器PackageInstaller真正的入口Activity。
简单说一下几个函数的作用。
@Override
protected void onCreate(Bundle icicle) {
...
//根据Uri的Scheme进行预处理,对apk进行解析
boolean wasSetUp = processPackageUri(packageUri);
...
}
@Override
protected void onResume() {
super.onResume();
...
if (mAppSnippet != null) {
// load dummy layout with OK button disabled until we override this layout in
// startInstallConfirm
bindUi();
checkIfAllowedAndInitiateInstall();//检查是否允许安装该软件包,如果允许,则启动安装。如果不允许,会弹出对应的对话框。
}
...
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
//检查包是否已安装。如果替换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();
}
在PackageInstallerActivity调用startInstallConfirm方法初始化安装确认界面后。
之后的流程:
PackageInstallerActivity::startInstall()-->InstallInstalling::onCreate()
--->InstallInstalling::onResume()--->InstallInstalling::onPostExecute(){
...
//PackageInstaller.Session session
session.commit(pendingIntent.getIntentSender());
...
}
./frameworks/base/core/java/android/content/pm/PackageInstaller.java
说明要通过IPackageInstallerSession来进行进程间的通信,
最终会调用PackageInstallerSession的commit方法,
这样代码逻辑就到了Java框架层的。
1.将APK的信息通过IO流的形式写入到PackageInstaller.Session中。
2.调用PackageInstaller.Session的commit方法,将APK的信息交由PMS处理。
禁止安装三方应用
PackageManagerService 作用
PackageInstaller 静默安装实现
MTK-Android13-包安装器PackageInstaller 静默安装实现-需要改动文件过多
MTK Android12静默安装接口-只能用于系统签名apk
/**
* 根据包名判断app是否具有系统签名
*/
private static boolean isSystemSign (Context context){
return context.getPackageManager().checkSignatures(Binder.getCallingUid(), android.os.Process.SYSTEM_UID) == PackageManager.SIGNATURE_MATCH;
}

浙公网安备 33010602011771号