前言
前面两篇文章简单讲述了PackageManagerService的启动过程,和APK安装的内容一直都没有涉及到。APK可以通过多种方式安装,例如通过adb install安装、系统开机会默认安装系统应用、将apk放到手机通过PackageInstaller交互安装等等。安装APK的方式很多,但是这里选择用户使用最多的安装方式进行讲解,即通过PackageInstaller交互式安装。PackageInstaller是Android系统的默认程序,源码路劲为packages/app/PackageInstaller
,同样我们的代码基于Android 8.1
PackageInstaller分析起点
在Android 7.0以前,我们可以通过下面的方式来调用PackageInstaller进行程序的安装
1 2 3 4 |
Intent intent = new Intent(Intent.ACTION_VIEW); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setDataAndType(Uri.parse("file://xxxx.apk"),"application/vnd.android.package-archive"); context.startActivity(intent); |
而在Android 7.0及以上版本,上面的代码会出现FileUriExposedException的异常,导致整个的原因是因为Android 7.0中有如下行为变更
For apps targeting Android 7.0, the Android framework enforces the StrictMode API policy that prohibits exposing
file://
URIs outside your app. If an intent containing a file URI leaves your app, the app fails with aFileUriExposedException
exception.
翻译过来的意思大概就是:对于面向 Android 7.0 的应用,Android 框架执行的 StrictMode API 政策禁止在您的应用外部公开 file:// URI
。如果一项包含文件 URI 的 intent 离开您的应用,则应用出现故障,并出现 FileUriExposedException 异常。
所以在Android 7.0及以上的版本普遍会使用如下代码调用PackageInstaller进行APK的安装:
1 2 3 4 5 6 7 8 9 10 |
Intent intent = new Intent(Intent.ACTION_VIEW); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); if (Build.VERSION.SDK_INT >= 24) { //参数1上下文;参数2 Provider主机地址 authorities 和配置文件中保持一致 ;参数3共享的文件 Uri apkUri = FileProvider.getUriForFile(paramContext, "com.xxx.xxx.xxx", file); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.setDataAndType(apkUri, "application/vnd.android.package-archive"); } else { intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive"); } |
总结一下这两段代码的共同之处就是:1)设置Intent的action为ACTION_VIEW;2)设置Intent TYPE为application/vnd.android.package-archive
; 3)隐式启动activity
接下来我们查看PackageInstaller的AndroidManifest.xml看是否有Activity能够对得上上面的条件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<activity android:name=".InstallStart" android:exported="true" android:excludeFromRecents="true"> <intent-filter android:priority="1"> <font color='red'> <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" /> </font> </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="file" /> <data android:scheme="package" /> <data android:scheme="content" /> </intent-filter> <intent-filter android:priority="1"> <action android:name="android.content.pm.action.CONFIRM_PERMISSIONS" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> |
如上是PackageInstaller的AndroidManifest.xml的部分内容,其中红色部分真好匹配Action和mimeType。
Installstart.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 获取PackageManager mIPackageManager = AppGlobals.getPackageManager(); Intent intent = getIntent(); String callingPackage = getCallingPackage(); // 获取sessionID,在前面启动Intent前并没有设置sessionID这里跳过 int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1); if (callingPackage == null && sessionId != -1) { PackageInstaller packageInstaller = getPackageManager().getPackageInstaller(); PackageInstaller.SessionInfo sessionInfo = packageInstaller.getSessionInfo(sessionId); callingPackage = (sessionInfo != null) ? sessionInfo.getInstallerPackageName() : null; } // 获取调用PackageInstaller的应用的信息 final ApplicationInfo sourceInfo = getSourceInfo(callingPackage); final int originatingUid = getOriginatingUid(sourceInfo); boolean isTrustedSource = false; // 判断调用PackageInstaller的应用是否包含PRIVATE_FLAG_PRIVILEGED // PRIVATE_FLAG_PRIVILEGED在PKMS启动的时候有看到过,当时是在添加一些Share User的时候设置 // 这里明显不是 if (sourceInfo != null && (sourceInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) { isTrustedSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false); } // 判断调用PackageInstaller的应用是否有申明REQUEST_INSTALL_PACKAGES权限 if (!isTrustedSource && originatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) { final int targetSdkVersion = getMaxTargetSdkVersionForUid(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 && !declaresAppOpPermission( 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); // The the installation source as the nextActivity thinks this activity is the source, hence // set the originating UID and sourceInfo explicitly nextActivity.putExtra(PackageInstallerActivity.EXTRA_CALLING_PACKAGE, callingPackage); nextActivity.putExtra(PackageInstallerActivity.EXTRA_ORIGINAL_SOURCE_INFO, sourceInfo); nextActivity.putExtra(Intent.EXTRA_ORIGINATING_UID, originatingUid); // intent的action为ACTION_VIEW if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) { nextActivity.setClass(this, PackageInstallerActivity.class); } else { Uri packageUri = intent.getData(); // 通过FileProvider来处理URI的时候会将路径转换为`content://Uri` if (packageUri != null && (packageUri.getScheme().equals(ContentResolver.SCHEME_FILE) || packageUri.getScheme().equals(ContentResolver.SCHEME_CONTENT))) { // 所以下一个要启动的Activity为InstallStaging nextActivity.setClass(this, InstallStaging.class); } else if (packageUri != null && packageUri.getScheme().equals( PackageInstallerActivity.SCHEME_PACKAGE)) { 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(); } |
代码中的解释写得挺详细的了,这里就不多做解释。onCreate最后显示的设置跳转到InstallStaging Activity中。下面我们接着分析InstallStaging。
InstallStaging.java
InstallStaging启动后的调用路径是onCreate->onResume。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 设置界面,界面如下图 setContentView(R.layout.install_staging); // 异常退出后需要恢复 if (savedInstanceState != null) { mStagedFile = new File(savedInstanceState.getString(STAGED_FILE)); if (!mStagedFile.exists()) { mStagedFile = null; } } findViewById(R.id.cancel_button).setOnClickListener(view -> { if (mStagingTask != null) { mStagingTask.cancel(true); } setResult(RESULT_CANCELED); finish(); }); } protected void onResume() { super.onResume(); if (mStagingTask == null) { if (mStagedFile == null) { try { // 创建mStagedFile用于存储临时数据 mStagedFile = TemporaryFileManager.getStagedFile(this); } catch (IOException e) { showError(); return; } } // 创建StagingAsyncTask进行异步调用 mStagingTask = new StagingAsyncTask(); mStagingTask.execute(getIntent().getData()); } } |
install_staging的界面如下:
其次就是上面代码中并没有太多的实际内容,主要内容还是通过StagingAsyncTask异步调用进行的,下面接着看StagingAsyncTask完成什么操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
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]; // 打开读取Uri指定的文件 try (InputStream in = getContentResolver().openInputStream(packageUri)) { if (in == null) { return false; } // 将指定Uri的文件写入到mStagedFile中 try (OutputStream out = new FileOutputStream(mStagedFile)) { byte[] buffer = new byte[1024 * 1024]; int bytesRead; while ((bytesRead = in.read(buffer)) >= 0) { // 响应cancel事件 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) { // 这里通过file协议Uri来调用PackageInstallerActivity来进行安装 Intent installIntent = new Intent(getIntent()); installIntent.setClass(InstallStaging.this, PackageInstallerActivity.class); 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中,将content协议的Uri的内容读取出来然后写入到mStagedFile中,然后接着在onPostExcute中从mStagedFile产生file协议的Uri,然后使用给Uri来调用PackageInstallerActivity。这里感觉就像是启用了StrideMode后,绕了一个大圈,最后还是通过file协议的Uri来调用PackageInstaller。
PackageInstallerActivity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
protected void onCreate(Bundle icicle) { super.onCreate(icicle); if (icicle != null) { mAllowUnknownSources = icicle.getBoolean(ALLOW_UNKNOWN_SOURCES_KEY); } // 获取PackageManager mPm = getPackageManager(); // 这里获取的IPackageManager,主要用来和PKMS通信 mIpm = AppGlobals.getPackageManager(); // 获取OPSManager mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE); // 获取PackageInstaller mInstaller = mPm.getPackageInstaller(); // 获取UserManager mUserManager = (UserManager) getSystemService(Context.USER_SERVICE); final Intent intent = getIntent(); // 获取Intent中携带的数据 mCallingPackage = intent.getStringExtra(EXTRA_CALLING_PACKAGE); mSourceInfo = intent.getParcelableExtra(EXTRA_ORIGINAL_SOURCE_INFO); mOriginatingUid = intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID, PackageInstaller.SessionParams.UID_UNKNOWN); mOriginatingPackage = (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) ? getPackageNameForUid(mOriginatingUid) : null; final Uri packageUri; // 这里的Action不是CONFIRM_PERMISSION if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) { ..................... } else { mSessionId = -1; // 获取file协议的Uri packageUri = intent.getData(); mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI); mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER); } if (packageUri == null) { Log.w(TAG, "Unspecified source"); setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI); finish(); return; } // 非wear设备 if (DeviceUtils.isWear(this)) { showDialogInner(DLG_NOT_SUPPORTED_ON_WEAR); return; } // 对Uri进行处理 boolean wasSetUp = processPackageUri(packageUri); if (!wasSetUp) { return; } // 绑定UI界面 bindUi(R.layout.install_confirm, false); checkIfAllowedAndInitiateInstall(); } |
这里首先会先获取到各种各样的管理类,列举如下:
接着通过processPackageUri对packageUri进行解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
private boolean processPackageUri(final Uri packageUri) { mPackageURI = packageUri; // 获取Uri使用的协议 final String scheme = packageUri.getScheme(); switch (scheme) { case SCHEME_PACKAGE: { ................... } break; // ContentResolver.SCHEME_FILE协议 case ContentResolver.SCHEME_FILE: { // 从Uri中获取文件的路径并创建该文件 File sourceFile = new File(packageUri.getPath()); // getPackgeInfo最后会通过解析AndroidManifest文件获取apk相关信息 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中主要获取Permission相关信息 mPkgInfo = PackageParser.generatePackageInfo(parsed, null, PackageManager.GET_PERMISSIONS, 0, 0, null, new PackageUserState()); // 获取应用的label名,如果应用没有定义label则使用应用包名 mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile); } break; default: { throw new IllegalArgumentException("Unexpected URI scheme " + packageUri); } } |
上面的代码通过解析file协议的Uri获取到文件相关信息保存到pased和mPkgInfo中。现在返回到PackageInstallerActivity的onCreate方法中,现在onCreate还剩下最后一个函数checkIfAllowedAndInitiateInstall还没有看,我们接着分析这个函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
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; } // 允许从未知源安装应用并且安装包来之未知源,则执行安装 if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) { initiateInstall(); } else { // 查看是否未知源应用的安装进行了限制 final int unknownSourcesRestrictionSource = mUserManager.getUserRestrictionSource( UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, Process.myUserHandle()); // 如果从未知源安装应用功能被系统或者用户限制 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(); } } } |
这里我们的应用程序属于未知源安装包,所以如果我们在setting中选择了允许未知源安装,那么这里会直接走initiateInstall。如果为从未知源安装应用功能被限制的话,就会显示相关的提示信息。所以接下来我们查看initiateInstall的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
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 { // 如果APP只是带有数据,但是被标记为已卸载,这里我们也将其判断为已安装 mAppInfo = mPm.getApplicationInfo(pkgName, PackageManager.MATCH_UNINSTALLED_PACKAGES); if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) { mAppInfo = null; } } catch (NameNotFoundException e) { mAppInfo = null; } // 显示确认安装界面 startInstallConfirm(); } |
好了,到这里终于到了显示确认安装信息的界面了。
小结
这一篇先到这里了,下一篇我们将从startInstallConfirm。这里我们先做一下这篇文章的小结吧:
- 根据Uri的scheme不同,跳转到不同的界面,Android 7.0及以上的版本通过FilePorvider来提供Uri的话,起scheme是content,这里会跳转到InstallStart activity。其余的会跳转到PackageInstallerActivity
- InstallStart完成的工作主要就是讲content协议的Uri转换为file协议的Uri,然后见该信息传递给PackageInstallerActivity,然后在跳转到该Activity。
- PackageInstallerActivity会对package洗衣和file洗衣的Uri进行处理,如果是file协议的话会解析得到应用的PackageInfo信息
- 接着PackageInstallerActivity会对应用的来源进行判断,如果Package的来源于未知源并且系统设置了允许安装未知源的APK就会执行安装,否则显示弹出相关安装未知源应用的提示框