1. 程式人生 > >Android 8.1 原始碼_核心篇 -- 深入研究 PackageManagerService 系列(1)

Android 8.1 原始碼_核心篇 -- 深入研究 PackageManagerService 系列(1)

開篇

前言

如果你真正的深入分析過 PackageManagerService,你會發現 PackageManagerService 的原始碼真的很大,而且邏輯、結構都甚為複雜。對 PackageManagerService 系列的原始碼分析是基於 Android 8.1的,我們知道隨著 Android 版本的迭代,程式碼邏輯面目全非,分析起來的難度很大。為什麼這麼說?因為筆者也是從頭開始分析,也想找一些大神所寫的博文作為參考!但是!!!搜遍了各大技術部落格,未能找到一篇較新的關於 PackageManagerService 的分析文章。即便是基於老版本(如 Android 5.1、6.0)的 PackageManagerService 分析,也沒有一篇內容詳盡,由裡到外的全面深入分析之作,整個篇幅都是貼上大量原始碼。所以導致筆者基本上是快速滑動滾輪,然後儘快的點選小叉叉。當然不敢說這個系列的文章能夠完全將 PackageManagerService 分析到位。但痛恨所有文章千篇一律,苦於學習原始碼的痛苦,更怕半途而廢,事倍功半,所以決定要分析就幹到底!秉承這樣的裝逼精神,我們開始 PMS 的分析之旅吧!

核心原始碼

關鍵類 路徑
SystemServer.java frameworks/base/services/java/com/android/server/SystemServer.java
PackageManagerService.java frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
Process.java frameworks/base/core/java/android/os/Process.java
SystemConfig.java frameworks/base/core/java/com/android/server/SystemConfig.java
Settings.java frameworks/base/services/core/java/com/android/server/pm/Settings.java
SharedUserSetting.java frameworks/base/services/core/java/com/android/server/pm/SharedUserSetting.java

簡介

PackageManagerService(PMS)是 SystemServer 啟動後的第一個核心服務,也是 Android 系統中最常用的服務之一。它負責系統中 Package 的管理,應用程式的安裝、解除安裝、資訊查詢等。如果你是面向 Android 系統開發的工程師,基礎概念我也不需要再多贅述,我們的重點是分析原始碼。

家族譜

首先,我們看一下 PackageManagerService 及客戶端的家族譜,如下圖所示(這邊只需要有個印象,等程式碼分析完再回來看這個家族譜你會清晰很多!!!):

微信截圖_20180910113833.png

簡單分析一下:

        ? IPackageManager 介面類中定義了服務端和客戶端通訊的業務函式,還定義了內部類 Stub,該類從 Binder 派生並實現了 IPackageManager 介面。

        ? PackageManagerService 繼承自 IPackageManager.Stub類,由於 Stub 類從 Binder 派生,因此 PackageManagerService 將作為服務端參與 Binder 通訊。

        ? Stub 類中定義了一個內部類 Proxy,該類有一個 IBinder型別(實際型別為 BinderProxy)的成員變數 mRemote,mRemote 用於和服務端 PackageManagerService通訊。

        ? IPackageManager 介面類中定義了許多業務函式,但是處於安全等方面的考慮,Android 對外(即SDK)提供的只是一個子集,該子類被封裝在抽象類 PackageManager中。客戶端一般通過 Context 的 getPackageManager 函式返回一個型別為 PackageManager的物件,該物件的實際型別是 PackageManager 的子類 ApplicationPackageManager。這種基於介面程式設計的方式,雖然極大降低了模組之間的耦合性,卻給程式碼分析帶來了不小的麻煩。

        ? ApplicationPackageManager 類繼承自 PackageManager類。它並沒有直接參與 Binder 通訊,而是通過 mPM 成員變數指向一個 IPackageManager.Stub.Proxy 型別的物件。

【提示】:原始碼中可能找不到 IPackageManager.java 檔案。該檔案是在編譯過程中產生的,最終的檔案位於 Android 原始碼 /out(out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/core/java/android/content/pm)目錄下面。

看下 IPackageManager.java:

public interface IPackageManager extends android.os.IInterface {
    /** Local-side IPC implementation stub class. */
    // 定義內部類 Stub,派生自 Binder,實現 IPackageManager 介面
    public static abstract class Stub extends android.os.Binder implements android.content.pm.IPackageManager {
        private static final java.lang.String DESCRIPTOR = "android.content.pm.IPackageManager";
        /** Construct the stub at attach it to the interface. */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }
        ... ...
        
        // 定義 Stub 的內部類 Proxy,實現 IPackageManager 介面
        private static class Proxy implements android.content.pm.IPackageManager {
            private android.os.IBinder mRemote;
            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }
            ... ...
        }
        ... ...
    }
    ... ...
}

當你已經迷惑了,覺得看不下去了,請繼續堅持!!!看原始碼,梳理流程,就像是走進迷宮,很容易迷失!所以在開始分析之前,我給讀者一些建議:緊抓主幹,熟悉流程,再去啃細枝末節!!!

Read The Fucking Code

重頭戲永遠都是 Read Source Code!!!所以,接下來我們就開始分析 PackageManagerService,後面簡稱為 PMS!

PackageManagerService 啟動源

