【Android 系統開發】_「核心服務」篇 -- PMS(1) 之 「啓動流程」

開篇

PackageManagerService 系列文章以下(基於 Android 9.0 源碼)html

         🍁   Framework 核心服務之 PackageManagerService 鑽研(1)- 啓動流程                          
         🍁   Framework 核心服務之 PackageManagerService 鑽研(2)- 構造函數
         🍁   Framework 核心服務之 PackageManagerService 鑽研(3)- PackageManager
         🍁   Framework 核心服務之 PackageManagerService 鑽研(4)- PackageInstaller
         🍁   Framework 核心服務之 PackageManagerService 鑽研(5)- APK 安裝流程(PackageInstaller)
         🍁   Framework 核心服務之 PackageManagerService 鑽研(6)- APK 安裝流程(PMS)
         🍁   Framework 核心服務之 PackageManagerService 鑽研(7)- PackageParserjava

修改日誌android

         一、從新梳理 Android 9.0 源碼中 PMS 的啓動流程;
         二、從新梳理涉及 Settings 的代碼邏輯;
         三、從新梳理 XML 文件掃描的代碼邏輯;
         四、博文格式,文章排版等優化;ios

前言

若是你真正的深刻分析過 PackageManagerService,你會發現 PackageManagerService 的源碼真的很大,並且邏輯、結構都甚爲複雜。對 PackageManagerService 系列的源碼分析是基於 Android 8.1的,咱們知道隨着 Android 版本的迭代,代碼邏輯面目全非,分析起來的難度很大。爲何這麼說?由於筆者也是從頭開始分析,也想找一些大神所寫的博文做爲參考!可是!!!搜遍了各大技術博客,未能找到一篇較新的關於 PackageManagerService 的分析文章。即使是基於老版本(如 Android 5.一、6.0)的 PackageManagerService 分析,也沒有一篇內容詳盡,由裏到外的全面深刻分析之做,整個篇幅都是粘貼大量源碼。因此致使筆者基本上是快速滑動滾輪,而後儘快的點擊小叉叉。固然不敢說這個系列的文章可以徹底將 PackageManagerService 分析到位。但痛恨全部文章千篇一概,苦於學習源碼的痛苦,更怕半途而廢,事倍功半,因此決定要分析就幹到底!秉承這樣的裝逼精神,咱們開始 PMS 的分析之旅吧!web

核心源碼

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

簡介

PackageManagerService(PMS)是 SystemServer 啓動後的第一個核心服務,也是 Android 系統中最經常使用的服務之一。它負責系統中 Package 的管理,應用程序的安裝、卸載、信息查詢等。若是你是面向 Android 系統開發的工程師,基礎概念我也不須要再多贅述,咱們的重點是閱讀分析源碼,鑽研原理的奧祕。shell

家族譜

首先,咱們看一下 PackageManagerService 及客戶端的家族譜,以下圖所示(這邊暫且只須要有個印象,整個系列分析完再回來看這個家族譜,你會清晰不少!)apache

微信截圖_20180910113833.png

簡單說明:編程

        🔰 IPackageManager 接口類中定義了服務端和客戶端通訊的業務函數,還定義了內部類 Stub,該類從 Binder 派生並實現了 IPackageManager 接口。segmentfault

        🔰 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;
            }
            ... ...
        }
        ... ...
    }
    ... ...
}

看源碼,梳理流程,就像是走進迷宮,很容易迷失!因此在開始分析以前,我給讀者一些建議:緊抓主幹,熟悉流程,再去啃細枝末節!!!

2、SystemServer

先來講說 PackageManagerService 是怎麼啓動的:PackageManagerService 做爲系統的核心服務,由 SystemServer 建立,SystemServer 調用了 PackageManagerService 的 main() 建立 PackageManagerService 實例(關於 SystemServer 的分析能夠閱讀《深刻鑽研 Android 啓動階段 之 SystemServer》)。

2.1 main

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

/**
  * The main entry point from zygote.
  */
public static void main(String[] args) {
    new SystemServer().run();
}

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

2.2 startBootstrapServices

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

private PackageManagerService mPackageManagerService;
private Context mSystemContext;
private boolean mOnlyCore;

