-
源頭
我們要研究一個(gè)應(yīng)用的安裝過程,首先要找到這個(gè)過程的起點(diǎn)是什么。我們回憶一下應(yīng)用安裝的場(chǎng)景:
- 應(yīng)用市場(chǎng)直接下載安裝的,這個(gè)場(chǎng)景下我們看不到apk后綴的文件,應(yīng)用市場(chǎng)是個(gè)軟件,他只提供了一個(gè)安裝的按鈕,或者設(shè)置成下載完直接安裝,整個(gè)過程對(duì)使用者來說是透明的;
- 瀏覽器下載apk文件,對(duì)于一些沒有發(fā)布在當(dāng)前廠商應(yīng)用市場(chǎng)的應(yīng)用,我們就需要在官網(wǎng)或者搜索引擎中找,這個(gè)場(chǎng)景下載下來的apk我們可以在“下載”菜單中看到,需要安裝的時(shí)候可以直接點(diǎn)擊一下即可;
通常會(huì)彈出一個(gè)應(yīng)用安裝的界面(安裝確認(rèn)頁)來進(jìn)行apk安裝(廠商應(yīng)用市場(chǎng)下載的可能直接就執(zhí)行安裝了不會(huì)彈出安裝確認(rèn)頁,因?yàn)榘l(fā)布在上面的都是已經(jīng)審核過的安全應(yīng)用),這個(gè)彈出的動(dòng)作很明顯是提供點(diǎn)擊apk文件動(dòng)作的這個(gè)軟件觸發(fā)的。
安裝確認(rèn)頁上面有查看權(quán)限、安裝按鈕、應(yīng)用信息展示等功能,手機(jī)中應(yīng)用的所有手動(dòng)apk安裝,都會(huì)顯示相同的(可能部分信息有差異)安裝確認(rèn)頁,如果是某個(gè)應(yīng)用內(nèi)置的安裝功能的話它們不可能那么巧合地做成一樣的界面,很明顯它應(yīng)該是系統(tǒng)提供的安裝界面。
Android中的界面無非是Activity,那么安裝確認(rèn)頁肯定依附于某個(gè)內(nèi)嵌的應(yīng)用,作為系統(tǒng)應(yīng)用,一般放在AOSP的/packages/apps/下,在其中我們找到了一個(gè)叫做PackageInstaller 的應(yīng)用,從名字上來看它大概就是我們要研究的主角。
接下來我們就順著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" ...> <!--系統(tǒng)重啟時(shí)刪除所有的安裝和卸載記錄--> <receiver android:name=".TemporaryFileManager" android:exported="true"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> </receiver> <!--PackageInstallerApplication的邏輯入口,負(fù)責(zé)解析安裝請(qǐng)求的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" /> <!--標(biāo)志文件類型是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安裝時(shí)用于把位于源應(yīng)用私有存儲(chǔ)位置的apk文件復(fù)制到公有目錄 --> <activity android:name=".InstallStaging" android:exported="false" /> <!-- 使用package的Uri安裝時(shí)用于最后刪除InstallStaging中創(chuàng)建的公有目錄的臨時(shí)文件 --> <activity android:name=".DeleteStagedFileOnResult" android:exported="false" /> <!--安裝確認(rèn)頁--> <activity android:name=".PackageInstallerActivity" android:exported="false" /> <!-- 運(yùn)行安裝的頁(安裝進(jìn)行中,有進(jìn)度條)--> <activity android:name=".InstallInstalling" android:theme="@style/DialogWhenLargeNoAnimation" android:exported="false" /> <!-- 安裝完成時(shí)IPackageInstaller會(huì)發(fā)送一條通知,InstallEventReceiver會(huì)捕獲然后觸發(fā)observer回調(diào),回調(diào)中決定是執(zhí)行安裝成功邏輯還是失敗邏輯 --> <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" /> <!--卸載確認(rèn)頁--> <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> <!-- 卸載完成時(shí)會(huì)收到通知決定后面的卸載成功還是失敗邏輯 --> <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> <!-- 執(zhí)行卸載,進(jìn)行中的頁面 --> <activity android:name=".UninstallUninstalling" android:excludeFromRecents="true" android:theme="@style/AlertDialogActivity" android:exported="false" /> <!-- 卸載成功頁面--> <receiver android:name=".UninstallFinish" android:exported="false" /> <!--權(quán)限授權(quán)頁--> <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> <!--權(quán)限管理頁--> <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 可穿戴設(shè)備的應(yīng)用(手表等)安裝相關(guān) --> ... </application> </manifest>TemporaryFileManager會(huì)在系統(tǒng)啟動(dòng)時(shí)觸發(fā):
@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方法會(huì)得到所有的安裝和卸載記錄文件,可見它的作用是系統(tǒng)重啟后刪除之前所有的安裝記錄,為什么這么說——后面會(huì)看到getNoBackupFilesDir中的文件是怎么來的。
在可以安裝apk的應(yīng)用中,當(dāng)我們點(diǎn)擊“安裝”時(shí),背后其實(shí)就是調(diào)用了startActivity,它的intent的配置就是指向了InstallStart 這個(gè)activity,我們來看一下它的邏輯:
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); mIPackageManager = AppGlobals.getPackageManager(); Intent intent = getIntent(); String callingPackage = getCallingPackage(); //這塊邏輯是判斷請(qǐng)求安裝的應(yīng)用是否是可信的,比如廠商的應(yīng)用市場(chǎng)就是可信的 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是由系統(tǒng)開發(fā)者(廠商)設(shè)置的,值為PRIVATE_FLAG_PRIVILEGED才會(huì)獲取Intent.EXTRA_NOT_UNKNOWN_SOURCE來判斷,所以如果你只是自定義intent這個(gè)屬性是沒用的 if (sourceInfo != null && (sourceInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) { isTrustedSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false); } //如果發(fā)送安裝請(qǐng)求的來源應(yīng)用不是系統(tǒng)可信的(不是一家人),則需要其他驗(yàn)證 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授權(quán) } else if (targetSdkVersion >= Build.VERSION_CODES.O //declaresAppOpPermission方法會(huì)查找所有的授權(quán)了Manifest.permission.REQUEST_INSTALL_PACKAGES的應(yīng)用信息,如果包含當(dāng)前這個(gè)來源應(yīng)用的話就不攔截 && !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); //如果需要展示安裝確認(rèn)頁的話 if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) { nextActivity.setClass(this, PackageInstallerActivity.class); } else { //其他的則按照Uri解析 Uri packageUri = intent.getData(); //設(shè)置了scheme為file或content的話則打開InstallStaging頁 if (packageUri != null && (packageUri.getScheme().equals(ContentResolver.SCHEME_FILE) || packageUri.getScheme().equals(ContentResolver.SCHEME_CONTENT))) { nextActivity.setClass(this, InstallStaging.class); //設(shè)置了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方法中首先會(huì)進(jìn)行一些安全性的驗(yàn)證,它是針對(duì)發(fā)送安裝請(qǐng)求的應(yīng)用的檢驗(yàn),比如應(yīng)用市場(chǎng)的安裝請(qǐng)求,沒問題的話會(huì)打開InstallStaging或者PackageInstallerActivity。
PackageInstallerActivity是安裝確認(rèn)頁,InstallStaging是實(shí)際安裝頁嗎?這得需要我們分析完它倆的源碼才能確定。
-
InstallStaging
InstallStaging的界面不重要,一個(gè)process view表示復(fù)制進(jìn)度而已。
@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()實(shí)際上就是new File(getDataDir(), "no_backup")。
getIntent().getData()就是要安裝的apk文件的URI,StagingAsyncTask的doInBackground方法中會(huì)把源apk文件復(fù)制到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; } //復(fù)制到當(dāng)前PackageInstaller應(yīng)用的私有目錄下 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; }復(fù)制結(jié)束后會(huì)finish并打開DeleteStagedFileOnResult這個(gè)Activity:
@Override protected void onPostExecute(Boolean success) { if (success) { Intent installIntent = new Intent(getIntent()); installIntent.setClass(InstallStaging.this, DeleteStagedFileOnResult.class); //注意傳入DeleteStagedFileOnResult的data此時(shí)已經(jīng)變成了臨時(shí)文件的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并沒設(shè)置任何界面,在其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方法中會(huì)刪除之前創(chuàng)建的臨時(shí)apk文件并finish掉自己。后續(xù)代碼分析中我們會(huì)看到,PackageInstallerActivity中在取消安裝時(shí)是會(huì)調(diào)用setResult方法的,所以會(huì)返回這里刪除掉臨時(shí)文件,所以它的作用就是干這個(gè)的,很巧妙是不是,PackageInstallerActivity完全感知不到臨時(shí)文件的存在。
-
PackageInstallerActivity
PackageInstallerActivity的界面上會(huì)顯示圖標(biāo)、appName、有一個(gè)自定義的ScrollView(可設(shè)置一個(gè)滾動(dòng)到底部時(shí)的回調(diào),在回調(diào)中改變“安裝”按鈕為可點(diǎn)擊狀態(tài),以此來盡量使用戶知悉應(yīng)用的所需權(quán)限)來盛放權(quán)限內(nèi)容、安裝和取消按鈕等。
onCreate中會(huì)調(diào)用checkIfAllowedAndInitiateInstall方法來驗(yàn)證當(dāng)前用戶的權(quán)限:
private void checkIfAllowedAndInitiateInstall() { final int installAppsRestrictionSource = mUserManager.getUserRestrictionSource( UserManager.DISALLOW_INSTALL_APPS, Process.myUserHandle()); ...//用戶是否允許安裝應(yīng)用的權(quán)限驗(yàn)證 if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) { initiateInstall(); } else { if (...) { ...//用戶是否允許安裝未知來源應(yīng)用的權(quán)限驗(yàn)證 } else { handleUnknownSources(); } } }handleUnknownSources中會(huì)檢查Manifest.permission.REQUEST_INSTALL_PACKAGES權(quán)限,如果通過則也會(huì)調(diào)用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. //即如果不支持運(yùn)行時(shí)權(quán)限的話(6.0以下)就在此列出所有有關(guān)安全的權(quán)限,支持運(yùn)行時(shí)權(quán)限的版本中,安全權(quán)限會(huì)在運(yùn)行時(shí)詢問授權(quán) boolean supportsRuntimePermissions = mPkgInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M; ...//列出安全權(quán)限 if (mScrollView == null) { // 不需要展示權(quán)限信息 mOk.setText(R.string.install); mOkCanInstall = true; } else { //需要展示權(quán)限信息的話設(shè)置一個(gè)滾動(dòng)到底部的監(jiān)聽,為了讓用戶往下滑動(dòng)以至于盡量使其知悉這些關(guān)鍵權(quán)限 mScrollView.setFullScrollAction(new Runnable() { @Override public void run() { mOk.setText(R.string.install); mOkCanInstall = true; } }); } }mOkCanInstall表示安裝按鈕可以觸發(fā)安裝操作:
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(); }可見,這里又打開了一個(gè)名為InstallInstalling的Activity。
PackageInstallerActivity檢驗(yàn)的是用戶相關(guān)的權(quán)限和合法性。 -
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(); //如果是系統(tǒng)應(yīng)用的話,那它們的應(yīng)用資源都和系統(tǒng)捆綁在一起的,所以直接安裝 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) { ...//復(fù)用 } 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 { //設(shè)置一個(gè)Observer,安裝完成后會(huì)觸發(fā)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(); }); //這個(gè)是更新界面安裝進(jìn)度的 mSessionCallback = new InstallSessionCallback(); } }其中PackageParser.parsePackageLite方法流程中會(huì)用到ApkAssets加載apk文件,它是直接使用了apk文件的路徑來加載的,這也就是為什么我們要使用InstallStaging多此一舉的原因,因?yàn)?code>根據(jù)Android系統(tǒng)的存儲(chǔ)權(quán)限要求,無法直接通過文件路徑訪問其他應(yīng)用的私有目錄下的文件,只能通過ContentProvider共享后用ContentResolver獲取。
然后在onResume中會(huì)啟動(dòng)一個(gè)后臺(tái)任務(wù)來執(zhí)行安裝:
@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 { //根據(jù)之前創(chuàng)建的mSessionId建立通道 session = getPackageManager().getPackageInstaller().openSession(mSessionId); } catch (IOException e) { return null; } //初始進(jìn)度 session.setStagingProgress(0); try { File file = new File(mPackageURI.getPath()); try (InputStream in = new FileInputStream(file)) { long sizeBytes = file.length(); //看來是寫入一個(gè)PackageInstaller相關(guān)的文件 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); //更新進(jìn)度 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其實(shí)持有了一個(gè)PackageInstallerSession,是通過它處理的,是一個(gè)AIDL文件。session.commit后如果驗(yàn)證成功會(huì)發(fā)送一個(gè)通知,InstallEventReceiver會(huì)接收到,然后觸發(fā)Observer,執(zhí)行l(wèi)aunchFinishBasedOnResult方法:
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(); }到這里我們已經(jīng)梳理完了安裝一個(gè)apk的大概流程,只知道安裝好像是把一個(gè)apk文件寫入到了某個(gè)地方,至于是什么地方,那就需要解析完P(guān)ackageInstallerSession之后才能知道了,限于篇幅,另起一篇博文。
-
總結(jié)
源應(yīng)用(觸發(fā)安裝請(qǐng)求的應(yīng)用)通過startActivity(intent)的方式發(fā)起安裝應(yīng)用的請(qǐng)求,intent中設(shè)置目標(biāo)信息,就是PackageInstaller應(yīng)用下AndroidManifest.xml中InstallStart 這個(gè)activity配置的intent-filter中的對(duì)應(yīng)內(nèi)容,然后和正常的Activity啟動(dòng)一樣,InstallStart就被打開了。
-
在InstallStart的onCreate方法中,首先會(huì)判斷發(fā)送安裝請(qǐng)求的應(yīng)用是否是系統(tǒng)信任的應(yīng)用,比如廠商自帶的應(yīng)用市場(chǎng)對(duì)于系統(tǒng)來說就是可信任的。如果不是系統(tǒng)自帶應(yīng)用則需要判斷targetSdkVersion是否有效,其次如果targetSdkVersion不小于26(8.0)的話,則還需要驗(yàn)證是否有
Manifest.permission.REQUEST_INSTALL_PACKAGES權(quán)限,沒達(dá)到這些要求的話就會(huì)被攔截,直接取消安裝finish,也就是說,這個(gè)類是驗(yàn)證發(fā)起安裝請(qǐng)求的應(yīng)用是否被允許安裝別的應(yīng)用。如果通過了驗(yàn)證,則會(huì)構(gòu)造nextActivity,nextActivity的目標(biāo)有兩種選擇,一種是跳到PackageInstallerActivity,一種是跳到InstallStaging。
intent設(shè)置的uri如果是
以package開頭的,說明安裝的apk是系統(tǒng)應(yīng)用包,則直接打開安裝確認(rèn)頁。intent設(shè)置的uri如果是以file或content開頭的,要安裝的apk文件肯定不在PackageInstaller應(yīng)用的私有存儲(chǔ)目錄中,
根據(jù)Android系統(tǒng)的存儲(chǔ)權(quán)限要求,無法直接通過文件路徑訪問其他應(yīng)用的私有目錄下的文件,只能通過ContentProvider共享后用ContentResolver獲取,而在后面的apk解析過程中ApkAssets會(huì)直接用到apk文件的路徑來加載的,所以為了避免權(quán)限問題,這里會(huì)先跳轉(zhuǎn)到InstallStaging,而InstallStaging中會(huì)通過ContentResolver獲取源apk文件,然后復(fù)制到PackageInstaller應(yīng)用的私有存儲(chǔ)目錄下的一個(gè)臨時(shí)目錄中(臨時(shí)文件位于當(dāng)前發(fā)起應(yīng)用內(nèi)的context.getDataDir()/no_backup目錄下,名叫“package.apk”),這樣在后續(xù)使用文件路徑訪問的時(shí)候就不會(huì)有存儲(chǔ)權(quán)限問題了。`然后復(fù)制完成后會(huì)打開一個(gè)叫DeleteStagedFileOnResult的Activity(它的作用是在安裝結(jié)束后自動(dòng)刪除臨時(shí)文件),在其onCreate方法中又打開了PackageInstallerActivity,所以,殊途同歸,最終都是打開PackageInstallerActivity。PackageInstallerActivity是安裝確認(rèn)頁,在這個(gè)頁你可以查看要安裝應(yīng)用的權(quán)限等信息,同時(shí)在這個(gè)頁會(huì)
驗(yàn)證用戶的權(quán)限和該安裝包是否合法,如果驗(yàn)證通過,則“安裝”按鈕就變?yōu)榭牲c(diǎn)擊狀態(tài),點(diǎn)擊后打開名為InstallInstalling的Activity。 InstallInstalling中會(huì)通過PackageInstaller的createSession方法把安裝包的信息傳過去,并得到一個(gè)mSessionId,然后創(chuàng)建一個(gè)AsyncTask,在其doInBackground方法中通過PackageInstaller的openSession(mSessionId)得到一個(gè)PackageInstaller.Session對(duì)象,它的相關(guān)方法都是調(diào)用了一個(gè)IPackageInstallerSession(AIDL)類型的IBinder開啟寫入,這也就是安裝的過程,最后在onPostExecute方法中調(diào)用session的commit方法,這個(gè)方法流程中會(huì)發(fā)送一個(gè)通知,InstallEventReceiver會(huì)接收,InstallEventReceiver在InstallInstalling的onCreate中注冊(cè)了一個(gè)Observer,這時(shí)這個(gè)Observer就會(huì)被回調(diào),在其中會(huì)判斷結(jié)果狀態(tài),如果安裝成功就打開InstallSuccess頁,如果失敗就打開InstallFailed頁。
Apk安裝的源碼分析(一)
最后編輯于 :
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
相關(guān)閱讀更多精彩內(nèi)容
- 前言 這是 Android 10 源碼分析系列的第 2 篇分支:android-10.0.0_r14全文閱讀大概 ...
- APK安裝流程系列文章整體內(nèi)容如下: APK安裝流程詳解0——前言APK安裝流程詳解1——有關(guān)"安裝ing"的實(shí)體...
- 第6步 啟動(dòng)InstallInstalling 該Activity是安裝等待界面,真正的安裝過程就是發(fā)生在這個(gè)界面...
- 前言 android系統(tǒng)里app有哪些類型及其安裝涉及目錄、所需權(quán)限是什么? apk安裝有幾種方式? apk安裝流...
- 問題:在部分機(jī)器,如果通過app內(nèi)部下載安裝一個(gè)app,可能會(huì)出現(xiàn) 解析包失敗 解決:這問題一般是跟機(jī)器相關(guān),走正...