先來說說 PackageManagerService 是怎麼啟動的:PackageManagerService 作為系統的核心服務,由 SystemServer 建立,SystemServer 呼叫了 PackageManagerService 的 main 函式 建立 PackageManagerService 例項。

原始碼如下:

// 原始碼路徑:frameworks/base/services/java/com/android/server/SystemServer.java

private void run() {
    // Start services.
    try {
        startBootstrapServices();
    ... ...
}

private PackageManagerService mPackageManagerService;

private Context mSystemContext;
private boolean mOnlyCore;

// 呼叫 startBootstrapServices
private void startBootstrapServices() {
    ... ...

    Installer installer = mSystemServiceManager.startService(Installer.class);
    
    // 呼叫 PMS 的 main 函式
    mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
                mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);

    // 判斷本次是否為初次啟動,當 Zygote 或 SystemServer 退出時,init 會再次啟動它們,所以這裡的 FirstBoot 是指開機後的第一次啟動
    mFirstBoot = mPackageManagerService.isFirstBoot();
    mPackageManager = mSystemContext.getPackageManager();
    ... ...
    
}

上面這段程式碼很好理解,即呼叫 PackageManagerService 的 main 函式建立 PackageManagerService 例項,那麼重點工作就要 繼續跟蹤這個 main 函式

核心main函式

// 原始碼路徑:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

public static PackageManagerService main(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
    // 此處主要檢查系統屬性
    PackageManagerServiceCompilerMapping.checkProperties();

    /*
     * 此處呼叫建構函式,其中factoryTest決定是否是測試版本,onlyCore決定是否只解析系統目錄
     * ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
     * 這邊的建構函式將是我們 PackageManagerService 分析的重點!!!
     * ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
     */
    PackageManagerService m = new PackageManagerService(context, installer,
                factoryTest, onlyCore);

    m.enableSystemUserPackages();
    ServiceManager.addService("package", m);
    final PackageManagerNative pmn = m.new PackageManagerNative();

    // 利用 Binder 通訊,將自己註冊到 ServiceManager 程序中
    ServiceManager.addService("package_native", pmn);
    return m;
}

main 函式看似幾行程式碼很簡單,但執行時間卻很長。主要原因是 PMS 在其“建構函式”中做了很多“重體力活”,這也是 Android 啟動速度慢的主要原因之一。

這邊,我們先簡單瞭解一下 PMS 建構函式的主要功能:

掃描 Android 系統中幾個目標資料夾中的 APK,從而建立合適的資料結構以管理如 Package 資訊、四大元件資訊、許可權資訊等各種資訊。抽象地看,PMS 像一個加工廠,它解析實際的物理檔案(APK檔案)以生成符合自己要求的產品。(例如,PMS 將解析 APK 包中的 AndroidManifest.xml,並根據其中宣告的 Activity 標籤來建立與此對應的物件並加以保管。)

從程式碼上看,PMS 的工作流程還是相對簡單的。但是深入研究下去,你會發現很複雜! 複雜的是其中用於儲存各種資訊的資料結構和它們之間的關係,以及影響最終結果的策略控制 。如果你自行研究過 PMS,你會發現程式碼中存在大量不同的資料結構以及它們之間的關係會讓人大為頭疼。

所以,在這篇文章中我們除了分析 PMS 的工作流程以外,會重點關注重要的資料結構以及它們的作用。

接下來就要集中重點分析 PMS 的建構函式 ,如果放在一篇文章中去分析完 PMS 邏輯架構是完全不可能講解清楚的!!!所以才有了系列一說,我們分段討論,規劃如下:

            ?  深入研究 PackageManagerService 建構函式(1) - 前期準備工作   (本篇文章要討論的內容)             ?  深入研究 PackageManagerService 建構函式(2) - 掃描Package             ?  深入研究 PackageManagerService 建構函式(3) - 掃尾工作

建構函式分析 - 前期準備工作

現在開始,我們就從原始碼角度深入剖析 PMS !直接上原始碼:

// 原始碼路徑:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

public static PackageManagerService main(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
    ... ...

    // 此處呼叫建構函式,其中factoryTest決定是否是測試版本,onlyCore決定是否只解析系統目錄
    PackageManagerService m = new PackageManagerService(context, installer,
                factoryTest, onlyCore);
    ... ...
}

跟蹤 PMS 的建構函式(第一階段):

// 原始碼路徑:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

// public static final int SDK_INT = SystemProperties.getInt("ro.build.version.sdk", 0);
final int mSdkVersion = Build.VERSION.SDK_INT;

public PackageManagerService(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
    ... ...

    /*
     * mSdkVersion是 PMS 的成員變數,定義的時候進行賦值,其值取自系統屬性 ro.build.version.sdk,即編譯的 SDK 版本
     * 如果沒有定義,則 APK 就無法知道自己執行在 Android 哪個版本上
     */
    if (mSdkVersion <= 0) {
        Slog.w(TAG, "**** ro.build.version.sdk not set!");    // 列印警告
    }

    mContext = context;
    mFactoryTest = factoryTest;   // 假定為false,即執行在非工廠模式下
    mOnlyCore = onlyCore;         // 假定為false,即執行在普通模式下    
    // mMetrics 用於儲存與顯示屏相關的一些屬性,例如螢幕的寬/高尺寸,解析度等資訊
    mMetrics = new DisplayMetrics();

    /*
     * ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
     * Settings 是一個非常重要的類,該類用於儲存系統執行過程中的一些設定,我們後面會重點分析這個類!!!
     * ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
     */
    mSettings = new Settings(mPackages);

    /*
     * ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
     * addSharedUserLPw 函式做了什麼?這是我們接下來要分析的重點!!!
     * ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 
     */
    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);