private void startBootstrapServices() {
    ... ...

    // 啓動 installer 服務
    Installer installer = mSystemServiceManager.startService(Installer.class);

    // 處於加密狀態則僅僅解析核心應用
    String cryptState = SystemProperties.get("vold.decrypt");
    if (ENCRYPTING_STATE.equals(cryptState)) {
        Slog.w(TAG, "Detected encryption in progress - only parsing core apps");
        mOnlyCore = true;
    } else if (ENCRYPTED_STATE.equals(cryptState)) {
        Slog.w(TAG, "Device encrypted - only parsing core apps");
        mOnlyCore = true;
    }
    
    // 調用 PMS 的 main 函數,主要是建立 PMS 服務,並註冊到 ServiceManager(服務管家)
    mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
                mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);

    // 判斷本次是否爲初次啓動,當 Zygote 或 SystemServer 退出時,init 會再次啓動它們
    // 因此這裏的 FirstBoot 是指開機後的第一次啓動
    mFirstBoot = mPackageManagerService.isFirstBoot();

    // 獲取 PackageManager
    mPackageManager = mSystemContext.getPackageManager();
    ... ...
}

2.3 startOtherServices

// 源碼路徑:frameworks/base/services/java/com/android/server/SystemServer.java
private void startOtherServices() {
    ... ...
    
    traceBeginAndSlog("MakePackageManagerServiceReady");
    mPackageManagerService.systemReady();
    traceEnd();

    ... ...
}

3、PackageManagerService

經過源碼,咱們知道了 SystemServer 調用 PackageManagerService 的 main() 建立了 PackageManagerService 實例。那麼接下來的重點就是關注 PackageManagerService 的 main()

3.1 main

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

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

    // 此處建立構造函數,其中,factoryTest:決定是否測試版本,onlyCore:決定是否只解析系統目錄
    PackageManagerService m = new PackageManagerService(context, installer, 
                                                        factoryTest, onlyCore);

    m.enableSystemUserPackages();

    // 利用 Binder 通訊,將本身註冊到 ServiceManager 進程中(這是 Binder 服務的常規註冊流程)
    ServiceManager.addService("package", m);
    final PackageManagerNative pmn = m.new PackageManagerNative();

    ServiceManager.addService("package_native", pmn);
    return m;
}

該方法主要建立 PMS 對象,並將其註冊到 ServiceManager 中,內部是一個 HashMap 的集合,存儲了不少相關的 Binder 服務,緩存起來,咱們在使用的時候,會經過 getService(key) 的方式去 Map 中獲取。

main 函數看似幾行代碼很簡單,但執行時間卻很長。主要緣由是 PMS 在其「構造函數」中作了不少「重體力活」,這也是 Android 啓動速度慢的主要緣由之一。

具體分析前,咱們先簡單瞭解一下 PMS 構造函數的主要功能:

掃描 Android 系統中幾個目標文件夾中的 APK,從而創建合適的數據結構來管理各類信息,如:Package 信息、四大組件信息、權限信息等。

抽象地來看,PMS 像一個加工廠,它解析實際的物理文件(APK文件)以生成符合本身要求的產品。(例如:PMS 將解析 APK 包中的 AndroidManifest.xml,並根據其中聲明的 Activity 標籤來建立與此對應的對象並加以保管。)

從源碼角度來看,PMS 的工做流程相對簡單。但深刻研究後,發現其很複雜!

複雜的是其中用於保存各類信息的數據結構它們之間的關係,以及影響最終結果的策略控制

若是你自行研究過 PMS,你會發現代碼中存在大量不一樣的數據結構以及它們之間的關係會讓人大爲頭疼。因此,在這篇文章中咱們除了分析 PMS 的工做流程之外,會重點關注重要的數據結構以及它們的做用。

接下來開始重點分析 PMS 的構造函數,若是放在一篇文章中去分析是徹底不可能梳理清楚的!

咱們分兩部分研究,以下:

           🍁  構造函數(1) - 前期準備工做 <font color=#FF0000>(本篇文章要討論的內容)
           🍁  構造函數(2) - 掃描 Package 和 掃尾工做 《Framework 核心服務之 PMS 鑽研(2)- 構造函數》


正式開始分析 PMS 的構造函數:

// 此處建立構造函數,其中,factoryTest:決定是否測試版本,onlyCore:決定是否只解析系統目錄
PackageManagerService m = new PackageManagerService(context, installer, 
                                                    factoryTest, onlyCore);

