--- title: 'PackageInstallerActivity 安裝' disqus: kyleAlien --- PackageInstallerActivity 安裝 === ## OverView of Content [TOC] ## 安裝 APK 安裝 APK 有以下幾種方式 1. 透過 Adb 指令進行安裝 2. 手動下載 APK 檔案,通過系統安裝器 PackageInstaller 安裝 APK 3. 手機開機時,安裝應用 4. 通過電腦 or 應用商店安裝 以下說明第二點,**手動下載 APK 檔案,通過系統安裝器 PackageInstaller 安裝 APK** ### 安裝重點步驟 * 重點步驟如下 1. 把 APK 的信息通過 IO 寫入 PackageInstaller#Session 中 2. 調用 Session#commit 後,透過 PKMS 安裝 APK 3. 將 APK 複製到 `/data` 目錄下 ### 隱式呼叫 InstallStart - 入口 * 查看在 PackageInstaller 目錄下的 [**AndroidManifest.xml 文件**](https://android.googlesource.com/platform/frameworks/base/+/master/packages/PackageInstaller/AndroidManifest.xml),可以看到 Activity 的入口 ```xml= // AndroidManifest.xml <activity android:name=".InstallStart" android:theme="@android:style/Theme.Translucent.NoTitleBar" 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="content" /> <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="package" /> <data android:scheme="content" /> </intent-filter> <intent-filter android:priority="1"> <action android:name="android.content.pm.action.CONFIRM_INSTALL" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> ``` :::warning * 在 Android 7.0 以後,如果 Intent 對外暴露 file://Uri 則會引發 FileUriExposedException 異常,7.0 以後必須透過 FileProvider 獲取 Uri ```java= // 版本差異的安裝 public void installApp(Object data) { Intent intent = new Intent(Intent.ACTION_VIEW); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_ACTIVITY_NEW_TASK); // 透過 FileProvider 獲取 Uri Uri contentUri = FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID + ".fileProvider", new File((String) data)); intent.setDataAndType(contentUri, "application/vnd.android.package-archive"); } else { // 7.0 以前可以透過 File uri 傳遞 intent.setDataAndType(Uri.fromFile(new File((String) data)), "application/vnd.android.package-archive"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } activity.startActivity(intent); } ``` ::: > ![](https://i.imgur.com/gwtJ2Hl.png) ### [PackageInstallerActivity](https://android.googlesource.com/platform/frameworks/base/+/master/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java) - 安裝 APK 視窗 * 系統安裝方式是透過 PackageInstallerActivity (在 [**packages 目錄**](https://android.googlesource.com/platform/frameworks/base/+/master/packages/PackageInstaller/src/com/android/packageinstaller)下),其中有幾個重要成員我們需要先知道他們的功能(如下表) | 使用類 | 功能 | | -------- | -------- | | PackageManager | 給應用 APP 提供功能的抽象,最終會透過 PKMS 來實現 | | IPackageManager | AIDL 接口,用來與 PKMS 通訊 | | AppOpsManager | 動態檢測權限(Android 4.3 後) | | PackageInstaller | 安裝、升級、卸載 APP 的服務 | | UserManager | 用來多用戶管理 | * 安裝 APK 步驟如下 1. **onCreate**:取得 intent,並取得要安裝的 Package 的 Uri,該 Uri 可以直接透過 intent#action 傳入,也可以透過 intent#data 帶入 > 主要差異是 SessionId ```java= // PackageInstallerActivity.java private int mSessionId = -1; private String mOriginatingPackage; // The package name corresponding to #mOriginatingUid ApplicationInfo mSourceInfo; PackageInstaller mInstaller; @Override protected void onCreate(Bundle icicle) { // 該 Activity 視窗,將該 Window 設定在最上層 getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); // 取得 PKMS 的通訊代理 mPm = getPackageManager(); ... 省略 // 取得安裝進程的代理 mInstaller = mPm.getPackageInstaller(); ... 省略 final Intent intent = getIntent(); // 呼叫者 mCallingPackage = intent.getStringExtra(EXTRA_CALLING_PACKAGE); // 呼叫者屬性 mCallingAttributionTag = intent.getStringExtra(EXTRA_CALLING_ATTRIBUTION_TAG); // 呼叫者的訊息 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; // Package Uri final Uri packageUri; if (PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction())) { // 已經準備好安裝 final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1); final PackageInstaller.SessionInfo info = mInstaller.getSessionInfo(sessionId); if (info == null || !info.sealed || info.resolvedBaseCodePath == null) { Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring"); finish(); return; } mSessionId = sessionId; packageUri = Uri.fromFile(new File(info.resolvedBaseCodePath)); mOriginatingURI = null; mReferrerURI = null; } else { // 尚未準備好安裝 mSessionId = -1; // 透過 intent#getData 取得 Uri packageUri = intent.getData(); mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI); mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER); } // 如果沒辦法取得 Uri 則直接 關閉 Activity if (packageUri == null) { setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI); finish(); return; } ... 不支援穿戴裝置 // @ processPackageUri 分析 boolean wasSetUp = processPackageUri(packageUri); if (mLocalLOGV) Log.i(TAG, "wasSetUp: " + wasSetUp); if (!wasSetUp) { return; } } ``` * **processPackageUri 分析 Uri 協議:取得 PackageInfo、創建 [PackageUtil](https://android.googlesource.com/platform/frameworks/base/+/master/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java)#AppSnippet**:支持兩種 scheme,^1.^ package (可以從 PKMS 取得)、^2.^ file (透過分析 Uri 的 Path,其實也是透過 PKMS 取得) :::info * AppSnippet 是 APK 的基本資訊,像是 APP Icon、name... 等等訊息 ::: ```java= // ContentResolver.java public static final String SCHEME_FILE = "file"; // ------------------------------------------------------------------- // PackageInstallerActivity.java private PackageUtil.AppSnippet mAppSnippet; PackageInfo mPkgInfo; PackageManager mPm; static final String SCHEME_PACKAGE = "package"; private boolean processPackageUri(final Uri packageUri) { mPackageURI = packageUri; final String scheme = packageUri.getScheme(); ...log 訊息 switch (scheme) { case SCHEME_PACKAGE: { // "package" try { mPkgInfo = mPm.getPackageInfo(packageUri.getSchemeSpecificPart(), PackageManager.GET_PERMISSIONS | PackageManager.MATCH_UNINSTALLED_PACKAGES); } catch (NameNotFoundException e) { } if (mPkgInfo == null) { ... 獲取失敗 return false; } CharSequence label = mPm.getApplicationLabel(mPkgInfo.applicationInfo); ... log mAppSnippet = new PackageUtil.AppSnippet(label, mPm.getApplicationIcon(mPkgInfo.applicationInfo)); } break; case ContentResolver.SCHEME_FILE: { // "file" // 透過 path 獲取 PackageInfo File sourceFile = new File(packageUri.getPath()); // 對 parsed 進行進一步的處理,並獲得 PackageInfo mPkgInfo = PackageUtil.getPackageInfo(this, sourceFile, PackageManager.GET_PERMISSIONS); // Check for parse errors if (mPkgInfo == null) { ... 獲取失敗 return false; } ... log mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile); } break; default: { throw new IllegalArgumentException("Unexpected URI scheme " + packageUri); } } return true; } ``` > ![](https://i.imgur.com/6A5AtAT.png) 2. **onResume**:透過 bindUi 方法,顯示安裝提示 Dialog ```java= // PackageInstallerActivity.java @Override protected void onResume() { super.onResume(); ... log if (mAppSnippet != null) { // @ 分析 build ui bindUi(); // @ checkIfAllowedAndInitiateInstall // 判斷是否是未知來源的 應用, // 如果開啟允須安裝未知來源的選項,則直接進行初始化安裝 checkIfAllowedAndInitiateInstall(); } if (mOk != null) { mOk.setEnabled(mEnableOk); } } private void checkIfAllowedAndInitiateInstall() { // 首先檢查用戶對應用安裝的限制 final int installAppsRestrictionSource = mUserManager.getUserRestrictionSource( UserManager.DISALLOW_INSTALL_APPS, Process.myUserHandle()); // 不允許安裝 if ((installAppsRestrictionSource & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) { ... log showDialogInner(DLG_INSTALL_APPS_RESTRICTED_FOR_USER); return; } else if (installAppsRestrictionSource != UserManager.RESTRICTION_NOT_SET) { ... log startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS)); finish(); return; } // 是否是未知來源、是否允許未知來源安裝 ? if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) { if (mLocalLOGV) Log.i(TAG, "install allowed"); // @ initiateInstall 直接進行初始安裝 initiateInstall(); } else { // Check for unknown sources restrictions. final int unknownSourcesRestrictionSource = mUserManager.getUserRestrictionSource( UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, Process.myUserHandle()); final int unknownSourcesGlobalRestrictionSource = mUserManager.getUserRestrictionSource( UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, Process.myUserHandle()); final int systemRestriction = UserManager.RESTRICTION_SOURCE_SYSTEM & (unknownSourcesRestrictionSource | unknownSourcesGlobalRestrictionSource); // 跳出未知來源警告視窗 if (systemRestriction != 0) { if (mLocalLOGV) Log.i(TAG, "Showing DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER"); showDialogInner(DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER); } else if (unknownSourcesRestrictionSource != UserManager.RESTRICTION_NOT_SET) { startAdminSupportDetailsActivity(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES); } else if (unknownSourcesGlobalRestrictionSource != UserManager.RESTRICTION_NOT_SET) { startAdminSupportDetailsActivity( UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY); } else { // 處理未知來源 APK handleUnknownSources(); } } } private void bindUi() { // mAlert 是父類 member、類型是 AlertController mAlert.setIcon(mAppSnippet.icon); mAlert.setTitle(mAppSnippet.label); mAlert.setView(R.layout.install_content_view); // 按下確定 mAlert.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.install), (ignored, ignored2) -> { if (mOk.isEnabled()) { if (mSessionId != -1) { mInstaller.setPermissionsResult(mSessionId, true); finish(); } else { // @ 分析 startInstall startInstall(); } } }, null); // 取消 Dialog mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.cancel), (ignored, ignored2) -> { // Cancel and finish setResult(RESULT_CANCELED); if (mSessionId != -1) { mInstaller.setPermissionsResult(mSessionId, false); } finish(); }, null); setupAlert(); // 確定 Button mOk = mAlert.getButton(DialogInterface.BUTTON_POSITIVE); mOk.setEnabled(false); if (!mOk.isInTouchMode()) { mAlert.getButton(DialogInterface.BUTTON_NEGATIVE).requestFocus(); } } ``` > ![](https://i.imgur.com/iWVPWF3.png) ### [PackageInstallerActivity](https://android.googlesource.com/platform/frameworks/base/+/master/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java) - 啟動 APK 過程 * 在上個小節我們有介紹 SDK 如何建構安裝 Dialog & 檢查機制,如果都通過後,使用者點擊安裝就會觸發 PackageInstallerActivity#startInstall 方法 1. 帶入 PackageInstallerActivity 分析好的 ApplicationInfo,下一個 Activity 會使用到 2. 將使用者帶入的 Uri 再次帶入 InstallInstallingActivity 3. 關閉當前 Actiivty,透過 intent **呼叫 InstallInstalling Activity** ```java= // PackageInstallerActivity.java private void startInstall() { // Start subactivity to actually install the application Intent newIntent = new Intent(); // 1. 必須放入 ApplicationInfo,在下一個 Activity 會取出來用 newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, mPkgInfo.applicationInfo); // 2. 將使用者帶入的 Uri 再次帶入 InstallInstallingActivity newIntent.setData(mPackageURI); // 3. 目標跳轉 InstallInstalling Activity newIntent.setClass(this, InstallInstalling.class); String installerPackageName = getIntent().getStringExtra( Intent.EXTRA_INSTALLER_PACKAGE_NAME); if (mOriginatingURI != null) { newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI); } if (mReferrerURI != null) { newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI); } if (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) { newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid); } if (installerPackageName != null) { newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, installerPackageName); } if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) { newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true); } newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); if (mLocalLOGV) Log.i(TAG, "downloaded app uri=" + mPackageURI); startActivity(newIntent); finish(); } ``` > ![](https://i.imgur.com/sqscTTH.png) ## [InstallInstalling](https://android.googlesource.com/platform/frameworks/base/+/master/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java) 概述 InstallInstalling 主要功能是在透過 **IPackageInstaller** ^1.^ 向 PackageInstallerService 發送 APK 訊息、^2.^ 處理 PackageInstallerService 回調訊息 (這也是一個 AIDL) ### [InstallInstalling](https://android.googlesource.com/platform/frameworks/base/+/master/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java) 註冊、監聽廣播 * InstallInstalling 註冊、監聽廣播 1. 透過 intent 取得 ApplicationInfo、mPackageURI ```java= // InstallInstalling.java private Uri mPackageURI; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); ApplicationInfo appInfo = getIntent() .getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO); // 使用者帶入的 Uri mPackageURI = getIntent().getData(); ... } ``` * 判斷 Scheme 做不同行為,根據上一個 Activity 我們知道這裡只接受 2 種 Uri Scheme 1. package:則就是已經安裝過的 APK 2. file:^1.^ **跳出 Dialog 視窗**(以下為第一次安裝,並且沒有被回收) 、^2.^**輕量級分析 APK**、^3.^註冊廣播監聽 GENERATE_NEW_ID、^4.^ 透過代理創建 Session 後取得 session id :::success createSession 方法是透過創建 IPackageInstaller 代理與 PackageInstallerService 通訊 ::: ```java= // InstallInstalling.java @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... // package 代表是已存在 APK if ("package".equals(mPackageURI.getScheme())) { try { getPackageManager().installExistingPackage(appInfo.packageName); launchSuccess(); } catch (PackageManager.NameNotFoundException e) { launchFailure(PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null); } } else { // 1. 根據 URI 創建對應的 File 檔案 final File sourceFile = new File(mPackageURI.getPath()); // 取得 Apk 快照 PackageUtil.AppSnippet as = PackageUtil.getAppSnippet(this, appInfo, sourceFile); mAlert.setIcon(as.icon); mAlert.setTitle(as.label); mAlert.setView(R.layout.install_content_view); // 取消按鈕 mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.cancel), (ignored, ignored2) -> { if (mInstallingTask != null) { mInstallingTask.cancel(true); } if (mSessionId > 0) { // 取消 Package installer 的 session getPackageManager().getPackageInstaller().abandonSession(mSessionId); mSessionId = 0; } setResult(RESULT_CANCELED); finish(); }, null); setupAlert(); requireViewById(R.id.installing).setVisibility(View.VISIBLE); // 判斷 Activity 是否是有被回收 if (savedInstanceState != null) { // 取出被回收前的 SessionID、InstallID // mSessionId 是 Apk 的 Session id mSessionId = savedInstanceState.getInt(SESSION_ID); // mInstallId 是安裝事件的 id mInstallId = savedInstanceState.getInt(INSTALL_ID); // 2. 向 InstallEventReceiver 添加安裝成功的觀察者 try { // 回調會觸發 launchFinishBasedOnResult 方法 InstallEventReceiver.addObserver(this, mInstallId, this::launchFinishBasedOnResult); } catch (EventResultPersister.OutOfIdsException e) { // Does not happen } } else { // 創建 Session 參數 PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( PackageInstaller.SessionParams.MODE_FULL_INSTALL); params.setInstallAsInstantApp(false); params.setReferrerUri(getIntent().getParcelableExtra(Intent.EXTRA_REFERRER)); params.setOriginatingUri(getIntent() .getParcelableExtra(Intent.EXTRA_ORIGINATING_URI)); params.setOriginatingUid(getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID, UID_UNKNOWN)); params.setInstallerPackageName(getIntent().getStringExtra( Intent.EXTRA_INSTALLER_PACKAGE_NAME)); params.setInstallReason(PackageManager.INSTALL_REASON_USER); // 3-1. 根據 Uri 創建一個對應的 File,準備進行輕量級解析 File file = new File(mPackageURI.getPath()); try { final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); // 輕量解析 Package final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite( input.reset(), file, /* flags */ 0); if (result.isError()) { // parse 失敗 Log.e(LOG_TAG, "Cannot parse package " + file + ". Assuming defaults."); Log.e(LOG_TAG, "Cannot calculate installed size " + file + ". Try only apk size."); params.setSize(file.length()); } else { final PackageLite pkg = result.getResult(); params.setAppPackageName(pkg.getPackageName()); params.setInstallLocation(pkg.getInstallLocation()); params.setSize( PackageHelper.calculateInstalledSize(pkg, params.abiOverride)); } } catch (IOException e) { Log.e(LOG_TAG, "Cannot calculate installed size " + file + ". Try only apk size."); params.setSize(file.length()); } try { // 向 InstallEventReceiver 添加安裝成功的觀察者 // 3-2. 添加廣播監聽 mInstallId = InstallEventReceiver .addObserver(this, EventResultPersister.GENERATE_NEW_ID, this::launchFinishBasedOnResult); } catch (EventResultPersister.OutOfIdsException e) { launchFailure(PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null); } try { // 3-3. 透過代理創建 Session 後取得 session id mSessionId = getPackageManager().getPackageInstaller().createSession(params); } catch (IOException e) { launchFailure(PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null); } } // 取消按鈕 mCancelButton = mAlert.getButton(DialogInterface.BUTTON_NEGATIVE); } } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); // 回收 Activity 前儲存資料 outState.putInt(SESSION_ID, mSessionId); outState.putInt(INSTALL_ID, mInstallId); } ``` 安裝流程圖,以下 **有相同的地方就是添加 InstallEventReceiver 的監聽**,最終不論成功還是失敗都會關閉該 Actiivty > ![](https://i.imgur.com/HgK8MDM.png) 2. onResume 方法會創建一個異步任務,並且取得 PackageInstaller.SessionInfo,並判斷該 **^1.^ Session 不為 null、^2.^ 不在活動中** ```java= // InstallInstalling.java @Override protected void onResume() { super.onResume(); // This is the first onResume in a single life of the activity if (mInstallingTask == null) { PackageInstaller installer = getPackageManager().getPackageInstaller(); PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId); // 判斷該 Session 可用的兩個條件 if (sessionInfo != null && !sessionInfo.isActive()) { // @ InstallingAsyncTask 是內部類 mInstallingTask = new InstallingAsyncTask(); // 執行異步任務 mInstallingTask.execute(); } else { // we will receive a broadcast when the install is finished mCancelButton.setEnabled(false); setFinishOnTouchOutside(false); } } } ``` > ![](https://i.imgur.com/MM7pbaF.png) ### InstallingAsyncTask 異步任務 * InstallInstalling#InstallingAsyncTask 創建一個異步任務 1. 在 doInBackground 方法 **讀取 Uri Path 的 APK 內容**,**對 ==PackageInstaller#Session 寫入==** ```java= // InstallInstalling.java // 內部類,可取得外部資訊 private final class InstallingAsyncTask extends AsyncTask<Void, Void, PackageInstaller.Session> { volatile boolean isDone; @Override protected PackageInstaller.Session doInBackground(Void... params) { PackageInstaller.Session session; try { // 取得 Session session = getPackageManager().getPackageInstaller().openSession(mSessionId); } catch (IOException e) { synchronized (this) { isDone = true; notifyAll(); } return null; } // Session 進度設定為 0 session.setStagingProgress(0); try { // 透過路徑 取得 APK 檔案 File file = new File(mPackageURI.getPath()); // 讀取 APK 檔案 try (InputStream in = new FileInputStream(file)) { long sizeBytes = file.length(); // 寫入 Session try (OutputStream out = session .openWrite("PackageInstaller", 0, sizeBytes)) { byte[] buffer = new byte[1024 * 1024]; while (true) { int numRead = in.read(buffer); // 讀取完畢 numRead 就是 -1 if (numRead == -1) { session.fsync(out); break; } if (isCancelled()) { session.close(); break; } // 通過 IO 流,寫入到 PackageInstaller#Session 中 out.write(buffer, 0, numRead); if (sizeBytes > 0) { float fraction = ((float) numRead / (float) sizeBytes); // 更新進度 session.addProgress(fraction); } } } } return session; } catch (IOException | SecurityException e) { Log.e(LOG_TAG, "Could not write package", e); session.close(); return null; } finally { synchronized (this) { isDone = true; notifyAll(); } } } ... } ``` > ![](https://i.imgur.com/CuhvsNQ.png) 2. 如果成功在 onPostExecute 方法呼叫 Session#**commit** (進入系統 PackageInstaller.Session 服務) ```java= // InstallInstalling.java // 內部類,可取得外部資訊 private final class InstallingAsyncTask extends AsyncTask<Void, Void, PackageInstaller.Session> { volatile boolean isDone; ... @Override protected void onPostExecute(PackageInstaller.Session session) { // 判斷 doInBackground 傳入的 session 參數 if (session != null) { Intent broadcastIntent = new Intent(BROADCAST_ACTION); broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); broadcastIntent.setPackage(getPackageName()); broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId); // 創建 PendingIntent PendingIntent pendingIntent = PendingIntent.getBroadcast( InstallInstalling.this, mInstallId, broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); // 透過 Session#commit 發送 PendingIntent // @ Session#commit 分析 session.commit(pendingIntent.getIntentSender()); mCancelButton.setEnabled(false); setFinishOnTouchOutside(false); } else { // 拋棄使用的 Session getPackageManager().getPackageInstaller().abandonSession(mSessionId); if (!isCancelled()) { launchFailure(PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INVALID_APK, null); } } } } ``` > ![](https://i.imgur.com/aT47X45.png) * PackageInstaller.Session 內是透過 IPackageInstallerSession 進行進程通訊 ```java= // PackageInstaller.java public static class Session implements Closeable { protected final IPackageInstallerSession mSession; public void commit(@NonNull IntentSender statusReceiver) { try { // @ PackageInstallerSession#commit mSession.commit(statusReceiver, false); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } } ``` ## 安裝 APK 簡單步驟如下 1. 把 APK 的信息通過 IO 寫入 PackageInstaller#Session 中 2. 調用 Session#commit 後,透過 PKMS 安裝 APK 3. 將 APK 複製到 `/data` 目錄下 ### [PackageInstallerActivity](https://android.googlesource.com/platform/frameworks/base/+/master/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java) - 系統安裝 APK * PackageInstallerActivity 安裝請看另外一篇文章 [**PackageInstallerActivity 安裝**](https://hackmd.io/b_xKfbCISS-58VSvq9szEw?view),最終會呼叫到系統的 PackageInstaller 進程的 Session#commit 方法 ### PackageInstaller - commit 呼叫 [Session](https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/pm/PackageInstallerSession.java) * 到這一步就在系統進程 (SystemServer) 中,而 IPackageInstallerSession 的實作類是 [**PackageInstallerSession**](https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/pm/PackageInstallerSession.java),之後透過 PackageInstallerSession#commit 方法發送 MSG_ON_SESSION_SEALED 訊息 ```java= // PackageInstaller.java public static class Session implements Closeable { protected final IPackageInstallerSession mSession; public void commit(@NonNull IntentSender statusReceiver) { try { // @ PackageInstallerSession#commit mSession.commit(statusReceiver, false); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } } // -------------------------------------------------------------- // PackageInstallerSession.java private final Handler mHandler; @Override public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) { ... // 避免重複提交 if (!markAsSealed(statusReceiver, forTransfer)) { return; } ... // @ dispatchSessionSealed 方法 dispatchSessionSealed(); } private void dispatchSessionSealed() { // @ MSG_ON_SESSION_SEALED mHandler.obtainMessage(MSG_ON_SESSION_SEALED).sendToTarget(); } ``` > ![](https://i.imgur.com/YqTFqmH.png) ### [PackageInstallerSession](https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/pm/PackageInstallerSession.java) - 驗證處理 * 該 Handler 是在 PackageInstallerSession 建構函數中被初始化並設定 callback 對象,所以可以在 callback 中接到以下訊息 1. MSG_ON_SESSION_SEALED:密封處理 2. MSG_STREAM_VALIDATE_AND_COMMIT:檢查所有 Session 是否都準備好 3. **MSG_INSTALL**:進行安裝 APK 前的 **驗證行為** ```java= // PackageInstallerSession.java private final Handler.Callback mHandlerCallback = new Handler.Callback() { @Override public boolean handleMessage(Message msg) { switch (msg.what) { case MSG_ON_SESSION_SEALED: // @ 1. handleSessionSealed 方法 handleSessionSealed(); break; case MSG_STREAM_VALIDATE_AND_COMMIT: // @ 2. handleStreamValidateAndCommit 方法 handleStreamValidateAndCommit(); break; case MSG_INSTALL: // @ 3. handleInstall 方法 handleInstall(); ... 省略其他 case } return true; } } public PackageInstallerSession(PackageInstallerService.InternalCallback callback, /* 省略部分參數*/ ) { ... // @ mHandlerCallback member mHandler = new Handler(looper, mHandlerCallback); ... } private void handleSessionSealed() { // 密封處理,防止我們創建的 Linked 被改變 mCallback.onSessionSealedBlocking(this); // @ dispatchStreamValidateAndCommit 分析 dispatchStreamValidateAndCommit(); } private void dispatchStreamValidateAndCommit() { // 第二步 @MSG_STREAM_VALIDATE_AND_COMMIT mHandler.obtainMessage(MSG_STREAM_VALIDATE_AND_COMMIT).sendToTarget(); } private void handleStreamValidateAndCommit() { PackageManagerException unrecoverableFailure = null; // 檢查 Session 是否都準備好 boolean allSessionsReady = false; try { allSessionsReady = streamValidateAndCommit(); } catch (PackageManagerException e) { unrecoverableFailure = e; } ... 省略部分 if (!allSessionsReady) { // 尚未準備好則會直接返回 return; } // 第三步 @MSG_INSTALL mHandler.obtainMessage(MSG_INSTALL).sendToTarget(); } ``` * handleInstall 方法後的過程較為反鎖,**主要是在做 ++==驗證==++ 的動作**,這裡只列出幾個重點方法,但是最終安裝還是會透過 **PKMS**#**==installStage==** ```java= // PackageInstallerSession.java private final PackageManagerService mPm; private void handleInstall() { ... 省略部分 verify(); } private void verify() { try { verifyNonStaged(); } catch (PackageManagerException e) { final String completeMsg = ExceptionUtils.getCompleteMessage(e); onSessionVerificationFailure(e.error, completeMsg); } } private void verifyNonStaged() throws PackageManagerException { // @ prepareForVerification 方法 final PackageManagerService.VerificationParams verifyingSession = prepareForVerification(); if (verifyingSession == null) { return; } ... 省略部分 if (isMultiPackage()) { ... 省略部分 } else { mPm.verifyStage(verifyingSession); } } @Nullable private PackageManagerService.VerificationParams prepareForVerification() throws PackageManagerException { ... 省略部分 synchronized (mLock) { // @ makeVerificationParamsLocked return makeVerificationParamsLocked(); } } private PackageManagerService.VerificationParams makeVerificationParamsLocked() { final IPackageInstallObserver2 localObserver; if (!hasParentSessionId()) { // Avoid attaching this observer to child session since they won't use it. localObserver = new IPackageInstallObserver2.Stub() { ... @Override public void onPackageInstalled(String basePackageName, int returnCode, String msg, Bundle extras) { if (returnCode == INSTALL_SUCCEEDED) { // @ onVerificationComplete onVerificationComplete(); } else { onSessionVerificationFailure(returnCode, msg); } } }; } else { localObserver = null; } ... 省略部分 } private void onVerificationComplete() { // Staged sessions will be installed later during boot if (isStaged()) { ... return; } // @ 分析 install 方法 install(); } private void install() { try { // @ 分析 installNonStaged installNonStaged(); } /* 省略 catch */ } private void installNonStaged() throws PackageManagerException { // 創建安裝參數類 final PackageManagerService.InstallParams installingSession = makeInstallParams(); if (installingSession == null) { ... 創建失敗就拋出 } if (isMultiPackage()) { // 安裝多個 APK ... 省略部分 } else { // @ PKMS#installStage 分析 mPm.installStage(installingSession); } } ``` * 接著看 PKMS#installStage 方法 ### [PKMS](https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/pm/PackageManagerService.java) - 複製 APK 檔案 * 在 PackageInstallerSession 驗證完 APK 後,就會讓 PKMS 對自己的 Handler 發送 **INIT_COPY** 訊息 (並攜帶 InstallParams 參數),進行複製 APK 的行為 (從 PKMS#**installStage** 分析) :::info InstallParams 是 APK 數據 ::: ```java= // PackageManagerService.java void installStage(InstallParams params) { final Message msg = mHandler.obtainMessage(INIT_COPY); msg.obj = params; ... 省略 trace mHandler.sendMessage(msg); } class PackageHandler extends Handler { public void handleMessage(Message msg) { try { doHandleMessage(msg); } finally { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); } } void doHandleMessage(Message msg) { switch (msg.what) { case INIT_COPY: { // HandlerParams 是抽象類 HandlerParams params = (HandlerParams) msg.obj; if (params != null) { ... 省略 trace & log // @ startCopy 方法 params.startCopy(); } } } } } ``` > ![](https://i.imgur.com/grrHE0j.png) * HandlerParams 是抽象類,會依照是 多個安裝 or 單個安裝來進行不同的行為 (這裡來看 單個安裝 InstallParams 類),主要做三件事情 :::success * HandlerParams 就是一種模板設計 ```java= // PackageManagerService.java private abstract class HandlerParams { final void startCopy() { ... log 訊息 handleStartCopy(); handleReturnCode(); } abstract void handleStartCopy(); abstract void handleReturnCode(); } ``` ::: 1. handleStartCopy:`getMinimalPackageInfo` 輕量分析 apk 檔案 2. handleReturnCode 又分為兩個部份 - 前半段 copyApk:複製 base.apk 檔案 - handleReturnCode 後半段 processInstallRequestsAsync:異步安裝 Apk ```java= // PackageManagerService.java // 實作類 class InstallParams extends HandlerParams { public void handleStartCopy() { if ((installFlags & PackageManager.INSTALL_APEX) != 0) { mRet = INSTALL_SUCCEEDED; return; } // Package 進行輕量分析 PackageInfoLite pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mContext, mPackageLite, origin.resolvedPath, installFlags, packageAbiOverride); ... 省略部分 // overrideInstallLocation 會依照策略改變 (這邊暫不分析) mRet = overrideInstallLocation(pkgLite); } @Override void handleReturnCode() { // @ 查看 processPendingInstall processPendingInstall(); } private void processPendingInstall() { // 前半段 InstallArgs args = createInstallArgs(this); if (mRet == PackageManager.INSTALL_SUCCEEDED) { // @ InstallArgs#copyApk 複製 Apk mRet = args.copyApk(); } ... 省略部分 // 後半段 if (mParentInstallParams != null) { mParentInstallParams.tryProcessInstallRequest(args, mRet); } else { PackageInstalledInfo res = createPackageInstalledInfo(mRet); // @ processInstallRequestsAsync processInstallRequestsAsync( res.returnCode == PackageManager.INSTALL_SUCCEEDED, Collections.singletonList(new InstallRequest(args, res))); } } } ``` > ![](https://i.imgur.com/RnO3ykf.png) * InstallParams#processPendingInstall 分析: 1. 前半段 複製 Apk:InstallArgs 同樣是個**抽象類**,目前的實做有兩種,這裡分析 **FileInstallArgs 類** | InstallArgs 子類 | 說明 | | - | - | | MoveInstallArgs | 用於處理已安裝 APK 在儲存中移動 | | FileInstallArgs | 非安裝到 `mnt/saec` 的 APK(也就是 **安裝到 `/data` 目錄下的 APK**) | ```java= // PackageManagerService.java private InstallArgs createInstallArgs(InstallParams params) { if (params.move != null) { return new MoveInstallArgs(params); } else { return new FileInstallArgs(params); } } static abstract class InstallArgs { ... 省略部分 abstract int copyApk(); } // --------------------------------------------------------------- // PackageManagerService.java final PackageInstallerService mInstallerService; // 實作類 class FileInstallArgs extends InstallArgs { private File codeFile; private File resourceFile; int copyApk() { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyApk"); try { // @ doCopyApk return doCopyApk(); } finally { ... Trace } } private int doCopyApk() { ... try { final boolean isEphemeral = (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0; // 創建臨時文件儲存目錄 final File tempDir = mInstallerService.allocateStageDirLegacy(volumeUuid, isEphemeral); // 暫時目錄賦予 codeFile codeFile = tempDir; } /* 省略部分 */ // @ copyPackage 追蹤 int ret = PackageManagerServiceUtils.copyPackage( origin.file.getAbsolutePath(), codeFile); if (ret != PackageManager.INSTALL_SUCCEEDED) { // 複製 Apk 失敗 Slog.e(TAG, "Failed to copy package"); return ret; } } } ``` :::success * 透過 PackageManagerServiceUtils#**copyPackage** 進行複製,**對目標資料夾 (一般應用是 `/data/app` 下臨時資料夾) 寫入一個權限為 644 的 base.apk 的文件** > 下面那一串就是臨時資料夾 > ![](https://i.imgur.com/O2WDtcl.png) ::: ```java= // PackageManagerServiceUtils.java public static int copyPackage(String packagePath, File targetDir) { if (packagePath == null) { return PackageManager.INSTALL_FAILED_INVALID_URI; } try { final File packageFile = new File(packagePath); final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); // Parse 輕量級分析 final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite( input.reset(), packageFile, /* flags */ 0); if (result.isError()) { Slog.w(TAG, "Failed to parse package at " + packagePath); return result.getErrorCode(); } // 結果也包括 apk 的 source 路徑 final PackageLite pkg = result.getResult(); // @ copyFile 方法,複製一個 base.apk copyFile(pkg.getBaseApkPath(), targetDir, "base.apk"); ... return PackageManager.INSTALL_SUCCEEDED; } catch (IOException | ErrnoException e) { Slog.w(TAG, "Failed to copy package at " + packagePath + ": " + e); return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; } } private static void copyFile(String sourcePath, File targetDir, String targetName) throws ErrnoException, IOException { ... // 用目標資料夾 + 目標檔案組成 File final File targetFile = new File(targetDir, targetName); // 644 的檔案 final FileDescriptor targetFd = Os.open(targetFile.getAbsolutePath(), O_RDWR | O_CREAT, 0644); // 改變權限 Os.chmod(targetFile.getAbsolutePath(), 0644); FileInputStream source = null; try { // 開啟 input IO 流,準備寫入 <目標資料夾>/base.apk source = new FileInputStream(sourcePath); FileUtils.copy(source.getFD(), targetFd); } finally { IoUtils.closeQuietly(source); } } ``` 2. 後半段 異步安裝 APK,在這裡會 **確保安裝環境**,主要分析 processInstallRequestsAsync 方法 > 透過 Handler#post 來達成異步 ```java= // PackageManagerService.java private void processInstallRequestsAsync(boolean success, List<InstallRequest> installRequests) { mHandler.post(() -> { List<InstallRequest> apexInstallRequests = new ArrayList<>(); List<InstallRequest> apkInstallRequests = new ArrayList<>(); for (InstallRequest request : installRequests) { if ((request.args.installFlags & PackageManager.INSTALL_APEX) != 0) { apexInstallRequests.add(request); } else { apkInstallRequests.add(request); } } ... 省略部分 if (success) { for (InstallRequest request : apkInstallRequests) { // 預安裝:在安裝前確保安裝環境可靠 // 如果安裝環境不可靠則會清除複製的 apk request.args.doPreInstall(request.installResult.returnCode); } synchronized (mInstallLock) { // 重點 // @ 分析 installPackagesTracedLI 方法 installPackagesTracedLI(apkInstallRequests); } for (InstallRequest request : apkInstallRequests) { // 如果安裝失敗,清除無用訊息 request.args.doPostInstall( request.installResult.returnCode, request.installResult.uid); } } for (InstallRequest request : apkInstallRequests) { restoreAndPostInstall(request.args.user.getIdentifier(), request.installResult, new PostInstallData(request.args, request.installResult, null)); } }); } private void installPackagesTracedLI(List<InstallRequest> requests) { try { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackages"); // @installPackagesLI installPackagesLI(requests); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } } ``` > ![](https://i.imgur.com/itqvpw3.png) ### [PKMS](https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/pm/PackageManagerService.java) - installPackagesLI 安裝、優化 Package * 回顧一下,FileInstallArgs 在複製要安裝的 apk 到臨時目錄後 (複製名稱為 base.apk) 會呼叫 **PKMS#installPackagesLI 方法** * PackageManagerService#installPackagesLI 方法有以下幾個重點方法步驟,我們先概略知道一下他們的功能 | 方法名 | 功能 | | -------- | -------- | | installPackagesLI | FileInstallArgs 呼叫該方法,開始進行安裝的入口 | | preparePackageLI | 分析 Package 並對其進行初始驗證 | | scanPackageTracedLI | 透過先前分析的 prepareResult 結果進行掃描 | | ReconcileRequest | Reconcile 調和,檢查裝置中驗證的 Package,確保安裝成功 | | commitPackagesLocked | 準備 commit 提交物件(CommitRequest),提交所有掃描的 Package 狀態,這是安裝過程中唯一可以修改系統狀態的地方,必須在此階段之前確定所有錯誤 | | executePostCommitSteps | 執行 commit,**為新的程式路徑準備應用程式配置文件,並 ++檢查 dex 優化++**,完成 APK 安裝 | ```java= // PackageManagerService.java private void installPackagesLI(List<InstallRequest> requests) { ... // <包名、掃描結果> final Map<String, PrepareResult> prepareResults = new ArrayMap<>(requests.size()); // <包名、安裝資訊> final Map<String, PackageInstalledInfo> installResults = new ArrayMap<>(requests.size()); ... boolean success = false; try { ... // 遍歷安裝需求 for (InstallRequest request : requests) { final PrepareResult prepareResult; try { ... // 1. 分析 Package 並對其進行初始驗證 prepareResult = preparePackageLI(request.args, request.installResult); } /* 省略 catch(會 return)、finally (Trace) */ // 設定返回為成功 request.installResult.setReturnCode(PackageManager.INSTALL_SUCCEEDED); // 安裝來源包名 request.installResult.installerPackageName = request.args.installSource.installerPackageName; // APK 包名 final String packageName = prepareResult.packageToScan.getPackageName(); prepareResults.put(packageName, prepareResult); installResults.put(packageName, request.installResult); installArgs.put(packageName, request.args); try { // 2. 透過先前分析的 prepareResult 結果進行掃描 final ScanResult result = scanPackageTracedLI( prepareResult.packageToScan, prepareResult.parseFlags, prepareResult.scanFlags, System.currentTimeMillis(), request.args.user, request.args.abiOverride); ... 省略部分 } catch (PackageManagerException e) { request.installResult.setError("Scanning Failed.", e); return; } } // 3. Reconcile 調和,檢查裝置中驗證的 Package,確保安裝成功 ReconcileRequest reconcileRequest = new ReconcileRequest(preparedScans, installArgs, installResults, prepareResults, mSharedLibraries, Collections.unmodifiableMap(mPackages), versionInfos, lastStaticSharedLibSettings); CommitRequest commitRequest = null; synchronized (mLock) { Map<String, ReconciledPackage> reconciledPackages; ... 省略部分 try { ... Trace 訊息 // 4. 準備 commit 提交物件(CommitRequest),提交所有掃描的 Package 狀態,這是安裝過程中唯一可以修改系統狀態的地方 // 必須在此階段之前確定所有錯誤 commitRequest = new CommitRequest(reconciledPackages, mUserManager.getUserIds()); // 提交掃描的 Package commitPackagesLocked(commitRequest); success = true; } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } } // 5. 執行 commit,完成 APK 安裝 // @ 分析 executePostCommitSteps executePostCommitSteps(commitRequest); } /* 省略 finally */ } // Function ``` > ![](https://i.imgur.com/CxvK5Og.png) * **PKMS#executePostCommitSteps** 安裝 APK && 為新 APP 配置文件 && 檢查是否需要進行 dex 優化,這有分為兩種情況 1. 裝置沒有這個應用 (直接安裝):準備 APP 路徑、配置文件 2. 裝置已經有這個應用 (替換更新):清除原來 APP 數據 (看是否有設置)、重新生成 APP 數據目錄等步驟 ```java= // PackageManagerService.java private void executePostCommitSteps(CommitRequest commitRequest) { final ArraySet<IncrementalStorage> incrementalStorages = new ArraySet<>(); for (ReconciledPackage reconciledPkg : commitRequest.reconciledPackages.values()) { ... 省略部分 // 1. 進行 package 安裝 // @ prepareAppDataAfterInstallLIF 方法 prepareAppDataAfterInstallLIF(pkg); // 2. 清除資料 (如果有需要的話) if (reconciledPkg.prepareResult.clearCodeCache) { clearAppDataLIF(pkg, UserHandle.USER_ALL, FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL | Installer.FLAG_CLEAR_CODE_CACHE_ONLY); } // 查看是否需要替換 App if (reconciledPkg.prepareResult.replace) { // 喚醒 Package 更新 mDexManager.notifyPackageUpdated(pkg.getPackageName(), pkg.getBaseApkPath(), pkg.getSplitCodePaths()); } // 3. 為新 APP 的路徑準備 ++應用程序配置文件++, // 這需要在調用 dexopt 之前完成 // 以便任何安裝時配置文件都可以用於優化 mArtManagerService.prepareAppProfiles( pkg, resolveUserIds(reconciledPkg.installArgs.user.getIdentifier()), /* updateReferenceProfileContent= */ true); ... 省略部分 final boolean performDexopt = (!instantApp || Global.getInt(mContext.getContentResolver(), Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0) && !pkg.isDebuggable() && (!onIncremental) && dexoptOptions.isCompilationEnabled(); if (performDexopt) { ... 省略部分 // 4. 進行 dexopt 優化 mPackageDexOptimizer.performDexOpt(pkg, realPkgSetting, null /* instructionSets */, getOrCreateCompilerPackageStats(pkg), mDexManager.getPackageUseInfoOrDefault(packageName), dexoptOptions); } ... } ... } ``` > ![](https://i.imgur.com/1j0DEaA.png) * 接下來準備分析 `prepareAppDataAfterInstallLIF` 方法 ### [PKMS](https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/pm/PackageManagerService.java) - 準備 AppData & 呼叫 Installer 安裝 * 從上一部的 prepareAppDataAfterInstallLIF 方法開始分析,該方法 **最終會調用到 Installer 進程為 APK 進行安裝** :::info 這中間有一個 Installer 算是一個橋接設計,它會連接真正的 Installer 服務 ::: ```java= // PackageManagerService.java final ArtManagerService mArtManagerService; // 創建 or 調整 or 更新 private void prepareAppDataAfterInstallLIF(AndroidPackage pkg) { final PackageSetting ps; synchronized (mLock) { ps = mSettings.getPackageLPr(pkg.getPackageName()); mSettings.writeKernelMappingLPr(ps); } ... Installer.Batch batch = new Installer.Batch(); for (UserInfo user : mUserManager.getUsers(false /*excludeDying*/)) { ... 省略部分 if (ps.getInstalled(user.id)) { // 已安裝 // @ prepareAppData 分析 prepareAppData(batch, pkg, user.id, flags).thenRun(() -> { ... 省略部分 }); } } ... } private @NonNull CompletableFuture<?> prepareAppData(@NonNull Installer.Batch batch, @Nullable AndroidPackage pkg, int userId, int flags) { ... // @ prepareAppDataLeaf 方法 return prepareAppDataLeaf(batch, pkg, userId, flags); } private @NonNull CompletableFuture<?> prepareAppDataLeaf(@NonNull Installer.Batch batch, @NonNull AndroidPackage pkg, int userId, int flags) { ... // createAppData 安裝完成後,更新裝置 return batch.createAppData(volumeUuid, packageName, userId, flags, appId, seInfo, targetSdkVersion).whenComplete((ceDataInode, e) -> { if (e != null) { // 有錯誤就刪除重建 AppData ... // 刪除 AppData destroyAppDataLeafLIF(pkg, userId, flags); try { // 創建 AppData ceDataInode = mInstaller.createAppData(volumeUuid, packageName, userId, flags, appId, seInfo, pkg.getTargetSdkVersion()); logCriticalInfo(Log.DEBUG, "Recovery succeeded!"); } /* 省略 catch */ } ... if (mIsUpgrade || mFirstBoot || (userId != UserHandle.USER_SYSTEM)) { // @ prepareAppProfiles 分析 mArtManagerService.prepareAppProfiles(pkg, userId, /* updateReferenceProfileContent= */ false); } ... prepareAppDataContentsLeafLIF(pkg, ps, userId, flags); } } } ``` * [**ArtManagerService**](https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/pm/dex/ArtManagerService.java)#prepareAppProfiles 準備創建 AppData ```java= // ArtManagerService.java public void prepareAppProfiles( AndroidPackage pkg, @UserIdInt int user, boolean updateReferenceProfileContent) { ... 省略部分 try { ArrayMap<String, String> codePathsProfileNames = getPackageProfileNames(pkg); for (int i = codePathsProfileNames.size() - 1; i >= 0; i--) { ... 省略部分 synchronized (mInstaller) { // @ prepareAppProfile (該 Installer 類並非進程 Installer 服務) boolean result = mInstaller.prepareAppProfile(pkg.getPackageName(), user, appId, profileName, codePath, dexMetadataPath); ... } } } /* 省略 catch */ } ``` * [**Installer**](https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/pm/Installer.java)#Batch#createAppData 創建 AppData 參數 (這個 Installer 並非服務進程) ```java= // Installer.java public class Installer extends SystemService { private volatile IInstalld mInstalld; public boolean prepareAppProfile(String pkg, @UserIdInt int userId, @AppIdInt int appId, String profileName, String codePath, String dexMetadataPath) throws InstallerException { ... try { // 呼叫 Installer 服務進程的 prepareAppProfile return mInstalld.prepareAppProfile(pkg, userId, appId, profileName, codePath, dexMetadataPath); } /* 省略 catch */ } public static class Batch { public synchronized @NonNull CompletableFuture<Long> createAppData(String uuid, String packageName, int userId, int flags, int appId, String seInfo, int targetSdkVersion) { if (mExecuted) throw new IllegalStateException(); final CreateAppDataArgs args = buildCreateAppDataArgs(uuid, packageName, userId, flags, appId, seInfo, targetSdkVersion); final CompletableFuture<Long> future = new CompletableFuture<>(); mArgs.add(args); mFutures.add(future); return future; } } } ``` > ![](https://i.imgur.com/ugVbvSA.png) ## Appendix & FAQ :::info ::: ###### tags: `Android Framework`