// 第一階段結束

What a fuck!!! 剛進入建構函式,我們就遇到了第一個較為複雜的資料結構 Settings 及它的 addSharedUserLPw 函式。

初識Settings

上面我們提出了一個問題: addSharedUserLPw 函式做了什麼? 接下來,準備開始分析 addSharedUserLPw 函式,從上面擷取一段程式碼:

// 原始碼路徑:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

    mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
                ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);

從上面原始碼可以看到,我們為 addSharedUserLPw 傳遞了 4 個引數:

            ✨  android.uid.system :字串

            ✨  Process.SYSTEM_UID :值為1000

            ✨  ApplicationInfo.FLAG_SYSTEM :標誌

            ✨  ApplicationInfo.PRIVATE_FLAG_PRIVILEGED :特權Apk

在進入對 addSharedUserLPw 函式的正式分析之前,我們先介紹一下 SYSTEM_UID 的相關知識。

UID/GID介紹

UID 為 “使用者ID” 的縮寫,GID 為 “使用者組ID” 的縮寫 。一般來說,每一個程序都會有一個對應的 UID(即標示該程序屬於哪個使用者,不同使用者有不同許可權)。一個程序也可分屬不用的使用者組(每個使用者都有對應的許可權)。

如上所述, UID/GID 和程序的許可權有關

在 Android 平臺中,系統定義的 UID/GID 在 Process.java 檔案中,如下所示(列舉部分):

// 原始碼路徑:frameworks/base/core/java/android/os/Process.java

    /**
     * Defines the UID/GID under which system code runs.
     */
    public static final int SYSTEM_UID = 1000;      // 系統 程序使用的 UID/GID,值為1000

    /**
     * Defines the UID/GID under which the telephony code runs.
     */
    public static final int PHONE_UID = 1001;       // Phone 程序使用的 UID/GID,值為1001

    /**
     * Defines the UID/GID for the user shell.
     * @hide
     */
    public static final int SHELL_UID = 2000;       // shell 程序使用的 UID/GID,值為2000

    /**
     * Defines the UID/GID for the log group.
     * @hide
     */
    public static final int LOG_UID = 1007;         // 使用 LOG 的程序所在的組的 UID/GID,值為1007

    /**
     * Defines the UID/GID for the WIFI supplicant process.
     * @hide
     */
    public static final int WIFI_UID = 1010;        // 供WIFI 相關程序使用的 UID/GID,值為1010

    /**
     * Defines the UID/GID for the mediaserver process.
     * @hide
     */
    public static final int MEDIA_UID = 1013;       // mediaserver 程序使用的 UID/GID,值為1013

    /**
     * Defines the UID/GID for the NFC service process.
     * @hide
     */
    public static final int NFC_UID = 1027;         // NFC 相關的程序的 UID/GID,值為1027

    /**
     * Defines the start of a range of UIDs (and GIDs), going from this
     * number to {@link #LAST_APPLICATION_UID} that are reserved for assigning
     * to applications.
     */
    public static final int FIRST_APPLICATION_UID = 10000;    // 第一個應用 Package 的起始 UID 為 10000
    
    /**
     * Last of application-specific UIDs starting at
     * {@link #FIRST_APPLICATION_UID}.
     */
    public static final int LAST_APPLICATION_UID = 19999;     // 系統所支援的最大的應用 Package 的 UID 為 19999

addSharedUserLPw

瞭解完 UID/GID,接下來就該分析 addSharedUserLPw 函數了!

// 原始碼路徑:frameworks/base/services/core/java/com/android/server/pm/Settings.java
        
// final ArrayMap<String, SharedUserSetting> mSharedUsers = new ArrayMap<String, SharedUserSetting>();
        
SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) {
    // mSharedUsers是一個 ArrayMap,key 為字串,值為 SharedUserSetting 物件
    SharedUserSetting s = mSharedUsers.get(name);
    if (s != null) {
        if (s.userId == uid) {
            return s;
        }
        ... ...
        return null;
    }
    /*
     * 建立一個新的 SharedUserSetting 物件,並設定 userId 為 uid
     *
     * ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
     * SharedUserSetting 是什麼?有什麼作用?接下來我們也會重點討論!!!
     * ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
     *
     */
    s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags);
    s.userId = uid;
    if (addUserIdLPw(uid, s, name)) {
        mSharedUsers.put(name, s);       // 將 name 與 s 鍵值對新增到 mSharedUsers 中儲存
        return s;
    }
    return null;
}

從以上程式碼我們看到,Settings中有一個 mSharedUsers 成員,該成員儲存的是 【“字串” 與 “SharedUserSetting” 鍵值對】 ,也就是說以字串為 key 得到對應的 SharedUserSetting 物件。

那麼 SharedUserSetting 是什麼?建立它的目的是什麼?接下來我們繼續分析!

SharedUserSetting

為了解釋 SharedUserSetting,我們拿 SystemUI 作為例子來討論這個問題。

