--- title: 'PackageManagerService' disqus: kyleAlien --- PackageManagerService === ## OverView of Content 每個應用的唯一標示就是 Package (也就是說一個 APP 就是一個包),PackageManagerService 是對所有應用的管理 (以下簡稱 PKMS) :::success App => 唯一 Package、PID、進程 ::: PMS 可以主要用來安裝、卸載、查詢,桌面程式可以看到所有的應用程式都是從 PKMS 來的 [TOC] ## PKMS 概述 * PKMS 概述 1. **PKMS 就是 `PackageManagerServervice`:它是透過** [**SystemServer 進程**](https://hackmd.io/pJpAeKAeRea0VIW3oI-mXw?view#SystemServer-%E5%95%9F%E5%8B%95%E6%B5%81%E7%A8%8B) **啟動**,該服務會 **++掃描系統中特定的目錄++**,尋找裡面的 APK 格式文件,並對這些文件進行解析 (得到 AppInfo 相關訊息),最後完成安裝 | 簡寫 | 真正服務全名 | | -------- | -------- | | PKMS | PackageManagerServervice | | PMS | PowerManagerServer | 2. PKMS 會在安裝應用的過程中解析 APK 中的 `AndroidManifest.xml` 文件 (解析出 Android 四大組件),這些解析完的訊息會保存在 Android 系統中,**方便系統隨時取用** :::info * `AndroidManifest.xml` 文件 就像是一個 APP 的目錄,它具備哪些功能、權限、廣播、Provider... 等等訊息 ::: ### PMS - 主要功能 * PKMS 主要功能 1. 掃描 `.apk` 文件,安裝 `系統應用`、`本地應用` 2. **Parser AndroidManifest.xml 清單文件,分析出四大零組件、權限.... 等等訊息,並儲存在 PKMS 方便查詢** 3. 管理本地應用,其中包括 `安裝`、`卸載`、`查詢 APK 訊息` ...等等 功能 4. **分配應用程式的 UID、GID** :::success 透過 GID (Group 群組 ID) 來管理權限管理,在某個群組中才能使用某些功能 ::: ## PKMS - AIDL 使用 * 一般來說我們可以透過 Context#**getPackageManager() 方法** 來取得 PKMS 服務 (其實它是取得 PKMS 的代理),PackageManager 是抽象,實作類是 [**ApplicationPackageManager**](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/ApplicationPackageManager.java) ```java= // 調用 PKMS 方式 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // AIDL 代理對象 // @ getPackageManager 分析 PackageManager packageManager = this.getBaseContext().getPackageManager(); } ``` ### ServiceManager 取得 PKMS - IPackageManager 代理 * Context 的透過裝飾模式包裝,實作類是 [**ContextImpl**](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/ContextImpl.java#11) (常看應該很孰悉了 !?) ```java= // ContextImpl.java @Override public PackageManager getPackageManager() { if (mPackageManager != null) { return mPackageManager; } // @ getPackageManager 分析 final IPackageManager pm = ActivityThread.getPackageManager(); if (pm != null) { // Doesn't matter if we make more than one instance. return (mPackageManager = new ApplicationPackageManager(this, pm)); } return null; } ``` * [**ActivityThread**](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/ActivityThread.java) 透過 ServiceManager 取得 PKMS 的 IBinder,這裡的 **IPackageManager 是透過 AIDL 自動產生的類,IPackageManager 透過 `asInterface` 來取得 Java 代理類,透過該代理類來與 PKMS 通訊** ```java= // ActivityThread.java @UnsupportedAppUsage public static IPackageManager getPackageManager() { if (sPackageManager != null) { return sPackageManager; } // 1. 取得 ServiceManager 的代理 // 2. 透過代理傳入 "package" 來取得 PKMS 的句柄 (handle) // 3. 返回 PKMS 的代理 final IBinder b = ServiceManager.getService("package"); // 創建 AIDL 的代理類 sPackageManager = IPackageManager.Stub.asInterface(b); return sPackageManager; } ``` * 下圖是 `App`、`ServiceManager`、`SystemService` 進程之間的協作 (簡易概念) > ![](https://i.imgur.com/llCRfmF.png) ## [SystemServer](https://android.googlesource.com/platform/frameworks/base/+/master/services/java/com/android/server/SystemServer.java) 初始化 PKMS SystemServer 是 init 進程所孵化的第一個進程,在裡面會有許多系統服務會啟動,而其中又區分為三種 1. 啟動引導服務 (startBootstrapServices):**PKMS 就是在這裡啟動** 2. 核心進程 (startCoreServices) 3. 其他服務 (startOtherServices) ```java= // SystemServer.java public static void main(String[] args) { new SystemServer().run(); } private void run() { ... 省略部分 try { startBootstrapServices(t); startCoreServices(t); startOtherServices(t); } /* 省略 catch */ ... 省略部分 // Loop forever. Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); } private void startBootstrapServices(@NonNull TimingsTraceAndSlog t) ... 省略部分 // 安裝服務 Installer installer = mSystemServiceManager.startService(Installer.class); mDisplayManagerService = mSystemServiceManager.startService(DisplayManagerService.class); ... 省略部分 try { ... watch dog // 呼叫 PackageManagerService 的靜態 main 方法 // @ 分析 main mPackageManagerService = PackageManagerService.main(mSystemContext, installer, domainVerificationService, mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore); } ... watch dog ... 省略部分 } ``` ### 分析 startBootstrapServices - PKMS 啟動部分 * PKMS 啟動部分就是從 SystemServer#`startBootstrapServices` 函數開始分析 1. **啟動 Installer 服務**,之後會用這個服務來安裝所有應用 ```java= // SystemServer.java private void startBootstrapServices(@NonNull TimingsTraceAndSlog t) { ... 省略部分 Installer installer = mSystemServiceManager.startService(Installer.class); ... 省略部分 } ``` 2. `VoldProperties#decrypt` 判斷手機裝置是否加密(**讀取 `init.rc` 檔的設定**),`mOnlyCore = true` 代表只運行核心程序(創建一個極簡的啟動環境) ```java= // SystemServer.java private static final String ENCRYPTING_STATE = "trigger_restart_min_framework"; private void startBootstrapServices(@NonNull TimingsTraceAndSlog t) { ... 省略部分 // Only run "core" apps if we're encrypting the device. String cryptState = VoldProperties.decrypt().orElse(""); if (ENCRYPTING_STATE.equals(cryptState)) { // 只運行核心程序 ... log msg mOnlyCore = true; } else if (ENCRYPTED_STATE.equals(cryptState)) { ... log msg mOnlyCore = true; } ... } ``` :::success * 可以透過 adb 讀取系統屬性查看設備是否被加密 ```shell= adb -e shell getprop | grep decrypt ``` > ![](https://i.imgur.com/XT8NYQw.png) ::: 3. 調用 PKMS#main 方法,創建 PKMS 對象 ```java= // SystemServer.java private void startBootstrapServices(@NonNull TimingsTraceAndSlog t) { ... 省略部分 try { ... PackageManagerService.main(mSystemContext, installer, domainVerificationService, mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore); // 假設目前傳入 false } /* 省略 finally */ ... 省略部分 } ``` 4. 如果設備沒有加密,就執行 A/B OTA dexopting,OtaDexoptService#main 方法 ```java= // SystemServer.java private void startBootstrapServices(@NonNull TimingsTraceAndSlog t) { ... 省略部分 if (!mOnlyCore) { boolean disableOtaDexopt = SystemProperties.getBoolean("config.disable_otadexopt", false); if (!disableOtaDexopt) { ... trace log try { ... watch dog OtaDexoptService.main(mSystemContext, mPackageManagerService); } /* 省略 catch、finally */ } } ... 省略部分 } ``` > ![](https://i.imgur.com/uCyIUpF.png) ### 分析 startOtherServices - PKMS 後續操作 * PKMS 在 `SystemServer#startBootstrapServices 函數` 完成初始化操作後,會在 `startOtherServices` 做後續的操作 (完成 Dex 優化、systemReady) 1. 透過 PKMS#performFstrimIfNeeded 完成 dex 優化 ```java= // SystemServer.java private void startOtherServices(@NonNull TimingsTraceAndSlog t) { ... 省略部分 try { mPackageManagerService.performFstrimIfNeeded(); } ... ... 省略部分 } ``` 2. PKMS 準備就緒調用 **systemReady**,**載入預設權限** (之後分析) ```java= // SystemServer.java private void startOtherServices(@NonNull TimingsTraceAndSlog t) { ... 省略部分 mPackageManagerService.systemReady(); ... 省略部分 } ``` > ![](https://i.imgur.com/EKt1eam.png) ## [PKMS](https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/pm/PackageManagerService.java) 分析 來詳細分析 PKMS 的創建細節 ### [PKMS](https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/pm/PackageManagerService.java)#main - 初始化 PKMS 物件 - Injector * 透過 PackageManagerService#main 方法就可以創建 PackageManagerService 物件,並且將 PKMS 透過 ^1^ **package**、^2^ **package_native** 添加到 ServiceManager 中 > 添加到 ServiceManager 之後,使用者就可以透過 ServiceManager 關鍵字 `package`、`package_native` 取得 PKMS 引用 ```java= // PackageManagerService.java public static PackageManagerService main(Context context, Installer installer, @NonNull DomainVerificationService domainVerificationService, boolean factoryTest, boolean onlyCore) { // 檢查 package 編譯相關系統屬性 PackageManagerServiceCompilerMapping.checkProperties(); ... 省略部分 // Injector 是內部類,之後會使用這個注入取得需要的東西, // 而 Injector 取得的數據則是單例 Injector injector = new Injector( context, lock, installer, installLock, new PackageAbiHelperImpl(), backgroundHandler, SYSTEM_PARTITIONS, (i, pm) -> new ComponentResolver(i.getUserManagerService(), pm.mPmInternal, lock), (i, pm) -> PermissionManagerService.create(context, i.getSystemConfig().getAvailableFeatures()), (i, pm) -> new UserManagerService(context, pm, new UserDataPreparer(installer, installLock, context, onlyCore), lock), (i, pm) -> new Settings(Environment.getDataDirectory(), RuntimePermissionsPersistence.createInstance(), i.getPermissionManagerServiceInternal(), domainVerificationService, lock), (i, pm) -> AppsFilter.create(pm.mPmInternal, i), (i, pm) -> (PlatformCompat) ServiceManager.getService("platform_compat"), (i, pm) -> SystemConfig.getInstance(), (i, pm) -> new PackageDexOptimizer(i.getInstaller(), i.getInstallLock(), i.getContext(), "*dexopt*"), (i, pm) -> new DexManager(i.getContext(), pm, i.getPackageDexOptimizer(), i.getInstaller(), i.getInstallLock()), (i, pm) -> new ArtManagerService(i.getContext(), pm, i.getInstaller(), i.getInstallLock()), (i, pm) -> ApexManager.getInstance(), (i, pm) -> new ViewCompiler(i.getInstallLock(), i.getInstaller()), (i, pm) -> (IncrementalManager) i.getContext().getSystemService(Context.INCREMENTAL_SERVICE), (i, pm) -> new DefaultAppProvider(() -> context.getSystemService(RoleManager.class), () -> LocalServices.getService(UserManagerInternal.class)), (i, pm) -> new DisplayMetrics(), (i, pm) -> new PackageParser2(pm.mSeparateProcesses, pm.mOnlyCore, i.getDisplayMetrics(), pm.mCacheDir, pm.mPackageParserCallback) /* scanningCachingPackageParserProducer */, (i, pm) -> new PackageParser2(pm.mSeparateProcesses, pm.mOnlyCore, i.getDisplayMetrics(), null, pm.mPackageParserCallback) /* scanningPackageParserProducer */, (i, pm) -> new PackageParser2(pm.mSeparateProcesses, false, i.getDisplayMetrics(), null, pm.mPackageParserCallback) /* preparingPackageParserProducer */, // Prepare a supplier of package parser for the staging manager to parse apex file // during the staging installation. (i, pm) -> new PackageInstallerService( i.getContext(), pm, i::getScanningPackageParser), (i, pm, cn) -> new InstantAppResolverConnection( i.getContext(), cn, Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE), (i, pm) -> new ModuleInfoProvider(i.getContext(), pm), (i, pm) -> LegacyPermissionManagerService.create(i.getContext()), (i, pm) -> domainVerificationService, (i, pm) -> { // 負責 apk 安裝、卸載 HandlerThread thread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/); thread.start(); return pm.new PackageHandler(thread.getLooper()); }, new DefaultSystemWrapper(), LocalServices::getService, context::getSystemService); // 調用 PackageMangerService 的建構函數 PackageManagerService m = new PackageManagerService(injector, onlyCore, factoryTest, Build.FINGERPRINT, Build.IS_ENG, Build.IS_USERDEBUG, Build.VERSION.SDK_INT, Build.VERSION.INCREMENTAL); ... 省略部分 // 透過 ++package++、++package_native++ 添加到到 ServiceManager 中 ServiceManager.addService("package", m); final PackageManagerNative pmn = m.new PackageManagerNative(); ServiceManager.addService("package_native", pmn); return m; } ``` * Inject 是 PKMS 中的一個內部類,可以透過它來達到 ^1.^ **單例的功能** (並且有延遲加載的功能)、^2.^ 其他類可以取得 PKMS 實例 ```java= // PackageManagerService.java public static class Injector { @VisibleForTesting(visibility = Visibility.PRIVATE) interface Producer<T> { /** Produce an instance of type {@link T} */ T produce(Injector injector, PackageManagerService packageManager); } ... 省略部分 @VisibleForTesting(visibility = Visibility.PRIVATE) static class Singleton<T> { private final Producer<T> mProducer; private volatile T mInstance = null; Singleton(Producer<T> producer) { this.mProducer = producer; } T get(Injector injector, PackageManagerService packageManagerService) { if (mInstance == null) { mInstance = mProducer.produce(injector, packageManagerService); } return mInstance; } } // 透過 Singleton 泛型創建出來的都是 單例對象 private final Singleton<PermissionManagerServiceInternal> mPermissionManagerServiceProducer; private final Singleton<UserManagerService> mUserManagerProducer; private final Singleton<Settings> mSettingsProducer; private final Singleton<AppsFilter> mAppsFilterProducer; ... 省略部分 private final Singleton<DisplayMetrics> mDisplayMetricsProducer; Injector(Context context, PackageManagerTracedLock lock, Installer installer, Object installLock, PackageAbiHelper abiHelper, Handler backgroundHandler, List<ScanPartition> systemPartitions, /* 省略部分入參 */, Producer<PermissionManagerServiceInternal> permissionManagerServiceProducer, Producer<UserManagerService> userManagerProducer, Producer<Settings> settingsProducer, Producer<DisplayMetrics> displayMetricsProducer, ) { ... 省略部分 mPermissionManagerServiceProducer = new Singleton<>(permissionManagerServiceProducer); mUserManagerProducer = new Singleton<>(userManagerProducer); mSettingsProducer = new Singleton<>(settingsProducer); ... 省略部分 mDisplayMetricsProducer = new Singleton<>(displayMetricsProducer); } public PermissionManagerServiceInternal getPermissionManagerServiceInternal() { return mPermissionManagerServiceProducer.get(this, mPackageManager); } public Context getContext() { return mContext; } public Settings getSettings() { return mSettingsProducer.get(this, mPackageManager); } public DisplayMetrics getDisplayMetrics() { return mDisplayMetricsProducer.get(this, mPackageManager); } ... 省略部分 } ``` ### [PKMS](https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/pm/PackageManagerService.java) 構造方法 * PackageManagerService 的構造函數中可以發現 PKMS 構造有 **5 個階段** (寫在 EventLog 中),並且之後我們會依照個 5 個階段進行分析介紹 | 階段標誌 | 說明 | 其他 | | -------- | -------- | -------- | | BOOT_PROGRESS_PMS_START | 初始階段,透過 Inject 內部類來取得 **單例** 的各種服務、對象 | DisplayMetrics、Installer、mPermissionManager、mSettings、mPackageDexOptimizer | | BOOT_PROGRESS_PMS_SYSTEM_SCAN_START | 掃描系統 | | | BOOT_PROGRESS_PMS_DATA_SCAN_START | 掃描 data 區塊 | | | BOOT_PROGRESS_PMS_SCAN_END | 掃描結束 | | | BOOT_PROGRESS_PMS_READY | 準備階段 | | * PKMS 有使用到多 Thread,而裡面有兩個重要的 **鎖** 需要注意 | 鎖名稱 | 說明 | 補充 | | -------- | -------- | -------- | | mPackages(小鎖) | 用於保護所有內存中解析 Package 的細節、狀態、更新 | **在持有 mInstallLock 的時候,獲取 mPackages 是安全的** | | mInstallLock(大鎖) | 用於保護所有安裝 APK 時的鎖,通常是有關到應用 APP 的重載 (**單線程、涉及繁重的 IO 操作**) | **在獲取 mPackages 時,不該再獲取此鎖** | ## 第一階段 - BOOT_PROGRESS_PMS_START :::info 第一階段的重點是 **Settings#readLPw 方法**,讀取 `/data/system` 目錄下的文件 ::: * **BOOT_PROGRESS_PMS_START**:取得 `DisplayMetrics`(分辨率)、`Installer`(安裝)、`PermissionManager` (權限管理)、`mSettings`(保存安裝訊息、清除不存在的應用)、`PackageDexOptimizer`(Dex 優化)... 詳細請看註解 ```java= // PackageManagerService.java public PackageManagerService(Injector injector, boolean onlyCore, boolean factoryTest, final String buildFingerprint, final boolean isEngBuild, final boolean isUserDebugBuild, final int sdkVersion, final String incrementalVersion) { ... 省略部分 EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START, SystemClock.uptimeMillis()); mOnlyCore = onlyCore; // 透過 injector 取得需要物件 mMetrics = injector.getDisplayMetrics(); // 分辨率 mInstaller = injector.getInstaller(); // 安裝器 mUserManager = injector.getUserManagerService(); // 多用戶管理 // 權限管理服務 mPermissionManager = injector.getPermissionManagerServiceInternal(); // 涉及 'data/system/' packages.xml // packages-backup.xml // packages.list // packages-stopped.xml // packages-stopped-backup.xml 文件 mSettings = injector.getSettings(); // 保存安裝訊息、清除不存在的應用 ... 省略部分 // 添加 system、phone、log、nfc、bluetooth、shell、se、 // networkstack、uwb 這幾個 sharedUserLPw 到 mSettings 中 mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID, ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID, ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); mSettings.addSharedUserLPw("android.uid.log", LOG_UID, ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID, ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID, ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID, ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); mSettings.addSharedUserLPw("android.uid.se", SE_UID, ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); mSettings.addSharedUserLPw("android.uid.networkstack", NETWORKSTACK_UID, ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); mSettings.addSharedUserLPw("android.uid.uwb", UWB_UID, ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); ... 省略部分 // 處理 dex 優化 (DexOpt 優化) mPackageDexOptimizer = injector.getPackageDexOptimizer(); mDexManager = injector.getDexManager(); // Art 虛擬機 管理 mArtManagerService = injector.getArtManagerService(); ... 省略部分 // 創建 /data/app 資料夾 mAppInstallDir = new File(Environment.getDataDirectory(), "app"); // 安裝 APK 時需要的鎖,保護所有對 installed 的訪問 // CHECKSTYLE:OFF IndentationCheck synchronized (mInstallLock) { // writer synchronized (mLock) { // 啟動 PackageManager Thread,負責 apk 安裝、卸載 mHandler = injector.getHandler(); // 進程紀錄 Handler mProcessLoggingHandler = new ProcessLoggingHandler(); // 監聽 PackageManager Thread 是否超時 10 分鐘 Watchdog.getInstance().addThread(mHandler, // WATCHDOG_TIMEOUT => 10 分鐘 WATCHDOG_TIMEOUT); ... 省略部分 // 讀取安裝相關的 SELinux 策略 SELinuxMMAC.readInstallPolicy(); // 讀取並解析 `/data/system` 中的 xml 文件 // // @ 分析 readLPw 方法 !!!!! mFirstBoot = !mSettings.readLPw(sUserManager.getUsers(false)); t.traceEnd(); } } } ``` 1. 其中 `mSettings.addSharedUserLPw` 方法是將 **`SharedUserId`** 添加到 Setting 中;一般來講進程間數據不可共享,但 **有共同 `SharedUserId` 就可以共享數據** 以下來看看藍芽的共享 `SharedUserId`,為 1002 > ![](https://i.imgur.com/VwLaZGN.png) ```shell= cat packages.xml | grep -i sharedUserId --color=auto | \ grep -i bluetooth --color=auto ``` > 確實看到共享 id > > ![](https://i.imgur.com/KfMLnEy.png) :::info 目前可以看到 android.uid 系列的 `system`、`phone`、`log`、`nfc`、`bluetooth`、`shell`、`se`、`networkstack`、`uwb` 是共享數據 ::: 2. `mPackageDexOptimizer` 是 Dex 的優化工具 3. mHandler 綁定後台 ServiceThread 的消息隊列,PKMS 通過它來驅動 APK 複製 & 安裝,並且該 Handler 會由 WatchDog 來監看(防止花費過多時間) :::success * Watchdog ? 用來監視系統進程是否發生死鎖,或是花費過多時間,必要時會殺掉 SystemServer 進程,再由 init 進程重啟 ::: 4. PKMD 會訪問幾個重要的資料夾 | 訪問 | 資料夾 | | - | - | | File(Environment.getDataDirectory(), "app") | /data/app | | File(Environment.getDataDirectory(), "app-lib") | /data/app-lib | ### [Settings](https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/pm/Settings.java) 創建 :::info Settings 用來管理應用的安裝資訊 ::: * 先複習 Settings 對象由來:PKMS 的 Settings 對象會由 PKMS#Inject 類提供 ```java= // PackageManager public static class Injector { private final Singleton<Settings> mSettingsProducer; Injector(/* 省略入參 */) { ... 省略部分 mSettingsProducer = new Singleton<>(settingsProducer); } public Settings getSettings() { return mSettingsProducer.get(this, mPackageManager); } } public static PackageManagerService main(Context context, Installer installer, @NonNull DomainVerificationService domainVerificationService, boolean factoryTest, boolean onlyCore) { Injector injector = new Injector( context, lock, installer, installLock, new PackageAbiHelperImpl(), backgroundHandler, SYSTEM_PARTITIONS, ... 省略部分 // 真正 new 出物件的地方 // @ 追蹤 Settings 建構方法 (i, pm) -> new Settings(Environment.getDataDirectory(), // /data 資料夾 RuntimePermissionsPersistence.createInstance(), i.getPermissionManagerServiceInternal(), domainVerificationService, lock), ); } ``` * [**Settings 類**](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/pm/Settings.java) 的建構函數會創建所需 文件 & 資料夾,並設定它們的權限 > 權限規範如下 > > ![](https://i.imgur.com/gfGPBPl.png) ```java= // Settings.java Settings(File dataDir, // 帶入的 dataDir 是 `/data` RuntimePermissionsPersistence runtimePermissionsPersistence, LegacyPermissionDataProvider permissionDataProvider, @NonNull DomainVerificationManagerInternal domainVerificationManager, @NonNull PackageManagerTracedLock lock) { ... 省略部分 // 創建 /data/system 目錄 mSystemDir = new File(dataDir, "system"); // mSystemDir 目錄指向 '/data/system' mSystemDir.mkdirs(); // 創建 `/data/system` // 設定資料夾權限 775 FileUtils.setPermissions(mSystemDir.toString(), FileUtils.S_IRWXU|FileUtils.S_IRWXG |FileUtils.S_IROTH|FileUtils.S_IXOTH, -1, -1); // 創建 `/data/system/packages.xml` 檔案 mSettingsFilename = new File(mSystemDir, "packages.xml"); // 創建 `/data/system/packages-backup.xml` 檔案 mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml"); // 創建 `data/system/packages.list` 檔案 mPackageListFilename = new File(mSystemDir, "packages.list"); // 設定 chmod 0640 FileUtils.setPermissions(mPackageListFilename, 0640, SYSTEM_UID, PACKAGE_INFO_GID); final File kernelDir = new File("/config/sdcardfs"); mKernelMappingFilename = kernelDir.exists() ? kernelDir : null; // Deprecated: Needed for migration // 創建 `data/system/packages-stopped.list` 檔案 mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml"); // 創建 `data/system/packages-stopped-backup.list` 檔案 mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml"); ... 省略部分 } ``` 1. 目錄 `/data/system`:**775** > ![](https://i.imgur.com/YYw1wX1.png) 2. 檔案 `/data/packages.list`:**640** > ![](https://i.imgur.com/ySDrmON.png) ### [Settings](https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/pm/Settings.java) - readLPw 讀取 package 文件 :::info 由於手機每次開機時都會重新安裝全部 APP,所以現在要 **掃描上一次手機裝置安裝的紀錄** ::: * **mSettings#readLPw() 函數**:讀取 `/data/system` 目錄下的文件,並保存到 Setting 對象中,而文件又分為以下幾種 (提出較為重要的幾個) | 文件名 | 說明 | 其他 | | -------- | -------- | -------- | | packages.xml | 安裝 app 訊息 | PKMS 掃描完目錄文件後會創建該文件,當系統執行 **安裝、卸載、更新** 等操作時都會更新這個文件 | | packages-backup.xml | 安裝 app 的備份訊息 | 避免在安裝時關機,產生意外 | | packages.list | 所有安裝的 app 訊息 (非系統 APP) | 描述所有非系統 APK 訊息,當有第三方 APP 變更時就會修改該文件 | | packages-stopped.xml | 強制停止 app 訊息 | 強制停止某個應用時(用戶操作),系統會將該應用的相關訊息記錄到該文件中 | | packages-stopped-backup.xml | 強制停止 app 的備份訊息 | | > `readLPw` 方法會返回一個 boolean,如果並非第一次讀取(已經有資料),就會返回 false > > **同時也代表了 PKMS 不是第一次啟動** > > ![](https://i.imgur.com/yKUbvsf.png) * **mSettings#readLPw()** 功能:首先掃描 `packages.xml`、`packages-backup.xml` 文件,其中與 Linux 使用 UID 有關的函數如下 1. **"package" 標籤**:使用 `readPackageLPw` 函數,讀取上一次分配的 Linux UID 2. **"shared-user" 標籤**:使用 `readSharedUserLPw` 函數,讀取上一次應用共用的 shared ID ```java= // Settings.java boolean readLPw(@NonNull List<UserInfo> users) { FileInputStream str = null; // 如果有 packages-backup.xml` 的話取得 backup IO 流 if (mBackupSettingsFilename.exists()) { try { str = new FileInputStream(mBackupSettingsFilename); ... } /* 省略 catch */ } try { if (str == null) { ... // 沒有 backup 就使用 `packages.xml` str = new FileInputStream(mSettingsFilename); } // 解析 xml final TypedXmlPullParser parser = Xml.resolvePullParser(str); int type; // 調整 xml 指標位置 while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { ; } // 解析格式,判斷 xml 格式是否正確 if (type != XmlPullParser.START_TAG) { ... err log return false; } int outerDepth = parser.getDepth(); // 開始分析 xml while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { continue; } // 解析節點名稱 String tagName = parser.getName(); if (tagName.equals("package")) { // 讀取上一次分配的 Linux UID // @ 分析 readPackageLPw readPackageLPw(parser, users); } else if (tagName.equals("permissions")) { mPermissions.readPermissions(parser); } else if (tagName.equals("permission-trees")) { mPermissions.readPermissionTrees(parser); } else if (tagName.equals("shared-user")) { // 讀取上一次應用共用的 GID // @ 分析 readSharedUserLPw readSharedUserLPw(parser, users); } ... 省略部分 else if else { ... log XmlUtils.skipCurrentTag(parser); } } str.close(); } /* 省略 catch、finally */ ... 省略部分 return true; } ``` :::success * 查看模擬器中安裝的 APP,可以 **對應到 `packages.xml` 裡面的資訊** > ![](https://i.imgur.com/umO7A9Y.png) ::: > ![](https://i.imgur.com/lM2BBmV.png) ### [Setting](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/pm/Settings.java) - readPackageLPw 應用分配獨立 Linux ID :::info 分析 Setting#readPackageLPw 方法,查看它如何存取應用的 **獨立 Linux ID** ::: > ![](https://i.imgur.com/ErRguus.png) * 接下來我們主要關注 "package" 標籤中的 3 元素 | 元素名 | 功能 | 補充 | | - | - | - | | `name` | 應用包名 | 必須要有 package 包名 | | `userId` | Linux 上次為該應用分配的 **獨立 UID** | | | `sharedUserId` | 該應用 **沒有獨立 UID**,與其它應用共用 UID | 將該應用添加進 `mPendingPackages` 列表之後判斷 | ```java= // Setting.java public static final String ATTR_NAME = "name"; private final WatchedArrayList<PackageSetting> mPendingPackages = new WatchedArrayList<>(); private void readPackageLPw(TypedXmlPullParser parser, List<UserInfo> users, ArrayMap<String, Long> originalFirstInstallTimes) throws XmlPullParserException, IOException { String name = null; int userId = 0; int sharedUserAppId = 0; try { // App package name name = parser.getAttributeValue(null, ATTR_NAME); ... userId = parser.getAttributeInt(null, "userId", 0); sharedUserAppId = parser.getAttributeInt(null, "sharedUserId", 0); // package name 是必須 if (name == null) { PackageManagerService.reportSettingsProblem(Log.WARN, "Error in package manager settings: <package> has no name at " + parser.getPositionDescription()); } else if (codePathStr == null) { ... } else if (userId > 0) { // 已分配到獨立 ID // @ 分析 addPackageLPw packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr), legacyNativeLibraryPathStr, primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString, userId, versionCode, pkgFlags, pkgPrivateFlags, null /* usesSdkLibraries */, null /* usesSdkLibraryVersions */, null /* usesStaticLibraries */, null /* usesStaticLibraryVersions */, null /* mimeGroups */, domainSetId); ... 省略部分 } else if (sharedUserAppId != 0) { // 未分配獨立 ID if (sharedUserAppId > 0) { // 保存該應用訊息 packageSetting = new PackageSetting(name.intern(), realName, new File(codePathStr), legacyNativeLibraryPathStr, primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString, versionCode, pkgFlags, pkgPrivateFlags, sharedUserAppId, null /* usesSdkLibraries */, null /* usesSdkLibrariesVersions */, null /* usesStaticLibraries */, null /* usesStaticLibraryVersions */, null /* mimeGroups */, domainSetId); ... // 添加到 `mPendingPackages` 列表 mPendingPackages.add(packageSetting); ... } else { ... } } else { ... } } /* 省略 catch */ } ``` * **Setting#`addPackageLPw` 函數**:在 PKMS 中 **每個應用都是以 `PackageSetting` 對象存在**,並將其儲存在以 package name 為 Key 的 HashMap 中 ```java= // Setting.java // 儲存全部應用的訊息 final WatchedArrayMap<String, PackageSetting> mPackages; private final AppIdSettingMap mAppIds; PackageSetting addPackageLPw(String name, String realName, File codePath, String legacyNativeLibraryPathString, String primaryCpuAbiString, String secondaryCpuAbiString, String cpuAbiOverrideString, int uid, ... /* 省略部分 */) { // 以 name 來取得 PackageSetting PackageSetting p = mPackages.get(name); if (p != null) { // 判斷 uid if (p.getAppId() == uid) { // 相等代表已分配獨立 UID return p; } PackageManagerService.reportSettingsProblem(Log.ERROR, "Adding duplicate package, keeping first: " + name); return null; } p = new PackageSetting(name, realName, codePath, legacyNativeLibraryPathString, primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString, ... /* 省略部分 */); // 設定 UID 給該 PackageSetting p.setAppId(uid); // 註冊該 ID 至系統 if (mAppIds.registerExistingAppId(uid, p, name)) { mPackages.put(name, p); return p; } return null; } ``` * [**AppIdSettingMap**](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/pm/AppIdSettingMap.java)#`registerExistingAppId` 函數:已分配的應用會從 `mNonSystemSettings` 取出 null ```java= // Process.java public static final int FIRST_APPLICATION_UID = 10000; // ------------------------------------------------------------ // AppIdSettingMap.java private final WatchedArrayList<SettingBase> mNonSystemSettings; public boolean registerExistingAppId(int appId, SettingBase setting, Object name) { // 大於 FIRST_APPLICATION_UID 代表是獨立應用 if (appId >= Process.FIRST_APPLICATION_UID) { int size = mNonSystemSettings.size(); final int index = appId - Process.FIRST_APPLICATION_UID; // fill the array until our index becomes valid while (index >= size) { mNonSystemSettings.add(null); size++; } // 已分配會取出 null if (mNonSystemSettings.get(index) != null) { // 重複 UID ...err msg return false; } mNonSystemSettings.set(index, setting); } else { // 小於 FIRST_APPLICATION_UID 代表是共享應用 ... } return true; } ``` ### [Setting](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/pm/Settings.java) - readPackageLPw 應用分配共享 Linux ID :::info 分析 Setting#readPackageLPw 方法,查看它如何存取應用的 **共享 Linux ID** ::: > ![](https://i.imgur.com/aoms6Ll.png) * 接下來我們主要關注 "shared-user" 標籤中的 3 元素 | 元素名 | 功能 | 補充 | | - | - | - | | `name` | 用來描述一個共用 Linux 使用者的名稱 | | | `userId` | 用來描述一個共用 Linux 使用者的 ID | | | `system` | 描述該 ID 是系統型態、使用者型態 | | ```java= // Setting.java private void readSharedUserLPw(TypedXmlPullParser parser, List<UserInfo> users) throws XmlPullParserException, IOException { String name = null; int pkgFlags = 0; int pkgPrivateFlags = 0; SharedUserSetting su = null; { name = parser.getAttributeValue(null, ATTR_NAME); // "name" int userId = parser.getAttributeInt(null, "userId", 0); if (parser.getAttributeBoolean(null, "system", false)) { pkgFlags |= ApplicationInfo.FLAG_SYSTEM; } if (name == null) { ... } else if (userId == 0) { ... } else { // @ 分析 addSharedUserLPw 函數 if ((su = addSharedUserLPw(name.intern(), userId, pkgFlags, pkgPrivateFlags)) == null) ... } } } ... } ``` * Setting#`addSharedUserLPw` 函數:使用 `SharedUserSetting` 來描述一個共享應用;比對 ID 確認沒問題後,就可以加入 `mSharedUsers` 列表 ```java= // Setting.java final WatchedArrayMap<String, SharedUserSetting> mSharedUsers = new WatchedArrayMap<>(); SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) { SharedUserSetting s = mSharedUsers.get(name); if (s != null) { if (s.mAppId == uid) { return s; } ...err return null; } // 創建對應對象 s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags); // 設定 ID s.mAppId = uid; // 嘗試註冊 if (mAppIds.registerExistingAppId(uid, s, name)) { mSharedUsers.put(name, s); return s; } return null; } ``` * [**AppIdSettingMap**](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/pm/AppIdSettingMap.java)#`registerExistingAppId` 函數:已分配的應用會從 `mNonSystemSettings` 取出 null ```java= // Process.java public static final int FIRST_APPLICATION_UID = 10000; // ------------------------------------------------------------ // AppIdSettingMap.java private final WatchedArrayList<SettingBase> mNonSystemSettings; public boolean registerExistingAppId(int appId, SettingBase setting, Object name) { // 大於 FIRST_APPLICATION_UID 代表是獨立應用 if (appId >= Process.FIRST_APPLICATION_UID) { ... } else { // 小於 FIRST_APPLICATION_UID 代表是共享應用 if (mSystemSettings.get(appId) != null) { ...err return false; } mSystemSettings.put(appId, setting); } return true; } ``` * 回到 Setting#`readLPw` 函數:經過上面分析 `"shared-user"`、`"package"` 後可以得到共享、獨立應用的相關訊息,接著就是要處理共享應用的 ID > 目前共享應用都存在 `mPendingPackages` 列表中 ```java= // Settings.java private final AppIdSettingMap mAppIds; boolean readLPw(@NonNull List<UserInfo> users) { ... 省略上面分析 final int N = mPendingPackages.size(); for (int i = 0; i < N; i++) { final PackageSetting p = mPendingPackages.get(i); final int sharedUserAppId = p.getSharedUserAppId(); // 共享 ID 一定大於 0 if (sharedUserAppId <= 0) { continue; } // 使用共享 ID,取得對應的安裝物件 final Object idObj = getSettingLPr(sharedUserAppId); if (idObj instanceof SharedUserSetting) { final SharedUserSetting sharedUser = (SharedUserSetting) idObj; // @ 查看 addPackageSettingLPw addPackageSettingLPw(p, sharedUser); } else if (idObj != null) { ... } else { ... } } mPendingPackages.clear(); return true; } public SettingBase getSettingLPr(int appId) { return mAppIds.getSetting(appId); } void addPackageSettingLPw(PackageSetting p, SharedUserSetting sharedUser) { mPackages.put(p.getPackageName(), p); // 如果是共享應用還要再判斷 if (sharedUser != null) { SharedUserSetting existingSharedUserSetting = getSharedUserSettingLPr(p); if (existingSharedUserSetting != null && existingSharedUserSetting != sharedUser) { ...err msg sharedUser.removePackage(p); } else if (p.getAppId() != sharedUser.mAppId) { ...err msg } sharedUser.addPackage(p); p.setSharedUserAppId(sharedUser.mAppId); // 決定最終 ID p.setAppId(sharedUser.mAppId); } Object userIdPs = getSettingLPr(p.getAppId()); if (sharedUser == null) { if (userIdPs != null && userIdPs != p) { mAppIds.replaceSetting(p.getAppId(), p); } } else { if (userIdPs != null && userIdPs != sharedUser) { mAppIds.replaceSetting(p.getAppId(), sharedUser); } } } ``` ## 第二階段 - BOOT_PROGRESS_PMS_SYSTEM_SCAN_START * **BOOT_PROGRESS_PMS_SYSTEM_SCAN_START**:掃描系統階段,其中目錄包括 (`system`、`vendor`、`product`、`odm`、`oem` .... 等等目錄中的 `priv-app`、`app`、`overlay`) 並且該步驟 **仍在兩個鎖內執行** * **scanDirTracedLI 方法**:該方法掃描 APK 包,會針對傳入的路徑掃描內部的 APK 檔案,之後會再針對 scanDirTracedLI 函數進行分析(先緩緩) ```java= // PackageManagerService.java // Key 是 應用包名,Value 是應用包 final ArrayMap<String, PackageParser.Package> mPackages = new ArrayMap<String, PackageParser.Package>(); public PackageManagerService(Injector injector, boolean onlyCore, boolean factoryTest, final String buildFingerprint, final boolean isEngBuild, final boolean isUserDebugBuild, final int sdkVersion, final String incrementalVersion) { ... 省略第一階階段 // CHECKSTYLE:OFF IndentationCheck synchronized (mInstallLock) { // writer synchronized (mLock) { ... 省略第一步驟 // 紀錄開始時間 long startTime = SystemClock.uptimeMillis(); EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SYSTEM_SCAN_START, startTime); // 從 init.rc 中獲取環境變量 BOOTCLASSPATH、 // SYSTEMSERVERCLASSPATH final String bootClassPath = System.getenv("BOOTCLASSPATH"); final String systemServerClassPath = System.getenv("SYSTEMSERVERCLASSPATH"); // 獲取 `/system/framework` 目錄 File frameworkDir = new File(Environment.getRootDirectory(), "framework"); // 獲取內部版本 final VersionInfo ver = mSettings.getInternalVersion(); // 判斷是否需要更新 mIsUpgrade = !buildFingerprint.equals(ver.fingerprint); // Android M 升級上來的版本,須將系統應用權限從安裝時申請,改為運行時申請 mPromoteSystemApps = mIsUpgrade && ver.sdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1; // Android N 升級上來的版本,需要像首次啟動一樣處理包提取,因為沒有可用的分析數據 mIsPreNUpgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.N; mIsPreNMR1Upgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.N_MR1; mIsPreQUpgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.Q; ... 省略部分 // 準備解析 package 的緩存 mCacheDir = preparePackageParserCache(mIsEngBuild); // 設定 flag,當掃描安裝資料夾時不改變 apk 路徑 int scanFlags = SCAN_BOOTING | SCAN_INITIAL; // 收集 `vendor/product/system_ext` overlay packages. for (int i = mDirsToScanAsSystem.size() - 1; i >= 0; i--) { final ScanPartition partition = mDirsToScanAsSystem.get(i); if (partition.getOverlayFolder() == null) { continue; } // 掃描 overlay... 等等 資料夾 內的 apk scanDirTracedLI(partition.getOverlayFolder(), systemParseFlags, systemScanFlags | partition.scanFlag, 0, packageParser, executorService); } // 掃描 framework 資料夾內的 apk scanDirTracedLI(frameworkDir, systemParseFlags, systemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED, 0, packageParser, executorService); // 判斷安裝包是否是 android if (!mPackages.containsKey("android")) { throw new IllegalStateException( "Failed to load frameworks package; check log for warnings"); } for (int i = 0, size = mDirsToScanAsSystem.size(); i < size; i++) { final ScanPartition partition = mDirsToScanAsSystem.get(i); if (partition.getPrivAppFolder() != null) { // 掃描 priv-app 資料夾 scanDirTracedLI(partition.getPrivAppFolder(), systemParseFlags, systemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag, 0, packageParser, executorService); } // 掃描 /system/app 資料夾 // @ 查看 scanDirTracedLI scanDirTracedLI(partition.getAppFolder(), systemParseFlags, systemScanFlags | partition.scanFlag, 0, packageParser, executorService); } ... 省略部分 if (!mOnlyCore) { // do this first before mucking with mPackages for the "expecting better" case final int numPackages = mPackages.size(); for (int index = 0; index < numPackages; index++) { final AndroidPackage pkg = mPackages.valueAt(index); if (pkg.isStub()) { stubSystemApps.add(pkg.getPackageName()); } } // 倒序掃描所有應用 for (int index = packageSettings.size() - 1; index >= 0; index--) { final PackageSetting ps = packageSettings.valueAt(index); // 如果包有 FLAG_SYSTEM 則忽略 if ((ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0) { continue; } /* * If the package is scanned, it's not erased. */ final AndroidPackage scannedPkg = mPackages.get(ps.name); if (scannedPkg != null) { // 若在 disabled packages list,添加進 // via OTA,再將其移除 if (mSettings.isDisabledSystemPackageLPr(ps.name)) { ... // 將系統 App 的 PackageSetting 從 PKMS 中 mPackage 移除 removePackageLI(scannedPkg, true); // 將升級包的路徑添加到 mExpectingBetter 列表中 mExpectingBetter.put(ps.name, ps.getPath()); } continue; } if (!mSettings.isDisabledSystemPackageLPr(ps.name)) { logCriticalInfo(Log.WARN, "System package " + ps.name + " no longer exists; it's data will be wiped"); // 系統 APP 將不存在,移除該 APP 的資料 removePackageDataLIF(ps, userIds, null, 0, false); } else { // we still have a disabled system package, but, it still might have // been removed. check the code path still exists and check there's // still a package. the latter can happen if an OTA keeps the same // code path, but, changes the package name. final PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(ps.name); if (disabledPs.getPath() == null || !disabledPs.getPath().exists() || disabledPs.pkg == null) { // 該系統 APP 在 isDisabledSystemPackage 中,並且沒有發現升級包 possiblyDeletedUpdatedSystemApps.add(ps.name); } else { // We're expecting that the system app should remain disabled, but add // it to expecting better to recover in case the data version cannot // be scanned. mExpectingBetter.put(disabledPs.name, disabledPs.getPath()); } } } } ... 省略部分 // ------------- 鎖 ---------------- } } } ``` :::success * 透過 `printenv` 查看 **環境變數 `BOOTCLASSPATH`、`SYSTEMSERVERCLASSPATH`** ```shell= # 進入 shell 後輸入 printenv ``` > ![](https://i.imgur.com/Q4jQA23.png) ::: ### 掃描升級系統 APP - system 資料夾 1. 目前階段 (`BOOT_PROGRESS_PMS_SYSTEM_SCAN_START`) 主要是掃描 `/system` 資料夾:這個目錄下資料夾都如何規劃放置; Android 系統架構有分為應用層、應用框架層、系統運行層、硬體抽象層、內核層 > 除了 內核層 放置在 boot 其他都在 system | system 內資料夾 | 放置內容 | 補充 | | -------- | -------- | - | | app | 系統 app | 包括 google 內置、手機廠商 app | | framework | 應用框架 jar 包 | 主要放 jar 包、vdex | | priv-app | 特權 app | | | lib | 放置動態 so 文件 | | | fonts | 系統字體 | 多是 ttf 檔 | | media | 系統音效 | 像是鈴聲、提示音、系統啟動動畫 | > 模擬器的 `/system` 資料目錄 > > ![](https://i.imgur.com/pbeClsY.png) 2. 第二個目的是掃描系統文件並處理,處理的 **重點是 ==OTA 升級==**,**系統會將可升級的系統應用標記為 DisabledSystemPackage**,並且有 3 種狀況 * 系統 APP 有更新:透過 removePackageLI 將系統 App 的 PackageSetting 從 PKMS 中 mPackage 移除,並 **添加到 mExpectingBetter 列表** * 系統 APP 被移除:透過 **removePackageDataLIF** 移除系統 APP 資料 * 沒有升級包:系統應用為 DisabledSystemPackage,但沒有發現升級包,代表代表系統升級包 **可能** 被刪除 > 等待 '/data' 資料掃瞄的 APP 添加到 possiblyDeletedUpdatedSystemApps 列表 :::info * 之所以是 **++可能++**,是因為系統尚未掃描 Data 區塊 (下一個階段才會掃描),暫時放置到 possiblyDeletedUpdatedSystemApps 列表 ::: ## 第三階段 - BOOT_PROGRESS_PMS_DATA_SCAN_START :::info 這個階段主要 **使用 scanDirTracedLI 方法來掃描 `data` 資料夾** ::: * **BOOT_PROGRESS_PMS_DATA_SCAN_START**:主要會 **掃描 `/data` 資料夾** 1. 掃描處理、更新 `/data` 目錄的應用信息 (也會及時移除不需要的數據) 2. 處理上一步遺留的 possiblyDeletedUpdatedSystemApps 列表 * 如果無法從 mPackage 中取得 Package 則會刪除該 APP * 如果可以取得 Package 但不是系統 APP,並且會做以下這些事情 1. 移除 System 權限 2. 移除 Package 3. 重新掃描 Package 路徑的 APK 3. 掃描 mExpectingBetter 列表 * 取得系統 APP 升級包的路徑,並且會做以下這些事情 1. 根據系統 APP 所在的目錄設定掃描的解析參數 2. 將 PackageName 設定給 mSetting#mPackages 中 (removeDisabledSystemPackageLPw 方法) 3. 清除 mExpectingBetter 列表 ```java= // PackageManagerService.java private final File mAppInstallDir; public PackageManagerService(Injector injector, boolean onlyCore, boolean factoryTest, final String buildFingerprint, final boolean isEngBuild, final boolean isUserDebugBuild, final int sdkVersion, final String incrementalVersion) { ... 省略第一階段 // 取得 /data/app 資料夾 mAppInstallDir = new File(Environment.getDataDirectory(), "app"); // CHECKSTYLE:OFF IndentationCheck synchronized (mInstallLock) { // writer synchronized (mLock) { ... 省略第一、二階段 if (!mOnlyCore) { EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START, SystemClock.uptimeMillis()); // 1. 掃描 '/data/app' 資料夾 scanDirTracedLI(mAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0, packageParser, executorService); } // 關閉 Parser 流 (解析 Package xml 的流) packageParser.close(); // 關閉 Executor 的全部任務 List<Runnable> unfinishedTasks = executorService.shutdownNow(); if (!unfinishedTasks.isEmpty()) { throw new IllegalStateException("Not all tasks finished before calling close: " + unfinishedTasks); } if (!mOnlyCore) { // 非核心模式 // 2. 掃描上一步所遺留的 APP 包 (倒序) for (int i = possiblyDeletedUpdatedSystemApps.size() - 1; i >= 0; --i) { final String packageName = possiblyDeletedUpdatedSystemApps.get(i); final AndroidPackage pkg = mPackages.get(packageName); final String msg; // 從禁用系統列表 (mDisabledSystemPackage) 中移除 mSettings.removeDisabledSystemPackageLPw(packageName); if (pkg == null) { // 應該要找到升級包,但沒找到~ (移除該 APP) msg = "Updated system package " + packageName + " no longer exists; removing its data"; // (實際上並非馬上移除) } else { // 如果有取到 pkg,代表該 APP 存在 Data 區中, // 不屬於 System app,移除系統權限 msg = "Updated system package " + packageName + " no longer exists; rescanning package on data"; // 以下,刪除並重新掃描數據包 removePackageLI(pkg, true); // 刪除 try { final File codePath = new File(pkg.getPath()); // 重新掃描 scanPackageTracedLI(codePath, 0, scanFlags, 0, null); } catch (PackageManagerException e) { Slog.e(TAG, "Failed to parse updated, ex-system package: " + e.getMessage()); } } // 最後一次檢查! // 如果我們還有一個包設置 [ie. 它是以前掃描並為系統所知] // 但是,我們沒有該 Pkg [即。 從 /data 掃描它時出錯 // partition],徹底刪除包數據。 final PackageSetting ps = mSettings.getPackageLPr(packageName); if (ps != null && mPackages.get(packageName) == null) { // 移除 data 內出錯的數據包 removePackageDataLIF(ps, userIds, null, 0, false); } logCriticalInfo(Log.WARN, msg); } // 3. 遍歷 mExpectingBetter 列表 for (int i = 0; i < mExpectingBetter.size(); i++) { final String packageName = mExpectingBetter.keyAt(i); if (!mPackages.containsKey(packageName)) { // 取得系統 APP 升級包的路徑 final File scanFile = mExpectingBetter.valueAt(i); for (int i1 = mDirsToScanAsSystem.size() - 1; i1 >= 0; i1--) { final ScanPartition partition = mDirsToScanAsSystem.get(i1); // 根據系統 APP 所在的目錄設定掃描的解析參數 if (partition.containsPrivApp(scanFile)) { reparseFlags = systemParseFlags; // 設定掃描參數 rescanFlags = systemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag; break; } if (partition.containsApp(scanFile)) { reparseFlags = systemParseFlags; rescanFlags = systemScanFlags | partition.scanFlag; break; } } if (rescanFlags == 0) { Slog.e(TAG, "Ignoring unexpected fallback path " + scanFile); continue; } // 將 PackageName 對應的包設置數據 (PackagesSetting) // 添加到 mSetting 的 mPackages 中 mSettings.enableSystemPackageLPw(packageName); try { // 掃描系統 APP 的升級包 final AndroidPackage newPkg = scanPackageTracedLI( scanFile, reparseFlags, rescanFlags, 0, null); // We rescanned a stub, add it to the list of stubbed system packages if (newPkg.isStub()) { stubSystemApps.add(packageName); } } /* 省略 catch */ } } // 解壓 & 安裝根系統應用 // 該操做要確保最後執行,來確保所有存根被替代 or 禁用 // Stub (存根) installSystemStubPackages(stubSystemApps, scanFlags); ... 省略部分 } // 清除升級列表 mExpectingBetter.clear(); // 取得 Storage manage 包名 mStorageManagerPackage = getStorageManagerPackageName(); // 解決保護 action 過濾器,只允許 setup wizard 為這些 action 有最高權限的過濾 mSetupWizardPackage = getSetupWizardPackageNameImpl(); ... 省略部分 // 讀取並更新要保留的 package 的上次使用時間 mPackageUsage.read(packageSettings); mCompilerStats.read(); } } ``` ### scanDirTracedLI - 分析 data 目錄內容 :::success APK 分析請看另一篇 [**PackageManagerService - APK 解析**](https://hackmd.io/xHl9BAXLTbutnsDNBtY17w?view) ::: * `/data` 資料夾可成為 Data 分區,它個主用公用有兩個 1. 儲存所有用戶的 **個人數據** 2. 儲存所有用戶的 **配置文件** * 接著來看 `/data` 目錄下的子目錄都放那些資料 | /data 底下目錄 | 含意 | | -------- | -------- | | app | 儲存該收機裝置,自己安裝的 APP 應用 | | data | 儲存所有已安裝的 APP 數據,**其中也 ==包括 System APP 資料==** | | app-private | APP 的私有儲存空間 | | app-lib | 儲存所有 APP 的 JNI Lib | | system | 存放系統配置文件 | | anr | 用於存放 ANR 發生時,系統生成的 `traces.text` 文件 | > ![](https://i.imgur.com/HkjwgKz.png) ## 第四階段 - BOOT_PROGRESS_PMS_SCAN_END :::info 這個階段會判斷 SDK 是否更新,設置權限,更新 `package.xml` 文件 ::: * **BOOT_PROGRESS_PMS_SCAN_END 階段**:OAT 升級後首次啟動,**要清除不必要的緩存數據、權限,更新 `package.xml` 檔案** (packages.xml 用來儲存所有 APP 的訊息) 1. SDK 版本更變,重新更新權限 2. OTA 升級後首次啟動,清除不要要緩存 :::info * OTA 英文全稱是 `Over-the-Air Technology` ,即空間下載技術,是手機通過行動網路對手機系統,應用程式進行管理的技術 ::: 3. 權限更新完成後清理相關數據 4. 透過 mSetting 的內容保存並更新 package.xml 文件,這樣以後 PKMS 創建時就會讀取到新的 Setting ```java= // PackageManagerService.java public PackageManagerService(Injector injector, boolean onlyCore, boolean factoryTest, final String buildFingerprint, final boolean isEngBuild, final boolean isUserDebugBuild, final int sdkVersion, final String incrementalVersion) { ... 省略第一階段 // CHECKSTYLE:OFF IndentationCheck synchronized (mInstallLock) { // writer synchronized (mLock) { ... 省略第一、二、三階段 // 進入第四階段 EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SCAN_END, SystemClock.uptimeMillis()); // 權限重製 mPermissionManager.onStorageVolumeMounted( StorageManager.UUID_PRIVATE_INTERNAL, mIsUpgrade); ver.sdkVersion = mSdkVersion; // 如果是第一次啟動或從 pre-M 升級上來,並且是正常啟動,那我們需要初始化 User 定義的首選預設應用 if (!mOnlyCore && (mPromoteSystemApps || mFirstBoot)) { for (UserInfo user : mInjector.getUserManagerInternal().getUsers(true)) { mSettings.applyDefaultPreferredAppsLPw(user.id); } } // 在啟動期間確實為 系統用戶 準備儲存空間 // 因為像是 `SettingsProvider`、`SystemUI` 無法等待使用者啟動 final int storageFlags; if (StorageManager.isFileEncryptedNativeOrEmulated()) { storageFlags = StorageManager.FLAG_STORAGE_DE; } else { storageFlags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE; } ... 省略部分 // 在 OTA 升級成功後的首次啟動,不必要的緩存,但不清除應用程式的配置文件 if (mIsUpgrade && !mOnlyCore) { Slog.i(TAG, "Build fingerprint changed; clearing code caches"); for (int i = 0; i < packageSettings.size(); i++) { final PackageSetting ps = packageSettings.valueAt(i); if (Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, ps.volumeUuid)) { // 沒有應用會在這時啟動,所以不必凍結 clearAppDataLIF(ps.pkg, UserHandle.USER_ALL, FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL | Installer.FLAG_CLEAR_CODE_CACHE_ONLY | Installer.FLAG_CLEAR_APP_DATA_KEEP_ART_PROFILES); } } ver.fingerprint = Build.FINGERPRINT; } // 在 Launcher 中隱藏他們的 icon (Android-Q 之前的非系統應用) if (!mOnlyCore && mIsPreQUpgrade) { Slog.i(TAG, "Whitelisting all existing apps to hide their icons"); int size = packageSettings.size(); for (int i = 0; i < size; i++) { final PackageSetting ps = packageSettings.valueAt(i); if ((ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0) { continue; } ps.disableComponentLPw(PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME, UserHandle.USER_SYSTEM); } } // clear only after permissions and other defaults have been updated mPromoteSystemApps = false; // 所有的改變都在掃描中完成 ver.databaseVersion = Settings.CURRENT_DATABASE_VERSION; // 更新 package.xml 檔案 t.traceBegin("write settings"); writeSettingsLPrTEMP(); t.traceEnd(); ... 省略部分 } } } ``` ## 第五階段 - BOOT_PROGRESS_PMS_READY * **BOOT_PROGRESS_PMS_READY**: PKMS 最後階段 1. PackageInstallerService 用於管理安裝 Session 服務,**每次安裝過程都會分配一個 SessionId** 2. 要求觸發 GC 回收內存 ```java= // PackageManagerService.java public PackageManagerService(Injector injector, boolean onlyCore, boolean factoryTest, final String buildFingerprint, final boolean isEngBuild, final boolean isUserDebugBuild, final int sdkVersion, final String incrementalVersion) { ... 省略第一步驟 // CHECKSTYLE:OFF IndentationCheck synchronized (mInstallLock) { // writer synchronized (mLock) { ... 省略第一、二、三、四步驟 EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_READY, SystemClock.uptimeMillis()); ... 省略部分 // PermissionController 是一個系統核心,預設同意 & 管理權限 mRequiredPermissionControllerPackage = getRequiredPermissionControllerLPr(); ... 省略部分 // 取得安裝器服務 mInstallerService = mInjector.getPackageInstallerService(); ... 省略部分 // 讀取 & 更新 dex 檔案的用法 // 在 PM init 結束時執行這個操作,以便所有包都協調其數據目錄 // 建構可以驗證的文件並建構內存緩存 // 使用文件預計很小,因此與其它活動相比,加載 & 驗證它都滿花費時間的 final Map<Integer, List<PackageInfo>> userPackages = new HashMap<>(); for (int userId : userIds) { userPackages.put(userId, getInstalledPackages(/*flags*/ 0, userId).getList()); } mDexManager.load(userPackages); if (mIsUpgrade) { FrameworkStatsLog.write( FrameworkStatsLog.BOOT_TIME_EVENT_DURATION_REPORTED, BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_INIT_TIME, SystemClock.uptimeMillis() - startTime); } // 屬性被重構後,重構 live computer mLiveComputer = createLiveComputer(); } } ... 省略部分 // 在打開應用 zip 後,確保他們都刷新,並進行 GC 回收 VMRuntime.getRuntime().requestConcurrentGC(); mInstaller.setWarnIfHeld(mLock); ... 省略部分 } ``` ## Appendix & FAQ :::info ::: ###### tags: `Android Framework`