3.2 Settings

// 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
     * 若是沒有定義,則 APK 就沒法知道本身運行在 Android 哪一個版本上
     */
    if (mSdkVersion <= 0) {
        Slog.w(TAG, "**** ro.build.version.sdk not set!");
    }

    mContext = context;

    mFactoryTest = factoryTest;      // 運行在非工廠模式下
    mOnlyCore = onlyCore;            // 標記是否只加載核心服務
    mMetrics = new DisplayMetrics(); // 存儲與顯示屏相關的一些屬性,例如屏幕的寬/高尺寸,分辨率等信息
    mInstaller = installer;          // 建立 Installer 對象,該對象和 Native 進程 installd 交互

    synchronized (mInstallLock) {
    synchronized (mPackages) {
        // Expose private service for system components to use.
        LocalServices.addService(PackageManagerInternal.class, 
                                 new PackageManagerInternalImpl());
        sUserManager = new UserManagerService(context, this,
                new UserDataPreparer(mInstaller, mInstallLock, mContext, mOnlyCore), 
                mPackages);
        mPermissionManager = PermissionManagerService.create(context,
                new DefaultPermissionGrantedCallback() {
                    @Override
                    public void onDefaultRuntimePermissionsGranted(int userId) {
                        synchronized(mPackages) {
                            mSettings.onDefaultRuntimePermissionsGrantedLPr(userId);
                        }
                    }
                }, mPackages /*externalLock*/);
        mDefaultPermissionPolicy = mPermissionManager.getDefaultPermissionGrantPolicy();
        // Settings 是一個很是重要的類,該類用於存儲系統運行過程當中的一些設置,咱們後面會重點分析這個類!
        mSettings = new Settings(mPermissionManager.getPermissionSettings(), mPackages);
    }
    }

    // 添加system、phone、log、nfc、bluetooth、shell這六種 shareUserId 到 mSettings
    // 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);
    mSettings.addSharedUserLPw("android.uid.se", SE_UID,
                ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
    ... ...
}

剛進入構造函數,咱們就遇到了第一個較爲複雜的數據結構 Settings ,以及它的 addSharedUserLPw 函數。

3.2.1 構造函數

public final class Settings {
    ... ...

    Settings(PermissionSettings permissions, Object lock) {
        this(Environment.getDataDirectory(), permissions, lock);
    }

    Settings(File dataDir, PermissionSettings permission, Object lock) {
        mLock = lock;
        mPermissions = permission;
        mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock);

        // 建立指向 /data/system/ 目錄的 File
        mSystemDir = new File(dataDir, "system");
        // 建立目錄
        mSystemDir.mkdirs();

        FileUtils.setPermissions(mSystemDir.toString(),
                  FileUtils.S_IRWXU|FileUtils.S_IRWXG
                  |FileUtils.S_IROTH|FileUtils.S_IXOTH,
                  -1, -1);

        // 用於描述系統所安裝的 Package 信息
        mSettingsFilename = new File(mSystemDir, "packages.xml");

        // packages.xml的備份信息
        mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml");

        // 保存系統中存在的全部非系統自帶的 APK 信息,即 UID 大於 10000 的 apk
        mPackageListFilename = new File(mSystemDir, "packages.list");
        FileUtils.setPermissions(mPackageListFilename, 0640, SYSTEM_UID, PACKAGE_INFO_GID);

        // sdcardfs 相關的文件
        final File kernelDir = new File("/config/sdcardfs");
        mKernelMappingFilename = kernelDir.exists() ? kernelDir : null;

        // 記錄系統中被強制中止運行的 App 信息,若有 App 被強制中止運行,會將一些信息記錄到該文件中
        mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml");
        
        // packages-stopped.xml 的備份信息
        mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml");
    }

    ... ...
}

Settings 的構造函數的主要工做:創建與某些系統配置文件、目錄之間的關聯。首先,它會建立指向 /data/system/ 目錄的 File 實例,這個目錄下會保存不少系統文件。其次,就是建立 /data/system/ 目錄下的某些 .xml 文件 或其餘文件的 File 實例。