我們看下 SystemUI 的 AndroidManifest.xml (這個檔案你肯定不陌生):

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
        package="com.android.systemui"
        android:sharedUserId="android.uid.system"
        coreApp="true">

在xml中,聲明瞭一個名為 android:sharedUserId 的屬性: “android.uid.systemui”

有必要聊聊這個 "sharedUserId" 的作用:

1、兩個或多個聲明瞭同一種 sharedUserId 的 APK 可共享彼此的資料,並且可執行在同一程序中。

2、通過宣告特定的 sharedUserId,該 APK 所在程序將被賦予指定的 UID(比如本例中的 SystemUI 聲明瞭 system 的 uid,執行 SystemUI 的程序就可享受 system 使用者所對應的許可權)。

除了在 AndroidManifest.xml 中宣告 sharedUserId外,APK 在編譯時還必須使用對應的證書進行簽名。
例如本例的 SystemUI,在其 Android.mk 中需要額外申明 LOCAL——CERTIFICATE := platform,如此才可以獲得指定的 UID。

通過以上分析,我們知道了如何組織一種資料結構來包括上面的內容。 此處有3個關鍵點需要注意:

           ? XML 中 sharedUserId 屬性指定了一個字串,它是 UID 的字串描述,故對應資料結構中也應該有這樣一個字串,這樣就把程式碼和 XML 中的屬性聯絡起來了。

           ? 在 LINUX 系統中,真正的 uid 是一個整數,所以該資料結構中必然有一個整型變數。

           ? 多個 Package 可宣告同一個 sharedUserId,因此該資料結構必然會儲存那些聲明瞭相同 sharedUserId 的 Package 的某些資訊。

以上三點現在只需要理解,可能你很迷茫,但是心裡有數就行,通過下面慢慢分析再回頭看就會清晰很多!!!

SharedUserSetting 我們做個總結:

✨ Settings 類定義了一個 mSharedUsers 成員,它是一個 ArrayMap,以字串(如:android.uid.system)為key,對應的 Value 是一個 SharedUserSetting 物件。

final ArrayMap<String, SharedUserSetting> mSharedUsers = new ArrayMap<String, SharedUserSetting>();

✨ SharedUserSetting 定義了一個成員變數 packages,型別為 ArraySet,用於儲存聲明瞭相同 sharedUserId 的 Package 的許可權設定資訊。

final class SharedUserSetting extends SettingBase {
    final String name;

    int userId;

    // flags that are associated with this uid, regardless of any package flags
    int uidFlags;
    int uidPrivateFlags;

    final ArraySet<PackageSetting> packages = new ArraySet<PackageSetting>();

✨ 每個 Package 有自己的許可權設定。許可權的概念由 PackageSeting 類表達。該類繼承自 PackageSettingBase 類,PackageSettingBase 又繼承自 SettingBase。

public final class PackageSetting extends PackageSettingBase {}
public abstract class PackageSettingBase extends SettingBase {}

✨ Settings 中還有兩個成員,一個是 mUserIds,另一個是 mOtherUserIds,這兩位成員的型別分別是 ArrayList 和 SparseArray。其目的是以 UID 為索引,得到對應的 SharedUserSetings 物件。在一般情況下,以索引獲取陣列元素的速度,比以 Key 獲取 ArrayMap 中元素的速度要快很多。

    private final ArrayList<Object> mUserIds = new ArrayList<Object>();
    private final SparseArray<Object> mOtherUserIds = new SparseArray<Object>();

What a fuck, again!!!

是不是看懵了?那我告訴你一個捷徑: 對應原始碼多看幾遍,你肯定能看懂!!!或者你也可以有個概念,繼續下面的征程,等回頭再來看,也是不錯的方法!!!

addUserIdLPw

我們剛剛雖然搞了個插曲,分析了 SharedUserSetings 物件,但是大前提是我們在分析 addSharedUserLPw 函式。現在有必要貼下程式碼,以便拉回一下我們的記憶:

// 原始碼路徑:frameworks/base/services/core/java/com/android/server/pm/Settings.java
        
SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) {
    SharedUserSetting s = mSharedUsers.get(name);
    if (s != null) {
        if (s.userId == uid) {
            return s;
        }
        ... ...
        return null;
    }
    s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags);
    s.userId = uid;
    if (addUserIdLPw(uid, s, name)) {
        mSharedUsers.put(name, s);  
        return s;
    }
    return null;
}

Ok,記憶找回!那麼下面就要繼續分析原始碼中的 addUserIdLPw 函數了,它的功能就是將 SharedUserSettings 物件儲存到對應的陣列中,程式碼如下:

// 原始碼路徑:frameworks/base/services/core/java/com/android/server/pm/Settings.java

