---
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`