上面源碼中涉及到 5 個文件:

           ✨ packages.xml: PMS 掃描完目標文件夾後,會建立packages.xml。當系統進行程序安裝、卸載和更新等操做時,均會更新該文件;
           ✨ packages-backup.xml:packages.xml 文件的備份;

           ✨ packages.list:用於描述系統中存在的全部非系統自帶的 APK 信息。當這些 APK 有變化時,PKMS就會更新該文件;

           ✨ packages-stopped.xml:記錄被用戶強行中止的應用的 Package 信息(例如,從設置進入某個應用,而後點擊強行中止,那麼應用的Package信息就會被記錄);

           ✨ packages-stopped-back.xml:packages-stopped.xml 文件的備份。

咱們注意到,上面的介紹中涉及到了兩個 back-up 文件,它們是作什麼的呢?其實 Android 系統在修改 packages.xmlpackages-stopped.xml 以前,會先對它們進行 備份。當對它們的修改操做正常完成,則會刪掉備份的文件。若是在修改過程當中系統出現問題重啓了,會再次去讀取這兩個文件;若是此時發現它們的備份文件還存在,則說明上一次對兩份文件的修改操做發生了異常,這兩份文件的內容可能已經不許確了,這時系統會去使用以前備份的文件的內容。

建立完相關係統文件 File 實例後,Settings 的構造工做也就結束了。

以前咱們提出了一個問題:addSharedUserLPw 函數作了什麼?從上面截取一段代碼回顧一下:

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

addSharedUserLPw 傳遞了 4 個參數:

           ✨  android.uid.system:字符串,name 和 uid 一一對應

           public static final int SYSTEM_UID = 1000;
           ✨  Process.SYSTEM_UID:值爲 1000,name 和 uid 一一對應

           public static final int FLAG_SYSTEM = 1<<0;
           ✨  ApplicationInfo.FLAG_SYSTEM:標誌

           public static final int PRIVATE_FLAG_PRIVILEGED = 1<<3;
           ✨  ApplicationInfo.PRIVATE_FLAG_PRIVILEGED:特權Apk

對 addSharedUserLPw 函數分析以前,咱們有必要了解 SYSTEM_UID 的相關知識。


3.2.2 UID/GID

UID 爲 用戶 ID 的縮寫,GID 爲 用戶組 ID 的縮寫。通常來講,每個進程都會有一個對應的 UID(即標示該進程屬於哪一個用戶,不一樣用戶擁有不一樣權限)。一個進程也可分屬不用的用戶組(每一個用戶都有對應的權限)。UID/GID 和進程的權限有關

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

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

public static final int SYSTEM_UID = 1000;      // 系統進程的 UID/GID

public static final int PHONE_UID = 1001;       // Phone 進程的 UID/GID

public static final int SHELL_UID = 2000;       // shell 進程的 UID/GID

public static final int LOG_UID = 1007;         // LOG 進程的 UID/GID

public static final int WIFI_UID = 1010;        // WIFI 進程的 UID/GID

public static final int MEDIA_UID = 1013;       // mediaserver 進程的 UID/GID

public static final int NFC_UID = 1027;         // NFC 進程的 UID/GID

public static final int FIRST_APPLICATION_UID = 10000;// 第一個應用 Package 的起始 UID

public static final int LAST_APPLICATION_UID = 19999; // 系統所支持的最大的應用 Package 的 UID

3.2.3 addSharedUserLPw

如今咱們開始分析 addSharedUserLPw 函數:

// 源碼路徑:frameworks/base/services/core/java/com/android/server/pm/Settings.java
                
SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) {
    // 根據 key 從 map 中獲取值
    SharedUserSetting s = mSharedUsers.get(name);
    // 若是值不爲 null 而且保存的 uid 和傳遞過來的一致,就直接返回結果
    // uid 不一致則返回 null
    if (s != null) {
        if (s.userId == uid) {
            return s;
        }
        PackageManagerService.reportSettingsProblem(Log.ERROR,
                    "Adding duplicate shared user, keeping first: " + name);
        return null;
    }

    // 若 s 爲 null,則根據傳遞過來的參數新建立對象
    s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags);
    s.userId = uid;
    // 在系統中保存值爲 uid 的 用戶 id,成功返回 true
    if (addUserIdLPw(uid, s, name)) {
        mSharedUsers.put(name, s);    // 將 name 與 s 鍵值對添加到 mSharedUsers 中保存
        return s;
    }
    return null;
}