private boolean addUserIdLPw(int uid, Object obj, Object name) {
    // uid 不能超出限制
    if (uid > Process.LAST_APPLICATION_UID) {      // 系統所支援的最大的應用 Package 的 UID
        return false;
    }

    if (uid >= Process.FIRST_APPLICATION_UID) {    // 第一個應用 Package 的起始 UID
        int N = mUserIds.size();                   
        // 計算索引,其值是 uid 和 FIRST_APPLICATION_UID 的差
        final int index = uid - Process.FIRST_APPLICATION_UID;
        while (index >= N) {
            mUserIds.add(null);
            N++;
        }
        // 判斷該索引位置的內容是否未空,為空則儲存
        if (mUserIds.get(index) != null) {
            PackageManagerService.reportSettingsProblem(Log.ERROR,
                    "Adding duplicate user id: " + uid
                    + " name=" + name);
            return false;
        }
        // mUserIds 儲存應用 Package 的 uid
        mUserIds.set(index, obj);                 
    } else {
        if (mOtherUserIds.get(uid) != null) {
            PackageManagerService.reportSettingsProblem(Log.ERROR,
                    "Adding duplicate shared id: " + uid
                            + " name=" + name);
            return false;
        }
        // 系統 Package 的 uid 由 mOtherUserIds 儲存
        mOtherUserIds.put(uid, obj);
    }
    return true;
}

至此對 Settings 的分析我們暫時告一段落。 除了重點分析了 UID/GID 以及 SharedUserId 方面的知識,還見識了幾個重要的資料結構,希望你能通過 SystemUI 的例項能夠理解這些資料結構存在的目的。

XML 檔案掃描

