PackageInstallerActivity 解析
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@Overrideprotected 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安装,直接跳转PackageInstallerActivityif (isSessionInstall) {nextActivity.setClass(this, PackageInstallerActivity.class);} else {Uri packageUri = intent.getData();//如果得到的URI的getScheme()是ContentResolver.SCHEME_CONTENT,则会跳转到InstallStagingif (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 processnextActivity.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;@Overrideprotected void onResume() {super.onResume();// This is the first onResume in a single life of the activityif (mStagingTask == null) {// File does not exist, or became invalidif (mStagedFile == null) {// Create file delayed to be able to show errortry {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> {@Overrideprotected 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 cancellationif (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;}@Overrideprotected void onPostExecute(Boolean success) {if (success) {// Now start the installation again from a fileIntent 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。
简单说一下几个函数的作用。
@Overrideprotected void onCreate(Bundle icicle) {...//根据Uri的Scheme进行预处理,对apk进行解析boolean wasSetUp = processPackageUri(packageUri);... }@Overrideprotected void onResume() {super.onResume();...if (mAppSnippet != null) {// load dummy layout with OK button disabled until we override this layout in// startInstallConfirmbindUi();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 sessionsession.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;}