從源碼中咱們發現,Settings 中有一個 mSharedUsers 成員,該成員存儲的是 【「字符串」 與 「SharedUserSetting」 鍵值對】,也就是說能夠經過 字符串key 獲得對應的 SharedUserSetting 對象。

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

3.2.3.1 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">

AndroidManifest.xml 中,聲明瞭一個名爲 android:sharedUserId 的屬性:android.uid.systemui

有必要聊聊這個 "sharedUserId" 的做用!

一、兩個或多個聲明瞭同一種 sharedUserIdAPK 可共享彼此的數據,而且可運行在同一進程中。

二、經過聲明特定的 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,用於保存聲明瞭相同 sharedUserIdPackage 的權限設置信息(這一點咱們以前提到過)。

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 {}

SettingBase 對象持有 PermissionsState 對象,用於表示可用的權限。

abstract class SettingBase {
    protected final PermissionsState mPermissionsState;
}

四、Settings 中還有兩個成員,一個是 mUserIds,另外一個是 mOtherUserIds,這兩位成員的類型分別是 ArrayListSparseArray。其目的是以 UID 爲索引,獲得對應的 SharedUserSeting 對象。在通常狀況下,以 索引 獲取數組元素的速度,比以 Key 獲取 ArrayMap元素 的速度要快不少。

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

3.2.3.2 addUserIdLPw

咱們回憶一下 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);
    ... ...

    s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags);
    s.userId = uid;
    if (addUserIdLPw(uid, s, name)) {
        mSharedUsers.put(name, s);  
        return s;
    }
    return null;
}

源碼中還有一個 addUserIdLPw 方法,它的功能就是將 SharedUserSettings 對象保存到對應的數組中,代碼以下:

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

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

    // 第一個應用 Package(非系統安裝應用)的起始 UID
    if (uid >= Process.FIRST_APPLICATION_UID) {
        // 獲取數組的長度
        int N = mUserIds.size();                   
        // 計算索引,其值是 uid 和 FIRST_APPLICATION_UID 的差
        final int index = uid - Process.FIRST_APPLICATION_UID;
        while (index >= N) {
            mUserIds.add(null);
            N++;
        }
        // 若是數組的目標索引值位置有不爲 null 的值,說明已經添加過
        if (mUserIds.get(index) != null) {
            PackageManagerService.reportSettingsProblem(Log.ERROR,
                    "Adding duplicate user id: " + uid
                    + " name=" + name);
            return false;
        }
        // 應用 Package 的 uid 由 mUserIds 保存
        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 的分析咱們暫時告一段落。

3.2.4 總結

咱們來看一個 SharedUserSettings 的類圖:(類名改變,後期從新補圖)

20160929092228475.jpg

如上圖所示,Settings 對象中持有多個 SharedUserSetting 對象,每一個 SharedUserSetting 對象又會持有多個 PackageSetting 對象。

從繼承關係來看,SharedUserSettingPackageSetting 對象,最終都將繼承 SettingBase 對象。

從圖上能夠看出,SettingBase 對象持有 PermissionsState 對象,用於表示可用的權限。

所以,SharedUserSetting 對象和 PackageSetting 對象中都將包含有 PermissionsState

從而咱們能夠據此推測出,SharedUserSetting 中持有的是一組 Package 共有的權限;PackageSetting 中持有的是單個 Package 獨有的權限。


3.3 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)) {
            mDefParseFlags = PackageParser.PARSE_IGNORE_PROCESSES;
            mSeparateProcesses = null;
            Slog.w(TAG, "Running with debug.separate_processes: * (ALL)");
        } else {
            mDefParseFlags = 0;
            mSeparateProcesses = separateProcesses.split(",");
            Slog.w(TAG, "Running with debug.separate_processes: " + separateProcesses);
        }
    } else {
        mDefParseFlags = 0;
        mSeparateProcesses = null;
    }

    // 對應用進行 dexopt 優化的輔助類
    mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,
                                                   "*dexopt*");
    DexManager.Listener dexManagerListener = DexLogger.getListener(this, installer,
                                                                   mInstallLock);
    mDexManager = new DexManager(this, mPackageDexOptimizer, installer, mInstallLock);
    mArtManagerService = new ArtManagerService(mContext, this, installer, mInstallLock);
    mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());

    mOnPermissionChangeListeners = new 
                                   OnPermissionChangeListeners(FgThread.get().getLooper());

    // 獲取當前設備的顯示屏信息
    getDefaultDisplayMetrics(context, mMetrics);
    
    // 經過 SystemConfig 讀取系統的 feature、permession 等配置,
    // 並初始化 mGlobalGids/mAvailableFeatures 成員
    SystemConfig systemConfig = SystemConfig.getInstance();    // 重點討論
    mAvailableFeatures = systemConfig.getAvailableFeatures();
    mProtectedPackages = new ProtectedPackages(mContext);

    ... ... 
 }