分析完 PMS 建構函式前期工作的第一階段後,接下來就要繼續回到建構函式中分析剩下的程式碼:

    // 原始碼路徑:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

    public PackageManagerService(Context context, Installer installer,
                boolean factoryTest, boolean onlyCore) {
        ... ...

        // 第一階段(分析完)

        // 該值和除錯有關,一般不設定該屬性
        String separateProcesses = SystemProperties.get("debug.separate_processes");
        if (separateProcesses != null && separateProcesses.length() > 0) {
            if ("*".equals(separateProcesses)) {
                ... ...
            } else {
                mDefParseFlags = 0;
                mSeparateProcesses = separateProcesses.split(",");
            }
        } else {
            mDefParseFlags = 0;
            mSeparateProcesses = null;
        }

        // 建立一個 Installer 物件,該物件和 Native 程序 installd 互動,以後分析 installd 時再來討論它的作用
        mInstaller = installer;
        ... ...

        // 獲取當前裝置的顯示屏資訊
        getDefaultDisplayMetrics(context, mMetrics);
        
        SystemConfig systemConfig = SystemConfig.getInstance();    ???? // 接下來重點關注的函式

        mGlobalGids = systemConfig.getGlobalGids();
        mSystemPermissions = systemConfig.getSystemPermissions();
        mAvailableFeatures = systemConfig.getAvailableFeatures();
        
        mProtectedPackages = new ProtectedPackages(mContext);

        synchronized (mInstallLock) {
        // writer
        synchronized (mPackages) {
            // 建立一個 HandlerThread 物件,實際就是建立一個帶訊息迴圈處理的執行緒,該執行緒的工作是:程式的安裝和解除安裝等,後面分析程式安裝時會跟它親密接觸
            mHandlerThread = new ServiceThread(TAG,
                    Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
            mHandlerThread.start();
           
            // 以 HandlerThread 執行緒的訊息迴圈(Looper物件)為引數建立一個 PackageHandler,該 Handler 的 handleMessage 函式將執行在此執行緒上
            mHandler = new PackageHandler(mHandlerThread.getLooper());
            
            ... ...
            
            File dataDir = Environment.getDataDirectory();
            
            // mAppInstallDir 指向 /data/data 目錄
            mAppInstallDir = new File(dataDir, "app");
            mAppLib32InstallDir = new File(dataDir, "app-lib");
            mAsecInternalPath = new File(dataDir, "app-asec").getPath();

            // mDrmAppPrivateInstallDir 指向 /data/app-private 目錄
            mDrmAppPrivateInstallDir = new File(dataDir, "app-private");

            // 建立一個 UserManager 物件,支援多個 User,每個 User 將安裝自己的應用
            sUserManager = new UserManagerService(context, this,
                    new UserDataPreparer(mInstaller, mInstallLock, mContext, mOnlyCore), mPackages);
            ... ...

            mFirstBoot = !mSettings.readLPw(sUserManager.getUsers(false)); ???? // 接下來重點關注的函式

Ok,我們又列出了一部分原始碼。可見以上程式碼除了建立了幾個物件以外,還有兩個重要的需要關注的函式,這也是我們接下來分析的重點!!!

SystemConfig

我們先來分析 SystemConfig systemConfig = SystemConfig.getInstance() 函式!

    // 原始碼路徑:frameworks/base/core/java/com/android/server/SystemConfig.java
    
    public static SystemConfig getInstance() {
        synchronized (SystemConfig.class) {
            if (sInstance == null) {
                sInstance = new SystemConfig();
            }
            return sInstance;
        }
    }

檢視它的建構函式:

    // 原始碼路徑:frameworks/base/core/java/com/android/server/SystemConfig.java

    SystemConfig() {
        // Read configuration from system
        readPermissions(Environment.buildPath( 
                Environment.getRootDirectory(), "etc", "sysconfig"), ALLOW_ALL);
                
        // Read configuration from the old permissions dir
        readPermissions(Environment.buildPath(
                Environment.getRootDirectory(), "etc", "permissions"), ALLOW_ALL);
                
        // Allow Vendor to customize system configs around libs, features, permissions and apps
        int vendorPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_PERMISSIONS |
                ALLOW_APP_CONFIGS;
        readPermissions(Environment.buildPath(
                Environment.getVendorDirectory(), "etc", "sysconfig"), vendorPermissionFlag);
        readPermissions(Environment.buildPath(
                Environment.getVendorDirectory(), "etc", "permissions"), vendorPermissionFlag);
                
        // Allow ODM to customize system configs around libs, features and apps
        int odmPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_APP_CONFIGS;
        readPermissions(Environment.buildPath(
                Environment.getOdmDirectory(), "etc", "sysconfig"), odmPermissionFlag);
        readPermissions(Environment.buildPath(
                Environment.getOdmDirectory(), "etc", "permissions"), odmPermissionFlag);
                
        // Only allow OEM to customize features
        readPermissions(Environment.buildPath(
                Environment.getOemDirectory(), "etc", "sysconfig"), ALLOW_FEATURES);
        readPermissions(Environment.buildPath(
                Environment.getOemDirectory(), "etc", "permissions"), ALLOW_FEATURES);
    }

我們發現個很神奇的現象, SystemConfig 的建構函式所做的工作就是: readPermissions() ,即從檔案中讀取許可權!!!

readPermissions

我們一起來看看這個很重要的函式:

    // 原始碼路徑:frameworks/base/core/java/com/android/server/SystemConfig.java
    
    void readPermissions(File libraryDir, int permissionFlag) {
        ... ...

        // Iterate over the files in the directory and scan .xml files
        File platformFile = null;
        for (File f : libraryDir.listFiles()) {
            // We'll read platform.xml last
            // 處理該目錄下的非 platform.xml 檔案
            if (f.getPath().endsWith("etc/permissions/platform.xml")) {
                platformFile = f;
                continue;
            }
            ... ...
            
            // 呼叫 readPermissionsFromXml 解析此 XML 檔案
            readPermissionsFromXml(f, permissionFlag);
        }

        // Read platform permissions last so it will take precedence
        if (platformFile != null) {
            // 不知道你有沒有發現,platform.xml文 件的解析優先順序最高哦!
            readPermissionsFromXml(platformFile, permissionFlag);
        }
    }

回顧一下上面的程式碼,我們發現 readPermissions 函式不就是呼叫 readPermissionFromXml 函式解析 "/system/etc/permissions" 目錄下的檔案嗎?

這些檔案似乎都是 XML 檔案。你也許有個疑問?該目錄下都有哪些 XML 檔案?這些 XML 檔案中有些什麼內容呢? 以我手中的 pixel 為例

sailfish:/system/etc/permissions $ ls -al
ls -al
total 168
drwxr-xr-x  2 root root  4096 2009-01-01 16:00 .
drwxr-xr-x 14 root root  4096 2009-01-01 16:00 ..
-rw-r--r--  1 root root  1050 2009-01-01 16:00 android.software.live_wallpaper.xml
-rw-r--r--  1 root root   748 2009-01-01 16:00 android.software.webview.xml
-rw-r--r--  1 root root  1778 2009-01-01 16:00 com.android.ims.rcsmanager.xml
-rw-r--r--  1 root root   828 2009-01-01 16:00 com.android.location.provider.xml
-rw-r--r--  1 root root   828 2009-01-01 16:00 com.android.media.remotedisplay.xml
-rw-r--r--  1 root root   820 2009-01-01 16:00 com.android.mediadrm.signer.xml
-rw-r--r--  1 root root   158 2009-01-01 16:00 com.android.omadm.service.xml
-rw-r--r--  1 root root   435 2009-01-01 16:00 com.android.sdm.plugins.connmo.xml
-rw-r--r--  1 root root   701 2009-01-01 16:00 com.android.sdm.plugins.sprintdm.xml
-rw-r--r--  1 root root   234 2009-01-01 16:00 com.android.vzwomatrigger.xml
-rw-r--r--  1 root root  1079 2009-01-01 16:00 com.customermobile.preload.vzw.xml
-rw-r--r--  1 root root   850 2009-01-01 16:00 com.google.android.camera.experimental2016.xml
-rw-r--r--  1 root root   563 2009-01-01 16:00 com.google.android.dialer.support.xml
-rw-r--r--  1 root root   816 2009-01-01 16:00 com.google.android.maps.xml
-rw-r--r--  1 root root   835 2009-01-01 16:00 com.google.android.media.effects.xml
-rw-r--r--  1 root root   811 2009-01-01 16:00 com.google.vr.platform.xml
-rw-r--r--  1 root root   160 2009-01-01 16:00 com.verizon.apn.xml
-rw-r--r--  1 root root   158 2009-01-01 16:00 com.verizon.embms.xml
-rw-r--r--  1 root root   288 2009-01-01 16:00 com.verizon.llkagent.xml
-rw-r--r--  1 root root   174 2009-01-01 16:00 com.verizon.provider.xml
-rw-r--r--  1 root root   220 2009-01-01 16:00 com.verizon.services.xml
-rw-r--r--  1 root root   239 2009-01-01 16:00 features-verizon.xml
-rw-r--r--  1 root root   811 2009-01-01 16:00 obdm_permissions.xml
-rw-r--r--  1 root root  8916 2009-01-01 16:00 platform.xml
-rw-r--r--  1 root root 23092 2009-01-01 16:00 privapp-permissions-google.xml
-rw-r--r--  1 root root  1346 2009-01-01 16:00 privapp-permissions-marlin.xml
-rw-r--r--  1 root root 20848 2009-01-01 16:00 privapp-permissions-platform.xml
-rw-r--r--  1 root root  1587 2009-01-01 16:00 vzw_mvs_permissions.xml
sailfish:/system/etc/permissions $

既然我們上面一直在說 platform.xml 這個檔案,那就看下 platform.xml 包含什麼東東:

<permissions>

    <!-- The following tags are associating low-level group IDs with
         permission names.  By specifying such a mapping, you are saying
         that any application process granted the given permission will
         also be running with the given group ID attached to its process,
         so it can perform any filesystem (read, write, execute) operations
         allowed for that group. -->

    <!-- 建立許可權名與 gid 的對映關係。如下面宣告的 BLUETOOTH_ADMIN 許可權,它對應的使用者組是 net_bt_admin。
         注意,該檔案中的 permission 標籤只對那些需要通過讀寫裝置(藍芽/cameta)/建立 socket 等程序劃分了 gid。
         因為這些許可權涉及和 Linux 核心互動,所以需要在底層許可權(由不用的使用者組界定)
         和 Android 層許可權(由不同的字串界定)之間建立對映關係。 -->

    <permission name="android.permission.BLUETOOTH_ADMIN" >
        <group gid="net_bt_admin" />
    </permission>

    <permission name="android.permission.BLUETOOTH" >
        <group gid="net_bt" />
    </permission>

    <permission name="android.permission.BLUETOOTH_STACK" >
        <group gid="bluetooth" />
        <group gid="wakelock" />
        <group gid="uhid" />
    </permission>

    ... ...

    <!-- The following tags are assigning high-level permissions to specific
         user IDs.  These are used to allow specific core system users to
         perform the given operations with the higher-level framework.  For
         example, we give a wide variety of permissions to the shell user
         since that is the user the adb shell runs under and developers and
         others should have a fairly open environment in which to
         interact with the system. -->

    <!-- 賦予對應 uid 相應的許可權。如果下面一行表示 uid 為 audioserver,那麼就
         賦予它 WAKE_LOCK 的許可權,其實就是把它加到對應的使用者組中 -->

    ... ...

    <assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="audioserver" />
    <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="audioserver" />
    <assign-permission name="android.permission.WAKE_LOCK" uid="audioserver" />
    <assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="audioserver" />
    <assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="audioserver" />

    ... ...

    <!-- This is a list of all the libraries available for application
         code to link against. -->
         
    <!-- 系統提供的 Java 庫,應用程式執行時必須要連結這些庫,該工作由系統自動完成 -->

    <library name="android.test.mock"
            file="/system/framework/android.test.mock.jar" />
    <library name="android.test.runner"
            file="/system/framework/android.test.runner.jar" />
    <library name="javax.obex"
            file="/system/framework/javax.obex.jar" />
    <library name="org.apache.http.legacy"
            file="/system/framework/org.apache.http.legacy.jar" />

    ... ...
</permissions>

platform.xml 檔案中主要使用瞭如下 4 個標籤

         ✨  permission group 用於建立 Linux 層 gid 和 Andrid 層 permission 之間的對映關係。

         ✨  assign-permission 用於向指定的 uid 賦予相應的許可權。這個許可權由 Android 定義,用於字串表示。

         ✨  library 用於指定系統庫。當應用程式執行時,系統會自動為這些程序載入這些庫。

不知道你是否已經產生了疑問?裝置上的 /system/etc/permission 目錄中的檔案是從哪裡來的?我們直接告訴你答案:在編譯階段由不用硬體平臺根據自己的配置資訊複製相關檔案到目標目錄中的來的。(這個具體我們不討論,有興趣的讀者可以自行查閱)

XML分析到此為止!接下來繼續跟原始碼... ...

readPermissionFromXML

前面我們說過: readPermissions 函式其實就是呼叫 readPermissionFromXml 函式解析 "/system/etc/permissions" 目錄下的檔案!

readPermissionFromXml 又有什麼作用?其實它的作用就是將 XML 檔案中的標籤以及它們之間的關係轉換成程式碼中的相應資料結構,直接看原始碼:

    // 原始碼路徑:frameworks/base/core/java/com/android/server/SystemConfig.java
    
    private void readPermissionsFromXml(File permFile, int permissionFlag) {
        FileReader permReader = null;
        ... ...

        final boolean lowRam = ActivityManager.isLowRamDeviceStatic();

        try {
            XmlPullParser parser = Xml.newPullParser();
            parser.setInput(permReader);

            ... ...

            while (true) {
                ... ...
                String name = parser.getName();
                // 解析 group 標籤
                if ("group".equals(name) && allowAll) {
                    String gidStr = parser.getAttributeValue(null, "gid");
                    if (gidStr != null) {
                        int gid = android.os.Process.getGidForName(gidStr);
                        // 轉換 XML 中的 gid 字串為整型,並儲存到 mGlobalGids中
                        mGlobalGids = appendInt(mGlobalGids, gid);
                    } else {
                        Slog.w(TAG, "<group> without gid in " + permFile + " at "
                                + parser.getPositionDescription());
                    }

                    XmlUtils.skipCurrentTag(parser);
                    continue;
                // 解析 permission標籤
                } else if ("permission".equals(name) && allowPermissions) {
                    String perm = parser.getAttributeValue(null, "name");
                    if (perm == null) {
                        ... ...
                        XmlUtils.skipCurrentTag(parser);
                        continue;
                    }
                    perm = perm.intern();
                    // 呼叫 readPermission 處理
                    readPermission(parser, perm);
                // 解析 assign-permission 標籤
                } else if ("assign-permission".equals(name) && allowPermissions) {
                    String perm = parser.getAttributeValue(null, "name");
                    ... ...
                    String uidStr = parser.getAttributeValue(null, "uid");
                    ... ...

                    // 如果是 assign-permission,則取出 uid 字串,然後獲得 Linux 平臺上的整型 uid 值
                    int uid = Process.getUidForName(uidStr);
                    ... ...
                    perm = perm.intern();
                    
                    // 和 assign 相關的資訊儲存在 mSystemPermissions 中
                    ArraySet<String> perms = mSystemPermissions.get(uid);
                    if (perms == null) {
                        perms = new ArraySet<String>();
                        mSystemPermissions.put(uid, perms);
                    }
                    perms.add(perm);
                    XmlUtils.skipCurrentTag(parser);
                // 解析 library 標籤
                } else if ("library".equals(name) && allowLibs) {
                    String lname = parser.getAttributeValue(null, "name");
                    String lfile = parser.getAttributeValue(null, "file");
                    if (lname == null) {
                        ... ...
                    } else if (lfile == null) {
                        ... ...
                    } else {
                        ... ...
                        // 將 XML 中的 name 和 library 屬性值儲存到 mSharedLibraries 中
                        mSharedLibraries.put(lname, lfile);
                    }
                    XmlUtils.skipCurrentTag(parser);
                    continue;
                // 解析 feature標籤
                } else if ("feature".equals(name) && allowFeatures) {
                    String fname = parser.getAttributeValue(null, "name");
                    int fversion = XmlUtils.readIntAttribute(parser, "version", 0);
                    boolean allowed;
                    ... ...
                } else if ("unavailable-feature".equals(name) && allowFeatures) {
                    ... ... // 解析其它標籤
            }
        ... ...

        }
        ... ...
        
    }

readPermission 函式果然是將 XML 中的標籤轉換成對應的資料結構!!! 具體不在深入,後面可能我還會回來補充!!!

readLPw

readLPw 函式的功能也是解析檔案,不過這些檔案的內容卻是在 PMS 正常啟動之後生成的。我們這邊僅僅簡單的介紹一下與 readLPW 相關的檔案資訊。檔案的具體位置在 Settings 建構函式中指明:

    Settings(File dataDir, Object lock) {
        mLock = lock;

        mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock);

        mSystemDir = new File(dataDir, "system");   // 指向 /data/system 目錄
        mSystemDir.mkdirs();    // 建立該目錄
        FileUtils.setPermissions(mSystemDir.toString(),
                FileUtils.S_IRWXU|FileUtils.S_IRWXG
                |FileUtils.S_IROTH|FileUtils.S_IXOTH,
                -1, -1);
                
        /*
         * 一共 new 了 6個 檔案
         * packages.xml 和 packages-backup.xml 為一組,用於描述系統中所安裝的 Package 資訊,其中 backup 是臨時檔案。
         * PMS 先把資料寫到 backup 中,資訊都寫成功後再改名成非 backuo 的檔案。其目的是防止在寫檔案過程中出錯,導致資訊丟失。
         * packages-stopped.xml 和 packages-stopped-backup.xml 為一組,用於描述系統中強制停止執行的 Package 的資訊,backup也是臨時檔案。
         * 如果此處存在臨時檔案,表明此前系統因為某種原因中斷了正常流程。
         * packages.list 列出了當前系統中應用級 Package 的資訊。
         */
        mSettingsFilename = new File(mSystemDir, "packages.xml");
        mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml");
        mPackageListFilename = new File(mSystemDir, "packages.list");
        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
        mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml");
        mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml");
    }

【小結】

         ✨  packages.xml :PMS 掃描完目標資料夾後會建立該檔案。當系統進行程式安裝、解除安裝和更新等操作時,均會更新該檔案。該檔案儲存了系統中與 Package 相關的一些資訊。

         ✨  packages.list :描述系統中存在的所有非系統自帶的 APK 的資訊。當這些程式有變動時,PMS 就會更新該檔案。

         ✨  packages-stopped.xml :從系統自帶的設定程式中進入應用程式頁面,然後在選擇強制停止某個應用時,系統會將該應用的相關資訊記錄到此檔案中。也就是該檔案儲存系統中被使用者強制停止的 Package 的資訊。

readLPw 的函式功能就是解析其中的 XML 檔案的內容,然後建立並更新對應的資料結構,例如停止的 Package 重啟之後依然是 stopped 狀態。

第一階段工作總結

所做工作: 掃描並解析 XML 檔案,將其中的資訊儲存到特定的資料結構中。

第一階段掃描的 XML 檔案與許可權及上一次掃描的到的 Package 資訊有關,它為 PMS 下一階段的工作提供了重要的參考資訊。

後續內容

本篇文章開啟了 PackageManagerService 的原始碼分析征程!已經分析的部分也僅僅是 PMS 建構函式的一部分,所以後面會有一系列的文章補充,但是每篇文章都會循序漸進的詳細分析,爭取梳理清楚 PMS 的整體架構(基於 Android 8.1原始碼)。