-
源头
我们要研究一个应用的安装过程,首先要找到这个过程的起点是什么。我们回忆一下应用安装的场景:
- 应用市场直接下载安装的,这个场景下我们看不到apk后缀的文件,应用市场是个软件,他只提供了一个安装的按钮,或者设置成下载完直接安装,整个过程对使用者来说是透明的;
- 浏览器下载apk文件,对于一些没有发布在当前厂商应用市场的应用,我们就需要在官网或者搜索引擎中找,这个场景下载下来的apk我们可以在“下载”菜单中看到,需要安装的时候可以直接点击一下即可;
通常会弹出一个应用安装的界面(安装确认页)来进行apk安装(厂商应用市场下载的可能直接就执行安装了不会弹出安装确认页,因为发布在上面的都是已经审核过的安全应用),这个弹出的动作很明显是提供点击apk文件动作的这个软件触发的。
安装确认页上面有查看权限、安装按钮、应用信息展示等功能,手机中应用的所有手动apk安装,都会显示相同的(可能部分信息有差异)安装确认页,如果是某个应用内置的安装功能的话它们不可能那么巧合地做成一样的界面,很明显它应该是系统提供的安装界面。
Android中的界面无非是Activity,那么安装确认页肯定依附于某个内嵌的应用,作为系统应用,一般放在AOSP的/packages/apps/下,在其中我们找到了一个叫做PackageInstaller 的应用,从名字上来看它大概就是我们要研究的主角。
接下来我们就顺着PackageInstaller的源码来一探究竟。
-
PackageInstaller的AndroidManifest配置
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.packageinstaller" coreApp="true"> <original-package android:name="com.android.packageinstaller" /> ...很多uses-permission... <application android:name=".PackageInstallerApplication" ...> <!--系统重启时删除所有的安装和卸载记录--> <receiver android:name=".TemporaryFileManager" android:exported="true"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> </receiver> <!--PackageInstallerApplication的逻辑入口,负责解析安装请求的intent并决定之后的走向--> <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" /> <!--标志文件类型是apk--> <data android:mimeType="application/vnd.android.package-archive" /> </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> <!-- 使用file或content的Uri安装时用于把位于源应用私有存储位置的apk文件复制到公有目录 --> <activity android:name=".InstallStaging" android:exported="false" /> <!-- 使用package的Uri安装时用于最后删除InstallStaging中创建的公有目录的临时文件 --> <activity android:name=".DeleteStagedFileOnResult" android:exported="false" /> <!--安装确认页--> <activity android:name=".PackageInstallerActivity" android:exported="false" /> <!-- 运行安装的页(安装进行中,有进度条)--> <activity android:name=".InstallInstalling" android:theme="@style/DialogWhenLargeNoAnimation" android:exported="false" /> <!-- 安装完成时IPackageInstaller会发送一条通知,InstallEventReceiver会捕获然后触发observer回调,回调中决定是执行安装成功逻辑还是失败逻辑 --> <receiver android:name=".InstallEventReceiver" android:permission="android.permission.INSTALL_PACKAGES" android:exported="true"> <intent-filter android:priority="1"> <action android:name="com.android.packageinstaller.ACTION_INSTALL_COMMIT" /> </intent-filter> </receiver> <!--安装成功页--> <activity android:name=".InstallSuccess" android:theme="@style/DialogWhenLargeNoAnimation" android:exported="false" /> <!--安装失败页--> <activity android:name=".InstallFailed" android:theme="@style/DialogWhenLargeNoAnimation" android:exported="false" /> <!--卸载确认页--> <activity android:name=".UninstallerActivity" android:configChanges="orientation|keyboardHidden|screenSize" android:excludeFromRecents="true" android:theme="@style/AlertDialogActivity"> <intent-filter android:priority="1"> <action android:name="android.intent.action.DELETE" /> <action android:name="android.intent.action.UNINSTALL_PACKAGE" /> <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="package" /> </intent-filter> </activity> <!-- 卸载完成时会收到通知决定后面的卸载成功还是失败逻辑 --> <receiver android:name=".UninstallEventReceiver" android:permission="android.permission.INSTALL_PACKAGES" android:exported="true"> <intent-filter android:priority="1"> <action android:name="com.android.packageinstaller.ACTION_UNINSTALL_COMMIT" /> </intent-filter> </receiver> <!-- 执行卸载,进行中的页面 --> <activity android:name=".UninstallUninstalling" android:excludeFromRecents="true" android:theme="@style/AlertDialogActivity" android:exported="false" /> <!-- 卸载成功页面--> <receiver android:name=".UninstallFinish" android:exported="false" /> <!--权限授权页--> <activity android:name=".permission.ui.GrantPermissionsActivity" android:configChanges="orientation|keyboardHidden|screenSize" android:excludeFromRecents="true" android:theme="@style/GrantPermissions" android:visibleToInstantApps="true"> <intent-filter android:priority="1"> <action android:name="android.content.pm.action.REQUEST_PERMISSIONS" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> <!--权限管理页--> <activity android:name=".permission.ui.ManagePermissionsActivity" android:configChanges="orientation|keyboardHidden|screenSize" android:excludeFromRecents="true" android:label="@string/app_permissions" android:theme="@style/Settings" android:permission="android.permission.GRANT_RUNTIME_PERMISSIONS"> <intent-filter android:priority="1"> <action android:name="android.intent.action.MANAGE_PERMISSIONS" /> <action android:name="android.intent.action.MANAGE_APP_PERMISSIONS" /> <action android:name="android.intent.action.MANAGE_PERMISSION_APPS" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> <!-- Wearable Components 可穿戴设备的应用(手表等)安装相关 --> ... </application> </manifest>
TemporaryFileManager会在系统启动时触发:
@Override public void onReceive(Context context, Intent intent) { long systemBootTime = System.currentTimeMillis() - SystemClock.elapsedRealtime(); File[] filesOnBoot = context.getNoBackupFilesDir().listFiles(); if (filesOnBoot == null) { return; } for (int i = 0; i < filesOnBoot.length; i++) { File fileOnBoot = filesOnBoot[i]; //重启后删除之前所有的记录 if (systemBootTime > fileOnBoot.lastModified()) { boolean wasDeleted = fileOnBoot.delete(); if (!wasDeleted) { Log.w(LOG_TAG, "Could not delete " + fileOnBoot.getName() + " onBoot"); } } else { Log.w(LOG_TAG, fileOnBoot.getName() + " was created before onBoot broadcast was " + "received"); } } }
getNoBackupFilesDir().listFiles方法会得到所有的安装和卸载记录文件,可见它的作用是系统重启后删除之前所有的安装记录,为什么这么说——后面会看到getNoBackupFilesDir中的文件是怎么来的。
在可以安装apk的应用中,当我们点击“安装”时,背后其实就是调用了startActivity,它的intent的配置就是指向了InstallStart 这个activity,我们来看一下它的逻辑:
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); mIPackageManager = AppGlobals.getPackageManager(); Intent intent = getIntent(); String callingPackage = getCallingPackage(); //这块逻辑是判断请求安装的应用是否是可信的,比如厂商的应用市场就是可信的 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; } final ApplicationInfo sourceInfo = getSourceInfo(callingPackage); final int originatingUid = getOriginatingUid(sourceInfo); boolean isTrustedSource = false; //ApplicationInfo的privateFlags是由系统开发者(厂商)设置的,值为PRIVATE_FLAG_PRIVILEGED才会获取Intent.EXTRA_NOT_UNKNOWN_SOURCE来判断,所以如果你只是自定义intent这个属性是没用的 if (sourceInfo != null && (sourceInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) { isTrustedSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false); } //如果发送安装请求的来源应用不是系统可信的(不是一家人),则需要其他验证 if (!isTrustedSource && originatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) { final int targetSdkVersion = getMaxTargetSdkVersionForUid(this, originatingUid); //如果包信息中没有targetSdkVersion则拦截 if (targetSdkVersion < 0) { ... // Invalid originating uid supplied. Abort install. mAbortInstall = true; //targetSdkVersion版本不低于Build.VERSION_CODES.O(也就是8.0)则需要Manifest.permission.REQUEST_INSTALL_PACKAGES授权 } else if (targetSdkVersion >= Build.VERSION_CODES.O //declaresAppOpPermission方法会查找所有的授权了Manifest.permission.REQUEST_INSTALL_PACKAGES的应用信息,如果包含当前这个来源应用的话就不拦截 && !declaresAppOpPermission( originatingUid, 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); nextActivity.putExtra(PackageInstallerActivity.EXTRA_CALLING_PACKAGE, callingPackage); nextActivity.putExtra(PackageInstallerActivity.EXTRA_ORIGINAL_SOURCE_INFO, sourceInfo); nextActivity.putExtra(Intent.EXTRA_ORIGINATING_UID, originatingUid); //如果需要展示安装确认页的话 if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) { nextActivity.setClass(this, PackageInstallerActivity.class); } else { //其他的则按照Uri解析 Uri packageUri = intent.getData(); //设置了scheme为file或content的话则打开InstallStaging页 if (packageUri != null && (packageUri.getScheme().equals(ContentResolver.SCHEME_FILE) || packageUri.getScheme().equals(ContentResolver.SCHEME_CONTENT))) { nextActivity.setClass(this, InstallStaging.class); //设置了scheme为package的话则打开PackageInstallerActivity页 } else if (packageUri != null && packageUri.getScheme().equals( PackageInstallerActivity.SCHEME_PACKAGE)) { nextActivity.setClass(this, PackageInstallerActivity.class); } else { //无效URI 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(); }
可见,InstallStart的onCreate方法中首先会进行一些安全性的验证,它是针对发送安装请求的应用的检验,比如应用市场的安装请求,没问题的话会打开InstallStaging或者PackageInstallerActivity。
PackageInstallerActivity是安装确认页,InstallStaging是实际安装页吗?这得需要我们分析完它俩的源码才能确定。
-
InstallStaging
InstallStaging的界面不重要,一个process view表示复制进度而已。
@Override protected void onResume() { super.onResume(); if (mStagingTask == null) { if (mStagedFile == null) { try { mStagedFile = TemporaryFileManager.getStagedFile(this); } catch (IOException e) { showError(); return; } } mStagingTask = new StagingAsyncTask(); mStagingTask.execute(getIntent().getData()); } }
TemporaryFileManager的getStagedFile方法如下:
@NonNull public static File getStagedFile(@NonNull Context context) throws IOException { return File.createTempFile("package", ".apk", context.getNoBackupFilesDir()); }
context.getNoBackupFilesDir()实际上就是new File(getDataDir(), "no_backup")。
getIntent().getData()就是要安装的apk文件的URI,StagingAsyncTask的doInBackground方法中会把源apk文件复制到context.getDataDir()/no_backup/package.apk文件:
@Override protected Boolean doInBackground(Uri... params) { if (params == null || params.length <= 0) { return false; } //源apk文件的Uri Uri packageUri = params[0]; //通过ContentResolver来获取apk文件 try (InputStream in = getContentResolver().openInputStream(packageUri)) { if (in == null) { return false; } //复制到当前PackageInstaller应用的私有目录下 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 | IllegalStateException e) { return false; } return true; }
复制结束后会finish并打开DeleteStagedFileOnResult这个Activity:
@Override protected void onPostExecute(Boolean success) { if (success) { Intent installIntent = new Intent(getIntent()); installIntent.setClass(InstallStaging.this, DeleteStagedFileOnResult.class); //注意传入DeleteStagedFileOnResult的data此时已经变成了临时文件的Uri 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(); } }
DeleteStagedFileOnResult并没设置任何界面,在其onCreate中直接打开了PackageInstallerActivity,那为何多此一举?
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { File sourceFile = new File(getIntent().getData().getPath()); sourceFile.delete(); setResult(resultCode, data); finish(); }
我们看到在它的onActivityResult方法中会删除之前创建的临时apk文件并finish掉自己。后续代码分析中我们会看到,PackageInstallerActivity中在取消安装时是会调用setResult方法的,所以会返回这里删除掉临时文件,所以它的作用就是干这个的,很巧妙是不是,PackageInstallerActivity完全感知不到临时文件的存在。
-
PackageInstallerActivity
PackageInstallerActivity的界面上会显示图标、appName、有一个自定义的ScrollView(可设置一个滚动到底部时的回调,在回调中改变“安装”按钮为可点击状态,以此来尽量使用户知悉应用的所需权限)来盛放权限内容、安装和取消按钮等。
onCreate中会调用checkIfAllowedAndInitiateInstall方法来验证当前用户的权限:
private void checkIfAllowedAndInitiateInstall() { final int installAppsRestrictionSource = mUserManager.getUserRestrictionSource( UserManager.DISALLOW_INSTALL_APPS, Process.myUserHandle()); ...//用户是否允许安装应用的权限验证 if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) { initiateInstall(); } else { if (...) { ...//用户是否允许安装未知来源应用的权限验证 } else { handleUnknownSources(); } } }
handleUnknownSources中会检查Manifest.permission.REQUEST_INSTALL_PACKAGES权限,如果通过则也会调用initiateInstall方法:
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(); }
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. //即如果不支持运行时权限的话(6.0以下)就在此列出所有有关安全的权限,支持运行时权限的版本中,安全权限会在运行时询问授权 boolean supportsRuntimePermissions = mPkgInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M; ...//列出安全权限 if (mScrollView == null) { // 不需要展示权限信息 mOk.setText(R.string.install); mOkCanInstall = true; } else { //需要展示权限信息的话设置一个滚动到底部的监听,为了让用户往下滑动以至于尽量使其知悉这些关键权限 mScrollView.setFullScrollAction(new Runnable() { @Override public void run() { mOk.setText(R.string.install); mOkCanInstall = true; } }); } }
mOkCanInstall表示安装按钮可以触发安装操作:
public void onClick(View v) { if (v == mOk) { if (mOk.isEnabled()) { if (mOkCanInstall || mScrollView == null) { if (mSessionId != -1) { mInstaller.setPermissionsResult(mSessionId, true); finish(); } else { startInstall(); } } else { mScrollView.pageScroll(View.FOCUS_DOWN); } } } else if (v == mCancel) { setResult(RESULT_CANCELED); if (mSessionId != -1) { mInstaller.setPermissionsResult(mSessionId, false); } finish(); } }
startInstall方法如下:
private void startInstall() { Intent newIntent = new Intent(); newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, mPkgInfo.applicationInfo); newIntent.setData(mPackageURI); newIntent.setClass(this, InstallInstalling.class); ... startActivity(newIntent); finish(); }
可见,这里又打开了一个名为InstallInstalling的Activity。
PackageInstallerActivity检验的是用户相关的权限和合法性。
-
InstallInstalling
其onCreate方法如下:
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.install_installing); ApplicationInfo appInfo = getIntent() .getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO); mPackageURI = getIntent().getData(); //如果是系统应用的话,那它们的应用资源都和系统捆绑在一起的,所以直接安装 if ("package".equals(mPackageURI.getScheme())) { try { getPackageManager().installExistingPackage(appInfo.packageName); launchSuccess(); } catch (PackageManager.NameNotFoundException e) { launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null); } } else { //第三方apk安装 final File sourceFile = new File(mPackageURI.getPath()); ... if (savedInstanceState != null) { ...//复用 } else { PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( PackageInstaller.SessionParams.MODE_FULL_INSTALL); params.installFlags = PackageManager.INSTALL_FULL_APP; params.referrerUri = getIntent().getParcelableExtra(Intent.EXTRA_REFERRER); params.originatingUri = getIntent() .getParcelableExtra(Intent.EXTRA_ORIGINATING_URI); params.originatingUid = getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID, UID_UNKNOWN); params.installerPackageName = getIntent().getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME); File file = new File(mPackageURI.getPath()); try { //解析出包信息赋值给params PackageParser.PackageLite pkg = PackageParser.parsePackageLite(file, 0); params.setAppPackageName(pkg.packageName); params.setInstallLocation(pkg.installLocation); params.setSize( PackageHelper.calculateInstalledSize(pkg, false, params.abiOverride)); } catch (PackageParser.PackageParserException e) { params.setSize(file.length()); } catch (IOException e) { params.setSize(file.length()); } try { //设置一个Observer,安装完成后会触发launchFinishBasedOnResult方法 mInstallId = InstallEventReceiver .addObserver(this, EventResultPersister.GENERATE_NEW_ID, this::launchFinishBasedOnResult); } catch (EventResultPersister.OutOfIdsException e) { launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null); } try { //建立和PackageInstaller的session mSessionId = getPackageManager().getPackageInstaller().createSession(params); } catch (IOException e) { launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null); } } mCancelButton = (Button) findViewById(R.id.cancel_button); mCancelButton.setOnClickListener(view -> { //取消安装 if (mInstallingTask != null) { mInstallingTask.cancel(true); } if (mSessionId > 0) { getPackageManager().getPackageInstaller().abandonSession(mSessionId); mSessionId = 0; } setResult(RESULT_CANCELED); finish(); }); //这个是更新界面安装进度的 mSessionCallback = new InstallSessionCallback(); } }
其中PackageParser.parsePackageLite方法流程中会用到ApkAssets加载apk文件,它是直接使用了apk文件的路径来加载的,这也就是为什么我们要使用InstallStaging多此一举的原因,因为
根据Android系统的存储权限要求,无法直接通过文件路径访问其他应用的私有目录下的文件,只能通过ContentProvider共享后用ContentResolver获取
。然后在onResume中会启动一个后台任务来执行安装:
@Override protected void onResume() { super.onResume(); if (mInstallingTask == null) { PackageInstaller installer = getPackageManager().getPackageInstaller(); PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId); if (sessionInfo != null && !sessionInfo.isActive()) { mInstallingTask = new InstallingAsyncTask(); mInstallingTask.execute(); } else { mCancelButton.setEnabled(false); setFinishOnTouchOutside(false); } } }
InstallingAsyncTask中:
private final class InstallingAsyncTask extends AsyncTask<Void, Void, PackageInstaller.Session> { volatile boolean isDone; @Override protected PackageInstaller.Session doInBackground(Void... params) { PackageInstaller.Session session; try { //根据之前创建的mSessionId建立通道 session = getPackageManager().getPackageInstaller().openSession(mSessionId); } catch (IOException e) { return null; } //初始进度 session.setStagingProgress(0); try { File file = new File(mPackageURI.getPath()); try (InputStream in = new FileInputStream(file)) { long sizeBytes = file.length(); //看来是写入一个PackageInstaller相关的文件 try (OutputStream out = session .openWrite("PackageInstaller", 0, sizeBytes)) { byte[] buffer = new byte[1024 * 1024]; while (true) { int numRead = in.read(buffer); if (numRead == -1) { session.fsync(out); break; } if (isCancelled()) { session.close(); break; } out.write(buffer, 0, numRead); if (sizeBytes > 0) { float fraction = ((float) numRead / (float) sizeBytes); //更新进度 session.addProgress(fraction); } } } } return session; } catch (IOException | SecurityException e) { session.close(); return null; } finally { synchronized (this) { isDone = true; notifyAll(); } } } @Override protected void onPostExecute(PackageInstaller.Session session) { if (session != null) { Intent broadcastIntent = new Intent(BROADCAST_ACTION); broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); broadcastIntent.setPackage( getPackageManager().getPermissionControllerPackageName()); broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId); PendingIntent pendingIntent = PendingIntent.getBroadcast( InstallInstalling.this, mInstallId, broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT); //安装完成 session.commit(pendingIntent.getIntentSender()); mCancelButton.setEnabled(false); setFinishOnTouchOutside(false); } else { getPackageManager().getPackageInstaller().abandonSession(mSessionId); if (!isCancelled()) { launchFailure(PackageManager.INSTALL_FAILED_INVALID_APK, null); } } } }
session其实持有了一个PackageInstallerSession,是通过它处理的,是一个AIDL文件。session.commit后如果验证成功会发送一个通知,InstallEventReceiver会接收到,然后触发Observer,执行launchFinishBasedOnResult方法:
private void launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage) { if (statusCode == PackageInstaller.STATUS_SUCCESS) { launchSuccess(); } else { launchFailure(legacyStatus, statusMessage); } } private void launchSuccess() { Intent successIntent = new Intent(getIntent()); successIntent.setClass(this, InstallSuccess.class); successIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); startActivity(successIntent); finish(); } private void launchFailure(int legacyStatus, String statusMessage) { Intent failureIntent = new Intent(getIntent()); failureIntent.setClass(this, InstallFailed.class); failureIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); failureIntent.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, legacyStatus); failureIntent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, statusMessage); startActivity(failureIntent); finish(); }
到这里我们已经梳理完了安装一个apk的大概流程,只知道安装好像是把一个apk文件写入到了某个地方,至于是什么地方,那就需要解析完PackageInstallerSession之后才能知道了,限于篇幅,另起一篇博文。
-
总结
-
源应用(触发安装请求的应用)通过startActivity(intent)的方式发起安装应用的请求,intent中设置目标信息,就是PackageInstaller应用下AndroidManifest.xml中InstallStart 这个activity配置的intent-filter中的对应内容,然后和正常的Activity启动一样,InstallStart就被打开了。
-
在InstallStart的onCreate方法中,首先会判断发送安装请求的应用是否是系统信任的应用,比如厂商自带的应用市场对于系统来说就是可信任的。如果不是系统自带应用则需要判断targetSdkVersion是否有效,其次如果targetSdkVersion不小于26(8.0)的话,则还需要验证是否有
Manifest.permission.REQUEST_INSTALL_PACKAGES
权限,没达到这些要求的话就会被拦截,直接取消安装finish,也就是说,这个类是验证发起安装请求的应用是否被允许安装别的应用
。如果通过了验证,则会构造nextActivity,nextActivity的目标有两种选择,一种是跳到PackageInstallerActivity,一种是跳到InstallStaging。
intent设置的uri如果是
以package开头的,说明安装的apk是系统应用包
,则直接打开安装确认页。intent设置的uri如果是以file或content开头的,要安装的apk文件肯定不在PackageInstaller应用的私有存储目录中,
根据Android系统的存储权限要求,无法直接通过文件路径访问其他应用的私有目录下的文件,只能通过ContentProvider共享后用ContentResolver获取,而在后面的apk解析过程中ApkAssets会直接用到apk文件的路径来加载的,所以为了避免权限问题,这里会先跳转到InstallStaging
,而InstallStaging中会通过ContentResolver获取源apk文件,然后复制到PackageInstaller应用的私有存储目录下的一个临时目录中(临时文件位于当前发起应用内的context.getDataDir()/no_backup目录下,名叫“package.apk”),这样在后续使用文件路径访问的时候就不会有存储权限问题了。`然后复制完成后会打开一个叫DeleteStagedFileOnResult的Activity(它的作用是在安装结束后自动删除临时文件),在其onCreate方法中又打开了PackageInstallerActivity,所以,殊途同归,最终都是打开PackageInstallerActivity。PackageInstallerActivity是安装确认页,在这个页你可以查看要安装应用的权限等信息,同时在这个页会
验证用户的权限和该安装包是否合法
,如果验证通过,则“安装”按钮就变为可点击状态,点击后打开名为InstallInstalling的Activity。 -
InstallInstalling中会通过PackageInstaller的createSession方法把安装包的信息传过去,并得到一个mSessionId,然后创建一个AsyncTask,在其doInBackground方法中通过PackageInstaller的openSession(mSessionId)得到一个PackageInstaller.Session对象,它的相关方法都是调用了一个IPackageInstallerSession(AIDL)类型的IBinder开启写入,这也就是安装的过程,最后在onPostExecute方法中调用session的commit方法,这个方法流程中会发送一个通知,InstallEventReceiver会接收,InstallEventReceiver在InstallInstalling的onCreate中注册了一个Observer,这时这个Observer就会被回调,在其中会判断结果状态,如果安装成功就打开InstallSuccess页,如果失败就打开InstallFailed页。
-
网友评论