以上代碼除了建立了幾個對象之外,還有一個重要的須要關注的類:SystemConfig,這就是咱們接下來分析的重點!!!

3.3.1 SystemConfig

咱們先來分析 SystemConfig systemConfig = SystemConfig.getInstance() 函數!

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

/**
 * Loads global system configuration info.
 */
public class SystemConfig {
    static SystemConfig sInstance;

    ... ...

    public static SystemConfig getInstance() {       // 單例模式
        synchronized (SystemConfig.class) {
            if (sInstance == null) {
                sInstance = new SystemConfig();
            }
            return sInstance;
        }
    }
    ... ...
}

查看它的構造函數:

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

/**
 * 經過 readPermissions() 讀取並解析 /system/etc/ 等目錄下的 sysconfig.xml、permission.xml 文件
 */
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);

    // Vendors are only allowed to customze libs, features and privapp permissions
    int vendorPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_PRIVAPP_PERMISSIONS;
    if (Build.VERSION.FIRST_SDK_INT <= Build.VERSION_CODES.O_MR1) {
        // For backward compatibility
        vendorPermissionFlag |= (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 as much as Vendor, because /odm is another
    // vendor partition other than /vendor.
    int odmPermissionFlag = vendorPermissionFlag;
    readPermissions(Environment.buildPath(
            Environment.getOdmDirectory(), "etc", "sysconfig"), odmPermissionFlag);
    readPermissions(Environment.buildPath(
            Environment.getOdmDirectory(), "etc", "permissions"), odmPermissionFlag);

    // Allow OEM to customize features and OEM permissions
    int oemPermissionFlag = ALLOW_FEATURES | ALLOW_OEM_PERMISSIONS;
    readPermissions(Environment.buildPath(
            Environment.getOemDirectory(), "etc", "sysconfig"), oemPermissionFlag);
    readPermissions(Environment.buildPath(
            Environment.getOemDirectory(), "etc", "permissions"), oemPermissionFlag);

    // Allow Product to customize system configs around libs, features, permissions and apps
    int productPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_PERMISSIONS |
            ALLOW_APP_CONFIGS | ALLOW_PRIVAPP_PERMISSIONS;
    readPermissions(Environment.buildPath(
            Environment.getProductDirectory(), "etc", "sysconfig"), productPermissionFlag);
    readPermissions(Environment.buildPath(
            Environment.getProductDirectory(), "etc", "permissions"), productPermissionFlag);
}

咱們發現 SystemConfig 的構造函數所作的工做就是:readPermissions(),即從文件中讀取權限

3.3.2 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 函數解析 "/xxx/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>

    <!-- 創建權限名與 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>

    ... ...

    <!-- 賦予對應 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 個標籤

         ✨ permissiongroup 用於創建 Linux 層 gid 和 Andrid 層 permission 之間的映射關係。

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

         ✨ library 用於指定系統庫。當應用程序運行時,系統會自動爲這些進程加載這些庫。

不知道你是否已經產生了疑問?設備上的 /system/etc/permission 目錄中的文件是從哪裏來的?我直接告訴你答案:在編譯階段由不用硬件平臺根據本身的配置信息複製相關文件到目標目錄中的來的。(這個具體咱們不討論,有興趣的讀者能夠自行查閱)

3.3.3 readPermissionFromXML

前面咱們說過:readPermissions 函數其實就是調用 readPermissionFromXml 函數解析 "/xxx/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 中的標籤轉換成對應的數據結構!


參考文章

 01. Android7.0 PackageManagerService (2) PKMS構造函數的主要工做
 02. APK安裝流程詳解7——PackageManagerService的啓動流程(上)

相關文章
相關標籤/搜索