Android包管理機制

very_on發表於2018-04-02

請尊重原創版權,轉載註明出處。

1 概要

每一個社會群落都有管理機制,其中有三個要素:被管理者、管理者以及管理機制的運轉。在Android的世界中,有一處群落叫“包管理”,要研究Android的包管理機制,同樣可以從以下幾個角度來思考:

  1. 被管理的物件是什麼?
  2. 管理者的職能是什麼?
  3. 管理機制是如何運轉的?

所謂包,其實就是一種檔案格式,譬如APK包、JAR包等。在Android中存活著很多包,所有的應用程式都是APK包,很多構成Android執行環境的都是JAR包,還有一些以so為字尾的庫檔案,包管理者很重要的一個職能就是識別不同的包,統一維護這些包的資訊。當有一個包進入或離開Android世界,都需要向包管理者申報,其他管理部門要獲取包的具體資訊,也都需要向包管理者申請。

如同社會是由人與人的協作形成,不同的包之間也需要進行協作。既然有協作,自然就有協作的規範,一個包可以幹什麼,不可以幹什麼,都需要有一個明確的範圍界定,這就是包管理中的許可權設計。涉及到的內容非常廣泛,Linux的UGO(User Group Other)和ACL(Access Control List,訪問控制列表)許可權管理、數字簽名與驗證、Android授權機制、Selinux,都是包管理中許可權設計的組成部分。

Android的世界就如同一個井然有序的人類社會,除了包管理部門,還有其他各種管理部門,譬如電源管理、視窗管理、活動管理等等,大家不僅各司其職,而且也有交流往來。從APK的安裝到Activity的顯示這麼一個看似簡單的過程,卻需要大量管理部門參與進來,不斷地進行資料解析、封裝、傳遞、呈現,內部機理十分複雜。

PackageManagerService是包管理中最重要的服務,為了描述方便,本文會簡寫成PMS

PMS的部分函式帶有LI字尾,表示需要獲取mInstalllock這個鎖時才能執行;部分函式帶有LP字尾,表示需要獲取mPackages這個鎖才能執行。

2 被管理物件的形態

Android中的APK和JAR包都以靜態檔案的形式分佈在不同的硬體分割槽,包管理者面臨的第一個任務就是將這些靜態的檔案轉化成記憶體的資料結構,這樣才能將其管理起來。Android中最重要的包管理物件就是APK,APK可以包含so檔案,負責將靜態檔案轉換記憶體中資料結構的工具就是PackageParser,包解析器。

Package from static to dynamic

Android L(5.0)以後,支援APK拆分,即一個APK可以分割成很多部分,位於相同的目錄下,每一個部分都是一個單獨的APK檔案,所有的APK檔案具備相同的簽名,在APK解析過程中,會將拆分的APK重新組合成記憶體中的一個Package。對於一個完整的APK,Android稱其為Monolithic;對於拆分後的APK,Android稱其為Cluster

在Android L(5.0)以前,APK檔案都是直接位於apppriv-app目錄下,譬如短彩信APK的目錄就是/system/priv-app/Mms.apk;到了Android L(5.0)之後,多了一級目錄結構,譬如短彩信APK的目錄是/system/priv-app/Mms/Mms.apk,這是Android為了支援APK拆分而做的改動,如果要將短彩信APK進行拆分,那所有被拆出來的APK都位於/system/priv-app/Mms/即可,這樣在包解析時,就會變成以Cluster的方式解析目錄。

一個包在記憶體中的資料結構就是Package,那麼,Package有一些什麼屬性?是怎麼從APK檔案中獲取資料的呢? 這就涉及到包解析器的工作原理。

2.1 包解析器

為了先讓讀者對被管理物件有一個初步的認識,我們先把一個包最終在記憶體中的資料結構拎出來。其實生成這個資料結構,需要包管理者進行大量的排程工作,排程中心是PMS,包解析的過程也都是由PMS驅動的。在分析包解析過程之前,我們先上包解析的結果:

Package Parser

這個類圖,示意了一個包最終在記憶體中的資料結構Package,它包含很多屬性,部分屬性還是包解析器中的子資料結構。我們可以從設計的角度來理解這個類圖:

  • 一個包中有很多元件,為此設計了一個高層的基類Component,所有具體的元件都是Component的子類。什麼是元件呢?AndroidManifest.xml檔案中所定義的的一些標籤,就是元件,譬如<activity>,<service>,<provider>,<permission>等,這些標籤分別對應到包解析器中的一個資料結構,它們各自有自身的屬性。

  • 諸如<activity>,<service>標籤,都可以配置<intent-filter>,來過濾其可以接收的Intent,這些資訊也需要在包解析器中體現出來,為此元件Component依賴於IntentInfo這個資料結構。每一個具體的元件所依賴的IntentInfo不同,所以ComponentIntentInfo之間的依賴關係採用了橋接(Bridge)這種設計模式,通過泛型的手段實現。

  • 各種元件最終聚合到Package這個資料結構中,形成了最終包解析器的輸出。當然,在解析的過程中,還有利用了一些資料結構來優化設計,PackageLiteApkLite就是一些很簡單的資料封裝。

要得到以上的資料結構,包解析器PackageParser功不可沒,從接收一個靜態的檔案(File型別)開始,會經過一個漫長的包解析過程,直到生成最終的Package

parsePackages(File file...)
└── parseClusterPackage(File packageDir...)
    └── parseClusterPackageLite(File packageDir...)
    |   └── parseApkLite(File apkFile...)
    |       └── parseApkLite(String codePath...)
    └── parseBaseApk()
        └── parseBaseApplication()
        |    └── parseActivity()
        |    └── parseService()
        |    └── ...
        └── parseInstrumentation()
        └── ...

這些函式的具體邏輯本文不予分析,僅把關鍵的流程捋出來:

  1. PackageParser.parsePackages()是包解析器的入口函式,它首先會判定給定的輸入是否為一個目錄,如果是目錄,則以為著目錄下可能存在多個拆分後的APK,這就需要以Cluster的方式進行解析;如果僅僅是一個APK檔案,就以Monolithic的方式解析;

  2. 解析APK,需要先得到一箇中間資料結構PacakgeLite,包名、版本、拆分包等資訊都會儲存在這個資料結構中;由於一個包可能有多個拆分的APK,所以PackageLite可能關聯到多個APK,每一個APK都對應到ApkLite這個資料結構,也是一些基本資訊的封裝。之所以以Lite為字尾命名,是因為這兩個資料結構都比較輕量,只儲存APK中很少資訊;

  3. 一個APK真正的資訊都寫在AndroidManifest.xml這個檔案中,PackageParser.parseBaseApk()這個函式就是用來解析該檔案。其解析過程與AndroidManifest.xml的檔案結構一一對應,譬如先解析<application>標籤的內容,然後解析其下的<activity>,<service>等標籤。由於AndroidManifest.xml檔案的結構非常複雜,所以該函式邏輯也非常龐大,讀者們可以自行分析原始碼。

至此,包解析器PackageParser就將一個靜態的檔案,轉換成了記憶體中的資料結構Package,它包含了一個包的所有資訊,如包名、包路徑、許可權、四大元件等,其資料來源主要就是AndroidManifest.xml檔案。

2.2 包資訊體

包解析器從靜態檔案中獲取的資料,很多都是需要用於跨程式傳遞的,譬如初次啟動Activity時,就需要把包資訊從系統程式傳遞到應用程式,先完成應用程式的啟動。在包解析器的類圖中,我們看到Activity、Service、Provider、Permission、Instrumentaion這些類都有一個共同的特徵:都具備info這個屬性,其實這些類的結構非常簡單,就是對info的一次封裝,info這個結構體才是真正的包資料,筆者暫且稱之為“包資訊體”:

Package Parser Parcelable

所有的info都實現了Parcelable介面,意圖很明顯,info是可以進行跨程式傳遞的。不同元件的info型別是不同的,除了實現了Parcelable介面,它們之間又構成了一個龐大的資料結構,把這些具體的info型別展開,就是以下的類圖:

Package Parser Parcelable

可以看到,這個類圖與PackageParser中的類圖在結構上很相似,我們依舊是從設計的角度來理解這個類圖:

  • PackageItemInfo作為包每一項資訊的高層基類:

    • 針對permissionpermissioninstrumentation等,分別為其設計了一個類,都繼承自PackageItemInfo
    • 針對activityserviceprovider等四大元件,在PackageItemInfo之下又多設計了一層:ComponentInfo作為四大元件的基類
    • ApplicationInfo也是包資訊中的一項,但與四大元件緊密相連,四大元件肯定都屬於某個Application,所以ComponentInfoApplication存在依賴關係,繼而,具體到每個元件都與Application存在依賴關係
  • 所有的包資訊都聚合到PackageInfo這個類中,PackageInfo就是一個包向外提供的所有資訊。其實除了上圖列出來的類,還有一些類沒有示意出來,譬如ConfigurationInfo,FeatureInfo,它們都可以對應到AndroidManifest.xml中的標籤。

這些結構體中的資料,都是在包解析器時初始化的,譬如Activity依賴於ActivityInfo,在解析Activity時,就會建立一個ActivityInfo物件,把<activity>所定義的資料全都填充到ActivityInfo中。讀者可以思考一下PackageParser中的Activity與此處的ActivityInfo的分開設計的目的和好處是什麼?

在分析包的形態時,我們見到了很多類,類的命名方式還有點相似,初讀程式碼的時候,很容易陷入各個類之間複雜的關係網之中。不得不說,包在記憶體中的資料結構是比較龐大的,因為它蘊含的資訊大多了。

3 PMS的啟動過程

3.1 物件構建

// 程式碼片段1:PMS部分屬性的初始化
public PackageManagerService(Context context, Installer installer,
        boolean factoryTest, boolean onlyCore) {
    // 關鍵的Event日誌,PMS開始啟動了
    EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START,
            SystemClock.uptimeMillis());

    mContext = context;
    mFactoryTest = factoryTest;
    mOnlyCore = onlyCore;
    // 如果是eng版,則延遲做DexOpt
    mLazyDexOpt = "eng".equals(SystemProperties.get("ro.build.type"));
    mMetrics = new DisplayMetrics();
    // Settings是包管理中一個很重要的資料結構,用於維護所有包的資訊
    mSettings = new Settings(mPackages);
    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);

    long dexOptLRUThresholdInMinutes;
    if (mLazyDexOpt) {
        dexOptLRUThresholdInMinutes = 30; // only last 30 minutes of apps for eng builds.
    } else {
        dexOptLRUThresholdInMinutes = 7 * 24 * 60; // apps used in the 7 days for users.
    }
    mDexOptLRUThresholdInMills = dexOptLRUThresholdInMinutes * 60 * 1000;
    ...
    mInstaller = installer;
    // DexOpt工具類
    mPackageDexOptimizer = new PackageDexOptimizer(this);
    mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());
    mOnPermissionChangeListeners = new OnPermissionChangeListeners(
                FgThread.get().getLooper());
    getDefaultDisplayMetrics(context, mMetrics);
    // 讀取系統預設的許可權
    SystemConfig systemConfig = SystemConfig.getInstance();
    mGlobalGids = systemConfig.getGlobalGids();
    mSystemPermissions = systemConfig.getSystemPermissions();
    mAvailableFeatures = systemConfig.getAvailableFeatures();

    // 這裡上了兩把鎖: mInstallLock是安裝APK時需要用到的鎖;mPackage是更新APK資訊時需要的鎖
    synchronized (mInstallLock) {
    synchronized (mPackages) {
        // 構建一個後臺執行緒,並將執行緒的訊息佇列繫結到Handler
        mHandlerThread = new ServiceThread(TAG,
                Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
        mHandlerThread.start();
        mHandler = new PackageHandler(mHandlerThread.getLooper());
        // 將PMS加入Watchdog的監控列表
        Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);

        // 初始化一些檔案目錄
        File dataDir = Environment.getDataDirectory();
        mAppDataDir = new File(dataDir, "data");
        mAppInstallDir = new File(dataDir, "app");
        mAppLib32InstallDir = new File(dataDir, "app-lib");
        mAsecInternalPath = new File(dataDir, "app-asec").getPath();
        mUserAppDataDir = new File(dataDir, "user");
        mDrmAppPrivateInstallDir = new File(dataDir, "app-private");

        // 初始化系統許可權
        ArrayMap<String, SystemConfig.PermissionEntry> permConfig
                = systemConfig.getPermissions();
        for (int i=0; i<permConfig.size(); i++) {
            SystemConfig.PermissionEntry perm = permConfig.valueAt(i);
            BasePermission bp = mSettings.mPermissions.get(perm.name);
            if (bp == null) {
                bp = new BasePermission(perm.name, "android", BasePermission.TYPE_BUILTIN);
                mSettings.mPermissions.put(perm.name, bp);
            }
            if (perm.gids != null) {
                bp.setGids(perm.gids, perm.perUser);
            }
        }

        // 初始化PMS中的ShareLibraries
        ArrayMap<String, String> libConfig = systemConfig.getSharedLibraries();
        for (int i=0; i<libConfig.size(); i++) {
            mSharedLibraries.put(libConfig.keyAt(i),
                    new SharedLibraryEntry(libConfig.valueAt(i), null));
        }

        // 讀取mac_permission.xml檔案的內容
        mFoundPolicyFile = SELinuxMMAC.readInstallPolicy();

        mRestoredSettings = mSettings.readLPw(this, sUserManager.getUsers(false),
                mSdkVersion, mOnlyCore);
        String customResolverActivity = Resources.getSystem().getString(
                        R.string.config_customResolverActivity);

// 未完接程式碼片段2

【程式碼片段1】完成了很多PMS的屬性初始化操作,幾個重要的屬性如下:

  • mSettings:PMS內部有一個Settings資料結構,用於維護所有包的資訊。寫過Android應用程式的朋友可能知道兩個APK可以執行在相同的程式中,前提是兩個APK具有相同的簽名和ShareUid。Android系統中定義了一些預設的ShareUid,譬如android.uid.system表示系統程式的UID,如果有一個APK想要執行在系統程式中,則其需要在AndroidManifest.xml檔案中宣告ShareUid為android.uid.system,並且該APK的簽名必須與framework-res.apk的簽名一致,即platform簽名。

    PMS在建立完Settings物件之後,便把很多系統預設的ShareUid加入其中。

  • mInstaller:Installer是一個系統服務,它封裝了很多PMS進行包管理需要用到的函式,譬如install()、 dexopt()、rename()等。在Installer內部,其實是通過Socket連線installd,將執行指令傳送到installd完成具體的操作。

  • mPackageDexOptimizer: 進行Dex優化的工具類。對於一個APK而言,編譯後其APK包中可執行檔案的格式dex,安裝到了手機上以後,需要經過檔案格式轉化才能執行,譬如APK需要轉換成oat格式才能在ART虛擬機器上執行,檔案格式轉換的過程就叫DexOpt。

    DalvikVM的時代,Android可執行檔案的格式是dex,有一種進一步優化的格式叫odex;ART虛擬機器的時代,Android可執行檔案的格式是oat。雖然都叫做DexOpt,但在DalvikVM和ART兩種不同虛擬機器的時代分別有不同的內涵。

  • SystemConfig: 系統全域性的配置資訊的資料結構。原始的資料來源於/system/etc/sysconfig/system/etc/permissions目錄下的XML檔案,在SystemConfig物件構建時,會讀取這兩個目錄下所有XML檔案的內容,主要有以下幾個維度:

    • 許可權與GID的對映關係,譬如,以下內容表示屬於inet這個組的使用者都擁有android.permission.INTERNET許可權:

      <permission name="android.permission.INTERNET" >
          <group git="inet">
      </permission>
      
    • 許可權與UID的對映關係,譬如,以下內容表示UID為meida使用者擁有android.permission.CAMERA這個許可權:

      <assign-permission name="android.permission.CAMERA" uid="media" />
      
    • 公共庫的定義。Android中有很多公共庫,除了BOOTCLASSPATH中定義的,框架層還支援額外的擴充套件,譬如,以下內容表示公共庫的包名和其路徑的關係:

      <library name="android.test.runner"
               file="/system/framework/android.test.runner.jar" />
      
  • mHandler: 建立PackageHandler物件,將其繫結到一個後臺執行緒的訊息佇列。可想而知,一些厚重的活,譬如安裝APK,就交由這個後臺執行緒完成了。由於PMS是一個重要的系統服務,這個後臺執行緒的訊息佇列如果過於忙碌,則會導致系統一直卡住,所以需要將這個訊息佇列加入Watchdog的監控列表,以便在這種情況下,Watchdog可以做出一些應急操作。

  • 初始化一些/data檔案目錄:應用程式的安裝和執行都需要用到Data分割槽,PMS會在Data分割槽新建一些子目錄。

  • 初始化系統許可權:在SystemConfig初始化的時候,從/system/etc/permissions/system/etc/sysconfig目錄下讀取了XML檔案,這些資訊要新增到Settings這個資料結構中。Android設計了一個BasePermission的資料結構,主要用於儲存許可權與包名之間的對映關係,此處,新增的許可權是從SystemConfig中取出,包名是android,也就是先將系統許可權新增到Settings中。

  • mFoundPolicyFile: 有了SeLinux以後,Android會為每個檔案打上SE Label,對於APK而言,打SE Label的準則就是簽名,即根據簽名資訊打上不同的SE Label。Android將簽名分類成為platform,testkey, media等,簽名與類別的對映關係就存在一個叫mac_permission.xml的檔案中。此處,需要讀取該檔案的內容。

在完成部分屬性的初始化之後,PMS要進入掃描安裝階段了。

//程式碼片段2:DexOpt處理,掃描系統檔案
    // 關鍵日誌,開始掃描系統APP
    EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SYSTEM_SCAN_START,
            startTime);

    // 設定掃描引數
    final int scanFlags = SCAN_NO_PATHS | SCAN_DEFER_DEX | SCAN_BOOTING | SCAN_INITIAL;
    // 1. 構建陣列變數用於儲存已經做過DexOpt的檔案,後文中,會往這個陣列變數中新增元素
    final ArraySet<String> alreadyDexOpted = new ArraySet<String>();
    final String bootClassPath = System.getenv("BOOTCLASSPATH");
    final String systemServerClassPath = System.getenv("SYSTEMSERVERCLASSPATH");

    // BOOTCLASSPATH環境變數所定義的檔案已經做過DexOpt
    if (bootClassPath != null) {
        String[] bootClassPathElements = splitString(bootClassPath, ':');
        for (String element : bootClassPathElements) {
            alreadyDexOpted.add(element);
        }
    }

    // SYSTEMSERVERCLASSPATH環境變數所定義的檔案已經做過了DexOpt
    if (systemServerClassPath != null) {
        String[] systemServerClassPathElements = splitString(systemServerClassPath, ':');
        for (String element : systemServerClassPathElements) {
            alreadyDexOpted.add(element);
        }
    }

    // 獲取指令集,以便後續進行DexOpt
    final List<String> allInstructionSets = InstructionSets.getAllInstructionSets();
    final String[] dexCodeInstructionSets =
            getDexCodeInstructionSets(
                        allInstructionSets.toArray(new String[allInstructionSets.size()]));

    // 公共庫是定義在 etc/sysconfig 和 etc/permissions 資料夾下的XML檔案中
    // 需要這些公共庫進行DexOpt處理
    if (mSharedLibraries.size() > 0) {
        for (String dexCodeInstructionSet : dexCodeInstructionSets) {
            for (SharedLibraryEntry libEntry : mSharedLibraries.values()) {
                final String lib = libEntry.path;
                if (lib == null) {
                    continue;
                }
                try {
                    int dexoptNeeded = DexFile.getDexOptNeeded(lib, null, dexCodeInstructionSet, false);
                    if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
                        alreadyDexOpted.add(lib);
                        mInstaller.dexopt(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet, dexoptNeeded);
                    }
                } catch (...)
            }
        }
    }

    File frameworkDir = new File(Environment.getRootDirectory(), "framework");
    alreadyDexOpted.add(frameworkDir.getPath() + "/framework-res.apk");
    alreadyDexOpted.add(frameworkDir.getPath() + "/core-libart.jar");

    // system/framework目錄下,除了framework-res.apk和core-libart.jar這兩個檔案外
    // 其他的APK和JAR檔案都需要進行DexOpt處理
    String[] frameworkFiles = frameworkDir.list();
    if (frameworkFiles != null) {
        for (String dexCodeInstructionSet : dexCodeInstructionSets) {
            for (int i=0; i<frameworkFiles.length; i++) {
                File libPath = new File(frameworkDir, frameworkFiles[i]);
                String path = libPath.getPath();
                if (alreadyDexOpted.contains(path)) {
                    continue;
                }
                if (!path.endsWith(".apk") && !path.endsWith(".jar")) {
                    continue;
                }
                try {
                    int dexoptNeeded = DexFile.getDexOptNeeded(path, null, dexCodeInstructionSet, false);
                    if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
                        mInstaller.dexopt(path, Process.SYSTEM_UID, true, dexCodeInstructionSet, dexoptNeeded);
                    }
                } catch(...)
            }
        }
    }

    // 2. Android M的APK授權機制有了變化,此處是與授權相關的版本相容處理
    final VersionInfo ver = mSettings.getInternalVersion();
    mIsUpgrade = !Build.FINGERPRINT.equals(ver.fingerprint);
    mPromoteSystemApps =
            mIsUpgrade && ver.sdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1;
    if (mPromoteSystemApps) {
        Iterator<PackageSetting> pkgSettingIter = mSettings.mPackages.values().iterator();
        while (pkgSettingIter.hasNext()) {
            PackageSetting ps = pkgSettingIter.next();
            if (isSystemApp(ps)) {
                mExistingSystemPackages.add(ps.name);
            }
        }
    }

    // 3. 掃描系統檔案
    File vendorOverlayDir = new File(VENDOR_OVERLAY_DIR);
    // 掃描 /vendor/overlay 目錄下的檔案
    scanDirLI(vendorOverlayDir, PackageParser.PARSE_IS_SYSTEM
            | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags | SCAN_TRUSTED_OVERLAY, 0);
    // 掃描 /system/framework 目錄下的檔案
    scanDirLI(frameworkDir, PackageParser.PARSE_IS_SYSTEM
            | PackageParser.PARSE_IS_SYSTEM_DIR
            | PackageParser.PARSE_IS_PRIVILEGED,
            scanFlags | SCAN_NO_DEX, 0);
    // 掃描 /system/priv-app 目錄下的檔案
    final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
    scanDirLI(privilegedAppDir, PackageParser.PARSE_IS_SYSTEM
            | PackageParser.PARSE_IS_SYSTEM_DIR
            | PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0);
    // 掃描 /system/app 目錄下的檔案
    final File systemAppDir = new File(Environment.getRootDirectory(), "app");
    scanDirLI(systemAppDir, PackageParser.PARSE_IS_SYSTEM
            | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
    // 掃描 /vendor/app 目錄下的檔案
    File vendorAppDir = new File("/vendor/app");
    try {
        vendorAppDir = vendorAppDir.getCanonicalFile();
    } catch (IOException e) {}
    scanDirLI(vendorAppDir, PackageParser.PARSE_IS_SYSTEM
            | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
    // 掃描 /oem/app 目錄下的檔案
    final File oemAppDir = new File(Environment.getOemDirectory(), "app");
    scanDirLI(oemAppDir, PackageParser.PARSE_IS_SYSTEM
            | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
    mInstaller.moveFiles();

    // 4. 對掃描到的系統檔案善後處理
    final List<String> possiblyDeletedUpdatedSystemApps = new ArrayList<String>();
    if (!mOnlyCore) {
        Iterator<PackageSetting> psit = mSettings.mPackages.values().iterator();
        while (psit.hasNext()) {
            PackageSetting ps = psit.next();
            if ((ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0) {
                continue;
            }
            final PackageParser.Package scannedPkg = mPackages.get(ps.name);
            if (scannedPkg != null) {
                if (mSettings.isDisabledSystemPackageLPr(ps.name)) {
                    removePackageLI(ps, true);
                    mExpectingBetter.put(ps.name, ps.codePath);
                }
                continue;
            }

            if (!mSettings.isDisabledSystemPackageLPr(ps.name)) {
                psit.remove();
                removeDataDirsLI(null, ps.name);
            } else {
                final PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(ps.name);
                if (disabledPs.codePath == null || !disabledPs.codePath.exists()) {
                    possiblyDeletedUpdatedSystemApps.add(ps.name);
                }
            }
        }
    }

    ArrayList<PackageSetting> deletePkgsList = mSettings.getListOfIncompleteInstallPackagesLPr();
    for(int i = 0; i < deletePkgsList.size(); i++) {
        cleanupInstallFailedPackage(deletePkgsList.get(i));
    }
    deleteTempPackageFiles();
    mSettings.pruneSharedUsersLPw();
// 未完接程式碼片段3

【程式碼片段2】的主體邏輯如下:

  1. 通過不斷往alreadyDexOpted陣列中填充元素,來略過不需要做DexOpt的檔案:BOOTCLASSPATH和SYSTEMSERVERPATH這兩個環境變數中定義的檔案、system/framework-res.apk、system/core-libart.jar。除略過的檔案外,其他APK和JAR檔案都是需要做DexOpt處理的,通過呼叫Installer.dexopt()函式完成,這個函式只是將dexopt命令傳送給installd。

  2. 由於Android M的APK授權機制發生了變化,在掃描系統檔案之前,做了一些簡單的記錄,以便後續的授權處理:

    • mIsUpgrade:如果當前版本的指紋與歷史版本的指紋資訊不一致,表示當前版本是一次OTA升級上來更新版本
    • mPromoteSystemApps:如果歷史版本是Android M之前的版本(ver.sdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1),當前又有版本升級,則需要用一個布林變數,表示當前需要對系統應用的授權做特殊處理,此時會先把已有的系統應用都儲存在mExistingSystemPackages這個陣列中
  3. 掃描系統檔案,PMS中所有的檔案掃描都是呼叫scanDirLI()函式,掃描系統檔案重要的引數就是 PackageParser.PARSE_IS_SYSTEMPackageParser.PARSE_IS_SYSTEM_DIR,在後文中我們會剖析這個函式。此處,需要注意的是被掃描目錄的順序,這個順序意味著:先被掃描到的檔案,就是最終被用到的檔案。

     /vendor/overlay >> /system/framework >> /system/priv-app >> /system/app >> /vendor/app >> /oem/app
    
  4. possiblyDeletedUpdatedSystemApps這個變數表示“可能被刪除的系統APP”,這是一個什麼概念呢?除了possiblyDeletedUpdatedSystemApps,還有mExpectingBetter,表示當前這個APK有更好的選擇,這又是什麼概念呢?對於一個系統APP而言,在一次OTA升級的過程中,有三種可能:

    • 保持原狀。即這個系統APP沒有任何更新。
    • 更新版本。即新的OTA版本中,這個系統APP有更新。
    • 不復存在。在新的OTA版本中已經刪除了這個系統APP。

    當系統APP升級過後,PMS的Settings中會將原來的系統APP標識為Disable狀態,這時候通過Settings.isDisabledSystemPackageLPr()函式呼叫便返回了false。因此,如果系統APP有更新版本,則屬於mExpectingBetter這一類,接下來會掃描Data分割槽的檔案,更新的系統APP就安裝在Data分割槽。

    如果一個系統APP不復存在,而且也沒有被標記為Disable狀態,說明這個系統APP已經徹底不存在了,需要把其在Data分割槽下的資料刪除;如果不復存在的系統APP被標記為Disable狀態,那還不能確定該系統APP是否已經被刪除,因為還沒有掃描Data分割槽的檔案,所以,只能暫時將其放到possiblyDeletedUpdatedSystemApps變數中,表示“可能被刪除”,在掃描Data分割槽之前,這是不能確定的。

掃描完系統檔案之後,接下來會掃描Data分割槽的檔案。

// 程式碼片段3:掃描Data分割槽檔案,更新公共庫資訊
    if (!mOnlyCore) {
        // 關鍵日誌,PMS對Data分割槽的檔案掃描開始了
        EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
                SystemClock.uptimeMillis());
        // 1. 掃描Data分割槽的檔案目錄
        scanDirLI(mAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0);
        scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK,
                scanFlags | SCAN_REQUIRE_KNOWN, 0);
        // 2. 掃描完Data分割槽後,處理“可能被刪除的系統應用”
        for (String deletedAppName : possiblyDeletedUpdatedSystemApps) {
            PackageParser.Package deletedPkg = mPackages.get(deletedAppName);
            mSettings.removeDisabledSystemPackageLPw(deletedAppName);
            String msg;
            if (deletedPkg == null) {
                removeDataDirsLI(null, deletedAppName);
            } else {
               deletedPkg.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM;
               PackageSetting deletedPs = mSettings.mPackages.get(deletedAppName);
               deletedPs.pkgFlags &= ~ApplicationInfo.FLAG_SYSTEM;
            }
        }
        // 3. 處理有版本更新的系統應用
        for (int i = 0; i < mExpectingBetter.size(); i++) {
            final String packageName = mExpectingBetter.keyAt(i);
            if (!mPackages.containsKey(packageName)) {
                final File scanFile = mExpectingBetter.valueAt(i);
                final int reparseFlags;
                // 設定重新掃描的解析引數
                if (FileUtils.contains(privilegedAppDir, scanFile)) {
                    reparseFlags = PackageParser.PARSE_IS_SYSTEM
                            | PackageParser.PARSE_IS_SYSTEM_DIR
                            | PackageParser.PARSE_IS_PRIVILEGED;
                } else {...}

                // 將原來的系統應用重新置為Enable狀態
                mSettings.enableSystemPackageLPw(packageName);
                try {
                    scanPackageLI(scanFile, reparseFlags, scanFlags, 0, null);
                } catch (PackageManagerException e) { ... }
            }
        }
    } // end of "if (!mOnlyCore)"
    mExpectingBetter.clear();

    // 4. 處理公共庫
    updateAllSharedLibrariesLPw();
    for (SharedUserSetting setting : mSettings.getAllSharedUsersLPw()) {
         adjustCpuAbisForSharedUserLPw(setting.packages, null /* scanned package */,
                 false /* force dexopt */, false /* defer dexopt */);
    }

    mPackageUsage.readLP();
    // 關鍵日誌,PMS掃描結束了
    EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SCAN_END,
            SystemClock.uptimeMillis());
// 未完接程式碼片段4

Data分割槽檔案的掃描都被mOnlyCore這個布林變數籠罩,當其為true時,表示只需要掃描系統檔案;當其為false時,才會掃描Data分割槽檔案。【程式碼片段3】的主體邏輯如下:

  1. 呼叫PMS.scanDirLI()函式掃描 /data/app 和 /data/app-private兩個目錄的檔案。後文會詳細剖析該函式。

  2. 掃描完Data分割槽的檔案後,需要對之前系統檔案的餘孽做一些處理,第一類是possiblyDeletedUpdatedSystemApps,因為在掃描Data分割槽檔案之前,不能確定系統應用有沒有被徹底刪除,如果在Data分割槽也無法找到了不復存在的系統應用,則需要徹底刪除該系統應用;如果在Data分割槽找到了不復存在的系統應用,則需要去除其系統應用的標識。

  3. 另外一類系統應用的餘孽是mExpectingBetter,表示系統應用已經升級過。如果在Data分割槽無法找到這些升級過的系統應用,那很可能是使用者在OTA升級時,清除了Data分割槽的資料,對於這種場景,需要重新掃描一下該應用原來位於系統分割槽的檔案。

  4. SystemConfig中定義了公共庫,在APK的AndroidManifest.xml檔案中,會通過<use-library>標籤標記該APK動態依賴的公共庫,此處的邏輯就是將APK與SystemConfig中的公共庫關聯起來。如果APK使用的公共庫並不存在,則會丟擲異常(INSTALL_FAILED_MISSING_SHARED_LIBRARY)。

// 程式碼片段4:收尾工作
    // 授權
    int updateFlags = UPDATE_PERMISSIONS_ALL;
    if (ver.sdkVersion != mSdkVersion) {
        updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL;
    }
    updatePermissionsLPw(null, null, updateFlags);
    ver.sdkVersion = mSdkVersion;

    // 多使用者場景下的版本相容處理
    if (!onlyCore && (mPromoteSystemApps || !mRestoredSettings)) {
        for (UserInfo user : sUserManager.getUsers(true)) {
            mSettings.applyDefaultPreferredAppsLPw(this, user.id);
            applyFactoryDefaultBrowserLPw(user.id);
            primeDomainVerificationsLPw(user.id);
        }
    }

    // 如果是升級新版本,則需要清除已有的Code cache目錄
    if (mIsUpgrade && !onlyCore) {
        for (int i = 0; i < mSettings.mPackages.size(); i++) {
            final PackageSetting ps = mSettings.mPackages.valueAt(i);
            if (Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, ps.volumeUuid)) {
                deleteCodeCacheDirsLI(ps.volumeUuid, ps.name);
            }
        }
        ver.fingerprint = Build.FINGERPRINT;
    }
    checkDefaultBrowser();
    mExistingSystemPackages.clear();
    mPromoteSystemApps = false;
    ver.databaseVersion = Settings.CURRENT_DATABASE_VERSION;
    mSettings.writeLPr();

    // 關鍵日誌,PMS已經啟動完畢了
    EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_READY,
            SystemClock.uptimeMillis());

    mRequiredVerifierPackage = getRequiredVerifierLPr();
    mRequiredInstallerPackage = getRequiredInstallerLPr();
    // 初始化包安裝服務
    mInstallerService = new PackageInstallerService(context, this);
    mIntentFilterVerifierComponent = getIntentFilterVerifierComponentNameLPr();
    mIntentFilterVerifier = new IntentVerifierProxy(mContext,
            mIntentFilterVerifierComponent);
    } // synchronized (mPackages)
    } // synchronized (mInstallLock)

    Runtime.getRuntime().gc();
    LocalServices.addService(PackageManagerInternal.class, new PackageManagerInternalImpl());
} // PMS建構函式完結

【程式碼片段4】主要進行一些收尾工作,有幾個關鍵點:

  • 授權,通過呼叫PMS.updatePermissionsLPw()函式,後文會詳細分析
  • 版本相容處理,Android M引入了多使用者,需要更新每個APK關聯到的userid
  • 將PMS的Settings資訊寫入/system/packages.xml檔案中,Settings是PMS中所有包資訊的彙總的資料結構,PMS對包的管理極其依賴於這個資料結構。
  • 初始化包安裝服務PackageInstallerService。

至此,PMS物件的構建過程已經分析完畢,整個邏輯還是較為清晰的,但其實這是一個非常耗時的過程,開機時間大部分都耗在檔案掃描上。

3.2 檔案掃描

scanDirLI()只是檔案掃描的起點,由此引發出一串的與檔案掃描相關的函式。

Package Scan Sequence Diagram

接下來,筆者會按照呼叫時序,對關鍵函式進行分析。PMS物件構建時,待掃描的目錄有很多,不同目錄的檔案掃描,只是在掃描引數上略有區別,整體邏輯上並無不同。

private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {
    final File[] files = dir.listFiles();
    if (ArrayUtils.isEmpty(files)) {
         return;
    }

    for (File file : files) {
        final boolean isPackage = (isApkFile(file) || file.isDirectory())
            && !PackageInstallerService.isStageName(file.getName());
        if (!isPackage) {
            continue;
        }
        try {
            scanPackageLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,
                    scanFlags, currentTime, null);
        } catch (PackageManagerException e) {
            if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
                    e.error == PackageManager.INSTALL_FAILED_INVALID_APK) {
                // 如果掃描Data分割槽的APK失敗,則刪除Data分割槽掃描失敗的檔案
                if (file.isDirectory()) {
                    mInstaller.rmPackageDir(file.getAbsolutePath());
                } else {
                    file.delete();
                }
            }
        }
    }
}

對於待掃描目錄/system/priv-app,該函式會針對每一個子目錄呼叫PMS.scanPackageLI()函式,限定了解析引數PackageParser.PARSE_MUST_BE_APK,表示只解析APK檔案。這個函式如果丟擲異常,則說明解析出錯,如果是掃描Data分割槽的APK出錯,則需要刪除掃描失敗的檔案。

下面,就開始掃描APK檔案了:

private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,
        long currentTime, UserHandle user) throws PackageManagerException {
    parseFlags |= mDefParseFlags;
    // 初始化PackageParser物件,用於解析包
    PackageParser pp = new PackageParser();
    pp.setSeparateProcesses(mSeparateProcesses);
    pp.setOnlyCoreApps(mOnlyCore);
    pp.setDisplayMetrics(mMetrics);

    if ((scanFlags & SCAN_TRUSTED_OVERLAY) != 0) {
        parseFlags |= PackageParser.PARSE_TRUSTED_OVERLAY;
    }

    final PackageParser.Package pkg;
    try {
        // 2. 解析包得到一個PackageParser.Package物件
        pkg = pp.parsePackage(scanFile, parseFlags);
    } catch(...)

    PackageSetting ps = null;
    PackageSetting updatedPkg;

    // 3. 判定系統APK是否需要更新
    synchronized (mPackages) {
        String oldName = mSettings.mRenamedPackages.get(pkg.packageName);
        if (pkg.mOriginalPackages != null && pkg.mOriginalPackages.contains(oldName)) {
            ps = mSettings.peekPackageLPr(oldName);
        }
        if (ps == null) {
            ps = mSettings.peekPackageLPr(pkg.packageName);
        }
        updatedPkg = mSettings.getDisabledSystemPkgLPr(ps != null ? ps.name : pkg.packageName);
    }

    boolean updatedPkgBetter = false;
    if (updatedPkg != null && (parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0) {
        ...
        if (ps != null && !ps.codePath.equals(scanFile)) {
            if (pkg.mVersionCode <= ps.versionCode) {
                ...
                throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,
                        "Package " + ps.name + " at " + scanFile
                        + " ignored: updated version " + ps.versionCode
                        + " better than this " + pkg.mVersionCode);
            } else {
                ...
                InstallArgs args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps),
                        ps.codePathString, ps.resourcePathString, getAppDexInstructionSets(ps));
                synchronized (mInstallLock) {
                    args.cleanUpResourcesLI();
                }
                synchronized (mPackages) {
                    mSettings.enableSystemPackageLPw(ps.name);
                }
                updatedPkgBetter = true;
            }
        }
    }
    ...
    // 4. 獲取APK的簽名資訊
    collectCertificatesLI(pp, ps, pkg, scanFile, parseFlags);

    // 5. 在Data分割槽存在一個應用,與當前掃描的系統APK包名相同
    boolean shouldHideSystemApp = false;
    if (updatedPkg == null && ps != null
        && (parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0 && !isSystemApp(ps)) {
        if (compareSignatures(ps.signatures.mSignatures, pkg.mSignatures)
                != PackageManager.SIGNATURE_MATCH) {
            // 簽名不匹配
            deletePackageLI(pkg.packageName, null, true, null, null, 0, null, false);
            ps = null;
        } else {
           if (pkg.mVersionCode <= ps.versionCode) {
               shouldHideSystemApp = true;
           } else {
               InstallArgs args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps),
                       ps.codePathString, ps.resourcePathString, getAppDexInstructionSets(ps));
               synchronized (mInstallLock) {
                   args.cleanUpResourcesLI();
               }
           }
        }
    }
    ...
    // 6. 呼叫另外一個scanPackageLI()函式,對包進行掃描
    PackageParser.Package scannedPkg = scanPackageLI(pkg, parseFlags, scanFlags
            | SCAN_UPDATE_SIGNATURE, currentTime, user);

    if (shouldHideSystemApp) {
        synchronized (mPackages) {
            mSettings.disableSystemPackageLPw(pkg.packageName);
        }
    }
    return scannedPkg;
}
  1. 初始化包解析器PackageParser,在前文中,我們已經見識過它了,現在我們知道,包解析器是在PMS掃描檔案時構建的;

  2. 有了包解析器,對靜態的APK檔案進行解析,最終就是為了得一個包在記憶體中的資料結構Package,我們已經前文中提前見到了這個結構的全貌,一個包的所有資訊都在其中;

  3. 系統應用升級後會安裝在Data分割槽,之前在System分割槽的應用會被標記為Disable狀態。這些狀態資訊記錄在PMS的Settings中,需要通過包名獲取。包解析完成以後,就能得到一個APK的包名了。這一部分程式碼中有兩個變數:pkg是Package型別,表示當前掃描的APK;ps是PackageSettings型別,表示PMS的Settings中儲存的APK資訊,即上一次安裝的APK資訊。通過比對這兩個變數,就能知道當前掃描的APK與已經安裝的歷史APK的差異,如果當前掃描的系統APK版本比已經安裝的系統APK版本要高,這就需要重新將系統APK設定為Enable狀態;否則,中斷掃描過程,丟擲異常;

  4. 之前構建Package物件時,還沒有APK的簽名資訊,現在正是把APK簽名資訊填進去的時候,因為到這一步已經確定要安裝APK了,APK能安裝的前提就是一定要有簽名資訊;如果是對已有APK進行升級,那簽名必須與已有APK匹配。PMS.collectCertificatesLI()函式就是從APK包中META-INF目錄讀取簽名資訊;

  5. 處理系統APK已經被安裝過的場景,已經被安裝過的APK位於Data分割槽。shouldHideSystemApp表示是否需要將系統APK設定為Disable狀態,預設情況下為false;如果安裝過的APK的版本比當前掃描的系統APK的版本要高,則意味著要使用Data分割槽的APK,隱藏系統APK,shouldHideSystemApp被置為true;

  6. 到這一步,已經通過包解析器完成了對APK檔案的解析,並且做了一些安裝場景的判斷。接下來,需要對解析出來的Package進行處理,這交由另外一個scanPackageLI()函式完成。

private PackageParser.Package scanPackageLI(PackageParser.Package pkg, int parseFlags,
        int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
    boolean success = false;
    try {
        final PackageParser.Package res = scanPackageDirtyLI(pkg, parseFlags, scanFlags,
            currentTime, user);
        success = true;
        return res;
    } finally {
        if (!success && (scanFlags & SCAN_DELETE_DATA_ON_FAILURES) != 0) {
            removeDataDirsLI(pkg.volumeUuid, pkg.packageName);
        }
    }
}

以上函式就是做了一層封裝呼叫,如果掃描失敗,且SCAN_DELETE_DATA_ON_FAILURES標誌位被置上,則需要刪除Data分割槽的APK檔案。真正幹活的是scanPackageDirtyLI()函式,這個函式邏輯非常龐大,隨著Andorid版本的升級,函式體也越來越龐大,筆者僅僅挑出幾個主幹邏輯進行分析,省略大量分支邏輯:

private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, int parseFlags,
int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
    ...
    // 1. 針對包名為“android”的APK進行處理
    if (pkg.packageName.equals("android")) {
        ...
        mPlatformPackage = pkg;
        pkg.mVersionCode = mSdkVersion;
        mAndroidApplication = pkg.applicationInfo;
        ...
    }
    ...
    // 2. 鎖上mPacakges物件,意味著要對這個資料結構進行寫操作,裡面儲存的就是已經解析出來的包資訊
    synchronized (mPackages) {
        // 如果有定義ShareUserId,則建立一個ShareUserSetting物件
        if (pkg.mSharedUserId != null) {
            suid = mSettings.getSharedUserLPw(pkg.mSharedUserId, 0, 0, true);
            if (suid == null) {
                throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
                "Creating application package " + pkg.packageName
                + " for shared user failed");
            }
        }
        ...
        // 生成PackageSetting物件,對應的資料結構將序列化在/data/system/packags.xml檔案中
        pkgSetting = mSettings.getPackageLPw(pkg, origPackage, realName, suid, destCodeFile,
                destResourceFile, pkg.applicationInfo.nativeLibraryRootDir,
                pkg.applicationInfo.primaryCpuAbi,
                pkg.applicationInfo.secondaryCpuAbi,
                pkg.applicationInfo.flags, pkg.applicationInfo.privateFlags,
                user, false);
         ...
         // 打上SELinux標籤
         if (mFoundPolicyFile) {
             SELinuxMMAC.assignSeinfoValue(pkg);
         }
         ...
         // 簽名驗證
         if (shouldCheckUpgradeKeySetLP(pkgSetting, scanFlags)) {
             if (checkUpgradeKeySetLP(pkgSetting, pkg)) {
                 pkgSetting.signatures.mSignatures = pkg.mSignatures;
             }
             ...
         } else {
             verifySignaturesLP(pkgSetting, pkg);
             ...
         }
    }

    pkg.applicationInfo.processName = fixProcessName(
            pkg.applicationInfo.packageName,
            pkg.applicationInfo.processName,
            pkg.applicationInfo.uid);
    // 3. 生成APK的data目錄
    File dataPath;
    if (mPlatformPackage == pkg) {
        // framework-res.apk的data目錄是 /data/system
        dataPath = new File(Environment.getDataDirectory(), "system");
        pkg.applicationInfo.dataDir = dataPath.getPath();
    } else {
        // 其他APK的data目錄是 /data/data/packageName
        dataPath = Environment.getDataUserPackageDirectory(pkg.volumeUuid,
                UserHandle.USER_OWNER, pkg.packageName);
        boolean uidError = false;
        if (dataPath.exists()) {
            // APK的data目錄已經存在
            ...
        } else {
            // APK的data目錄不存在,則通過Installd建立之
            int ret = createDataDirsLI(pkg.volumeUuid, pkgName, pkg.applicationInfo.uid,
                    pkg.applicationInfo.seinfo);
            if (ret < 0) {
                throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
                        "Unable to create data dirs [errorCode=" + ret + "]");
            }
            ...
        }
    }
    ...
    // 4. 設定Native Library
    if ((scanFlags & SCAN_NEW_INSTALL) == 0) {
        derivePackageAbi(pkg, scanFile, cpuAbiOverride, true /* extract libs */);
        if (isSystemApp(pkg) && !pkg.isUpdatedSystemApp() &&
                pkg.applicationInfo.primaryCpuAbi == null) {
            setBundledAppAbisAndRoots(pkg, pkgSetting);
            setNativeLibraryPaths(pkg);
        }
    } else {
        ...
        setNativeLibraryPaths(pkg);
    }
    ...
    // 5. 填充PMS中的資料
    synchronized (mPackages) {
        mSettings.insertPackageSettingLPw(pkgSetting, pkg);
        mPackages.put(pkg.applicationInfo.packageName, pkg);
        ...
        int N = pkg.providers.size();
        for (i=0; i<N; i++) {
            PackageParser.Provider p = pkg.providers.get(i);
            p.info.processName = fixProcessName(pkg.applicationInfo.processName,
                    p.info.processName, pkg.applicationInfo.uid);
            mProviders.addProvider(p);
            ...
        }

        N = pkg.services.size();
        for (i=0; i<N; i++) {
            PackageParser.Service s = pkg.services.get(i);
            s.info.processName = fixProcessName(pkg.applicationInfo.processName,
                    s.info.processName, pkg.applicationInfo.uid);
            mServices.addService(s);
            ...
        }

        N = pkg.receivers.size();
        for (i=0; i<N; i++) {
            PackageParser.Activity a = pkg.receivers.get(i);
            a.info.processName = fixProcessName(pkg.applicationInfo.processName,
                    a.info.processName, pkg.applicationInfo.uid);
            mReceivers.addActivity(a, "receiver");
            ...
        }

        N = pkg.activities.size();
        for (i=0; i<N; i++) {
            PackageParser.Activity a = pkg.activities.get(i);
            a.info.processName = fixProcessName(pkg.applicationInfo.processName,
                    a.info.processName, pkg.applicationInfo.uid);
            mActivities.addActivity(a, "activity");
        }
        ...
    }

    return pkg;
}
  • 1 包名為“android”的APK就是system/framework/framework-res.apk,PMS中有幾個專門的變數用於儲存這個APK的資訊:mPlatfromPackage用於儲存該APK的Package資料結構、mAndroidApplicationInfo用於儲存該APK的ApplicationInfo

  • 2 當Package物件建立以後,就需要將其納入PMS的管轄範圍,PMS有一個mPackages物件,儲存的是包名到Package物件的對映,當一個APK順利通過掃描過程之後,其Package物件便被新增到mPackages這個對映表中。在這一步,鎖上mPackages物件,意味著需要對其相關的內容進行寫操作,主要涉及以下方面:

    • 處理ShareUserId,如果APK有定義”android:ShareUserId”,則會為其建立一個ShareUserSetting物件,並將其納入PMS的mShareUsers中

    • 生成PackageSetting物件,每一個包名都會對應一個PacageSetting物件,這個對映關係儲存在PMS的Settings.mPackages中。PMS的Settings最終會序列化到/data/system/packages.xml檔案中,前文分析的系統應用升級替換的邏輯,需要獲取已經安裝應用的資訊,就是從/data/system/packages.xml檔案中讀取的

    • 打上SELinux標籤,這是在Android引入SELinux以後才有的邏輯,每一個的靜態的APK檔案都會被打上一個SE Label,一個APK該打上什麼型別的SE Label是由其簽名資訊決定的。在前文中,我們提到過mFoundPolicyFile就是mac_permission.xml檔案,在這個檔案中,儲存了簽名到簽名型別的對映。筆者在AOSP原始碼上編譯生成的 out\target\product\generic_arm64\obj\ETC\mac_permissions.xml_intermediates\mac_permissions.xml檔案內容如下:

      <policy>
        <signer signature="308204a83082039...">
          <seinfo value="platform"/>
        </signer>
        <default>
         <seinfo value="default"/>
        </default>
      </policy>
      

      其中,signature就是簽名,不同簽名的值不一樣,本例中,該簽名的型別就是platform,其他簽名的型別都是default。SELinxu在打標籤的時候,就是根據seinfo來打上不同的標籤。

  • 3 生成APK的data目錄,framework-res.apk比較特殊,它的data目錄位於/data/system/,其他APK的data目錄都位於/data/data/packageName下,packageName就是APK的包名,譬如com.yourname.test。

  • 4 設定APK依賴的Native庫,即so檔案的目錄。這一塊邏輯比較複雜,筆者不予展開,在分析安裝APK的過程時,再回過來理解如果處理APK所依賴的so檔案。

  • 5 該函式的最後一部分,就是填充PMS中的一些資料結構。每一個包的PackageSetting會填充到PMS.mPackages中,activity、provider、service、receiver四大元件的資訊會填充PMS對應的陣列中,當然還有permission、instrument等會填充到PMS中。

至此,由scanDir()函式引發的一系列包掃描的過程就完成了,通過對包的解析得到一個包在記憶體中的資料結構Package,然後再對這個資料結構進行加工處理,將所有包的資訊彙集到PMS這個包管理中心。

本文中列舉的包檔案掃描,僅僅是一個大體的流程,包檔案掃描的細節遠不止這些,諸如包掃描的引數、特殊的掃描場景都是本文中沒有涉及到的。讀者在理解PMS的功能定位時,也可以適當地跳出細節,籠統來看,包掃描就是包管理者對管理物件設定的一套認證系統,只有管理物件通過認證,才能被納入管理範圍。同時,包管理者也是一個資訊中心,它維護所有包的資訊。

3.3 應用授權

包解析過後,PMS的Settings這個資料結構中聚合了所有包的資訊以及所有許可權資訊,此時,需要對所有應用授權。整個授權機制的原理如下圖所示:

Package Permission Grant Mechanism

APK在清單檔案中宣告需要使用的許可權,PMS設計了一套授權機制,用於判定是否授權以及授權型別。

Android中,有許可權持有者許可權申請者兩個角色,一個Android包可以扮演其中一種角色,或兩種角色兼備。持有者通過設計許可權來保護介面和資料,申請者如果要訪問受保護的介面和資料時,需要事先宣告,然後交由包管理者來判斷是否要授權。

對於許可權持有者而言,可以通過protectionLevel屬性定義了許可權的受保護級別,其取值主要有以下四種:

  • normal(0): 最普通的一類許可權,只要申請使用這一類許可權就授予。

  • dangerous(1): 較為危險的一類許可權,譬如訪問聯絡人、獲取位置服務等許可權,需要經過使用者允許才授予。

  • signature(2): 如果申請者與該許可權的持有者簽名相同,則授予這類許可權。

  • signatureOrPrivileged(18): 對singature的一個補充,許可權申請者與持有者簽名相同,或者申請者是位於/system/priv-app目錄下的應用,則授予這類許可權。在早期的Android版本,所有系統應用都位於/system/app目錄下,其定義為signatureOrSystem(3),但該定義已經過時了;當Android引入了/system/priv-app目錄以後,就將這一類保護級別重新定義為signatureOrPrivileged(18)

除了以上最主要的四類保護級別,android.content.pm.PermissionInfo中還定義了其他保護級別,讀者可以自行參考。筆者擷取了framework/base/core/res/AndroidManifest.xml檔案中的部分許可權定義:

    <!-- 讀取聯絡人,許可權保護級別為dangerous -->
    <permission android:name="android.permission.READ_CONTACTS"
        android:permissionGroup="android.permission-group.CONTACTS"
        android:label="@string/permlab_readContacts"
        android:description="@string/permdesc_readContacts"
        android:protectionLevel="dangerous" />
    <!-- 網路訪問, 許可權保護級別為normal -->
    <permission android:name="android.permission.INTERNET"
        android:description="@string/permdesc_createNetworkSockets"
        android:label="@string/permlab_createNetworkSockets"
        android:protectionLevel="normal" />
    <!-- USB管理,許可權保護界別為signatureOrPrivileged -->
    <permission android:name="android.permission.MANAGE_USB"
        android:protectionLevel="signature|privileged" />
    <!-- 賬戶管理,許可權保護級別為signature -->
    <permission android:name="android.permission.ACCOUNT_MANAGER"
        android:protectionLevel="signature" />

以上這些許可權的持有者其實就是framework-res.apk,這個APK的簽名是platform,執行在系統程式(system_process)之中,可以理解為系統許可權,幾乎所有應用都要申請其中若干項許可權。當然,一個應用可以自定義許可權,設計其許可權保護級別,供其他申請者所用。

有了上面的許可權級別限制,就可以理解,部分許可權需要申請者滿足一定的條件才能被授予,從Android M(6.0)開始,對最終授予的許可權進行了分類:

  • install:安裝時授予的許可權。normalsignaturesignatureOrPrivilege的授予都屬於這一類。

  • runtime:執行時由使用者決定是否授予的許可權。在Android M(6.0)以前,dangerous的許可權屬於install型別,但從Android M(6.0)以後,dangerous的許可權改為屬於runtime一類了。在使用這類許可權時,會彈出一個對話方塊,讓使用者選擇是否授權。

注意,授權型別是Android M(6.0)才引進的,之前只有是否授權的區分。之所以做授權型別的區分,是為了適應多使用者使用的場景,install型別的授權,對所有使用者都是一樣的;runtime型別的授權,不同使用者使用的選擇不一樣,譬如一個使用者在使用的會授予某個應用訪問聯絡人資料的許可權,但另一個使用者使用時會選擇拒絕授權。

在深入分析應用授權的邏輯之前,我們先介紹一下授權機制涉及到的資料結構:

Package Permission Grant Class Diagram
  • BasePermission:許可權持有者所定義的每一項許可權都會生成一個BasePermission,所有BasePermission都聚合在PMS的Settings中。BasePermission有三種型別: NORMAL、BUILTIN、DYNAMIC。

  • PackageSetting: 每一個解析成功的包都會生成一個PackageSetting,所有的PackageSetting都聚合在PMS的Settings中。PackageSetting繼承了其祖宗SettingBase的屬性mPermissionsState,這個屬性表示一個包的授權狀態。

  • PermissionsState(注意,這是複數):聚合了包中所有許可權的授予狀態,在多使用者的場景下,不同使用者的授權情況不同,因此要區分出一個許可權針對每個使用者的授予情況,為此設計了一個資料封裝類PermissionData(注意,這是單數),記錄了一個BasePermission與多個使用者mUserState之間的關係。每一個使用者的許可權授予狀態用PermissionState(注意,這是單數)來記錄。

理解應用授權機制的資料結構設計,可以結合日常生活中的門禁系統。一個大廈裡面有很多房間,進入不同的房間需要獲得通過門禁的許可權,每一個房間的門禁就可以理解為BasePermission,所有的門禁許可權都記錄在大廈管理者的資料中心,這個資料中心就可以理解為PMS的Settings。每一個使用者來到大廈,都會被登記,登記的記錄就是PackageSetting。

使用者會申請要進入哪些房間,待管理中心分配,最後,使用者會拿到的門禁卡,表示可以進入哪些房間。不同使用者需要進入和有權進入的房間通常是不同的,譬如涉及大廈安全的監控房間,就不讓訪客進入,這就決定了對不同使用者而言,門禁卡開通的許可權不一樣。門禁卡所對應的就是PermissionsState。

假設大廈臨時換了一套管理規定,之前某個使用者有權進入某個房間,在臨時的管理規定下變成了無權進入。同一個房間,在不同管理規定下,對一個使用者開放的許可權是不同的,這個資訊被記錄下來,就是PermissionData。這樣一來,同一張門禁卡,面對同一張門,在不同的管理規定下,開啟門的情況是不一樣的。

不同的管理規定,其實就是Android中的多使用者使用情況的體現,不同使用者的使用規則是不同的,對於同一個應用而言,不同使用者使用下的授權情況會有差異。

有了以上應用授權的一些基礎知識,我們再來分析程式碼會稍微輕鬆一點。

在包掃描完成以後,PMS會呼叫updatePermissionsLPw()函式,來更新所有APK的授權狀態。

private void updatePermissionsLPw(String changingPkg,
        PackageParser.Package pkgInfo, int flags) {
    ... // 此處省略大段邏輯。需要刪除沒有歸屬的BasePermission,
    // 如果一個BasePermission找不到其關聯的包,則需要刪除之

    // 遍歷包掃描的結果,依次對每個包進行授權。
    // PMS初始化呼叫該函式時,傳入的pkgInfo為null。
    if ((flags&UPDATE_PERMISSIONS_ALL) != 0) {
        for (PackageParser.Package pkg : mPackages.values()) {
            if (pkg != pkgInfo) {
                grantPermissionsLPw(pkg, (flags&UPDATE_PERMISSIONS_REPLACE_ALL) != 0,
                        changingPkg);
            }
        }
    }

    if (pkgInfo != null) {
        grantPermissionsLPw(pkgInfo, (flags&UPDATE_PERMISSIONS_REPLACE_PKG) != 0, changingPkg);
    }
}

該函式的呼叫場景比較多,譬如安裝、解除安裝、更新系統應用時,都會呼叫到。此處分析流程是處於PMS構造時呼叫的,在開機掃描完所有的包後,便會掉用該函式,一次性來更新所有包的許可權。

其實,該函式僅僅是完成授權的準備工作,需要保證所有的掃描出來的許可權都有歸屬,才能開始授權。真正的授權函式是grantPermissionsLPw(),帶上LP字尾表示需要獲取mPackages這個鎖,多了一個w表示需要對mPackages進行寫操作。

private void grantPermissionsLPw(PackageParser.Package pkg, boolean replace,
        String packageOfInterest) {
    // 1. 初始化授權相關的資料
    final PackageSetting ps = (PackageSetting) pkg.mExtras;
    if (ps == null) {
        return;
    }
    // 申請者的授權狀態PermissionsState,在後文的邏輯中,會以origPermissions這個變數來表示
    PermissionsState permissionsState = ps.getPermissionsState();
    PermissionsState origPermissions = permissionsState;
    ...
    if (replace) {
        // 如果replace為true,表示包許可權需要更新
        // PackageSettings.installPermissionsFixed這個布林變數表示
        // 包的**install**型別的許可權已經確定下來,在安裝成功後,該變數會被置為true
        // 此處,需要重新更新許可權,故又將其置為false
        ps.installPermissionsFixed = false;
        if (!ps.isSharedUser()) {
            origPermissions = new PermissionsState(permissionsState);
            permissionsState.reset();
        }
    }
    ...
    // 2. 遍歷所有申請的許可權,依次判斷是否授權
    final int N = pkg.requestedPermissions.size();
    for (int i=0; i<N; i++) {
        // name表示待申請的許可權名
        final String name = pkg.requestedPermissions.get(i);
        // bp表示待申請許可權的資料結構,bp根據name從已有的許可權列表中獲取的
        final BasePermission bp = mSettings.mPermissions.get(name);
        if (bp == null || bp.packageSetting == null) {
            // 如果沒有匹配到bp,則說明當前系統中還不存在name指定的許可權
            if (packageOfInterest == null || packageOfInterest.equals(pkg.packageName)) {
                Slog.w(TAG, "Unknown permission " + name
                        + " in package " + pkg.packageName);
            }
            continue;
        }

        final String perm = bp.name;
        boolean allowedSig = false;
        int grant = GRANT_DENIED;
        ...
        // 根據所申請許可權的保護級別,確定授權型別
        final int level = bp.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
        switch (level) {
            case PermissionInfo.PROTECTION_NORMAL: {
                grant = GRANT_INSTALL;
            } break;

            case PermissionInfo.PROTECTION_DANGEROUS: {
                if (pkg.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1) {
                    grant = GRANT_INSTALL_LEGACY;
                } else if (origPermissions.hasInstallPermission(bp.name)) {
                    grant = GRANT_UPGRADE;
                } else if (mPromoteSystemApps
                        && isSystemApp(ps)
                        && mExistingSystemPackages.contains(ps.name)) {
                    grant = GRANT_UPGRADE;
                } else {
                    grant = GRANT_RUNTIME;
                }
            } break;

            case PermissionInfo.PROTECTION_SIGNATURE: {
                allowedSig = grantSignaturePermission(perm, pkg, bp, origPermissions);
                if (allowedSig) {
                    grant = GRANT_INSTALL;
                }
            } break;
        }

        // 3. 根據授權型別,更新包的PermissionsState
        if (grant != GRANT_DENIED) {
            // 小插曲,對於Data分割槽的應用而言,原則上是不會授予新的install型別的許可權
            if (!isSystemApp(ps) && ps.installPermissionsFixed) {
                if (!allowedSig && !origPermissions.hasInstallPermission(perm)) {
                    if (!isNewPlatformPermissionForPackage(perm, pkg)) {
                        grant = GRANT_DENIED;
                    }
                }
            }

            switch (grant) {
                case GRANT_INSTALL: {
                    // 收回所有已授予的runtime許可權。因為runtime可能在新版本中變成install型別
                    // runtime是面向多使用者的,所以涉及到runtime授權時,都會有一個迴圈遍歷所有的使用者
                    for (int userId : UserManagerService.getInstance().getUserIds()) {
                        if (origPermissions.getRuntimePermissionState(
                                bp.name, userId) != null) {
                            origPermissions.revokeRuntimePermission(bp, userId);
                            origPermissions.updatePermissionFlags(bp, userId,
                                  PackageManager.MASK_PERMISSION_FLAGS, 0);
                            changedRuntimePermissionUserIds = ArrayUtils.appendInt(
                                    changedRuntimePermissionUserIds, userId);
                        }
                    }
                    // 授予install型別的許可權
                    if (permissionsState.grantInstallPermission(bp) !=
                            PermissionsState.PERMISSION_OPERATION_FAILURE) {
                        changedInstallPermission = true;
                    }
                } break;

                case GRANT_INSTALL_LEGACY: {
                    if (permissionsState.grantInstallPermission(bp) !=
                            PermissionsState.PERMISSION_OPERATION_FAILURE) {
                        changedInstallPermission = true;
                    }
                } break;

                case GRANT_RUNTIME: {
                    for (int userId : UserManagerService.getInstance().getUserIds()) {
                        PermissionState permissionState = origPermissions
                                .getRuntimePermissionState(bp.name, userId);
                        final int flags = permissionState != null
                            ? permissionState.getFlags() : 0;
                        if (origPermissions.hasRuntimePermission(bp.name, userId)) {
                            if (permissionsState.grantRuntimePermission(bp, userId) ==
                                    PermissionsState.PERMISSION_OPERATION_FAILURE) {
                                changedRuntimePermissionUserIds = ArrayUtils.appendInt(
                                    changedRuntimePermissionUserIds, userId);
                            }
                        }
                        permissionsState.updatePermissionFlags(bp, userId, flags, flags);
                }
               } break;

               case GRANT_UPGRADE: {
                   // 先回收之前授予的install許可權,再重新授予runtime許可權
                   ...
               }

               default: {
                   // 如果進入這個分支,表示並沒有拒絕授權,但許可權又沒有真正授予給申請者
                   // 因為當時申請的許可權還不存在,即便後來有了待申請的許可權,也不會授予給申請者
                   if (packageOfInterest == null
                           || packageOfInterest.equals(pkg.packageName)) {
                       Slog.w(TAG, "Not granting permission " + perm
                               + " to package " + pkg.packageName
                               + " because it was previously installed without");
                   }
               } break;
           }
       else {
           // 拒絕授權,則需要回收已經授予的許可權
           ...
       }
    }
    if ((changedInstallPermission || replace) && !ps.installPermissionsFixed &&
            !isSystemApp(ps) || isUpdatedSystemApp(ps)){
        ps.installPermissionsFixed = true;
    }
    for (int userId : changedRuntimePermissionUserIds) {
        mSettings.writeRuntimePermissionsForUserLPr(userId, false);
    }
}

先從整體來思考一下這個函式,該函式完成對一個包授權,接收三個輸入引數:

函式引數說明
pkg包解析器解析出來的物件PackageParser.Package。這就是待授權的包。
replace是否需要替換已有包的許可權。初始化PMS時,如果不是OTA升級系統版本,則該引數為false。
packageOfInterest感興趣的包,主要是為了列印日誌。不影響函式的主體邏輯。

在呼叫這個函式之前,所有包的資訊都寫入了PMS的Settings中,每一個包都有一個PermissionsState物件,用於記錄授權狀態。在呼叫這個函式之後,包的PermissionsState物件的資料會更新。該函式其實就是Android的包授權規則的實現函式,要更新的資料就是PermissionsState物件。授權規則體現在函式的邏輯中:

  1. 初始化授權相關的資料:

    • origPermissions:表示一個包已有的授權狀態,包掃描完後,這個資料結構就已經構建完畢了。
    • changedRuntimePermissionUserIds:表示已經授予的runtime型別的許可權發生了變化的那些使用者。
    • changedInstallPermission:表示已經授予的install型別的許可權發生的變化,預設為false。
    • PackageSettings.installPermissionsFixed: 表示包的install型別的權已經確定。如果傳入的函式引數replace為true,則意味著需要對install型別的授權進行調整,故會將此變數重新置為false。
  2. 遍歷所有申請的許可權,根據所申請許可權的保護級別(protectionLevel),會得到幾種結果:

    • GRANT_INSTALL:對於Normal級別的許可權而言,都是在安裝時授予給申請者的。對於Signature級別的許可權,如果簽名匹配或者滿足Previlege系統應用的要求,則也是在安裝時授予給申請者的。

    • GRANT_INSTALL_LEGACY:帶上LEGACY表示這屬於遺留的安裝許可權,在Android M(6.0)之前,Dangerous級別的許可權都是授予install型別,到Android M(6.0)之後,Dangerous級別的許可權都是以runtime型別來授予。這是Android為了保證向下相容的設計:對於一個應用程式而言,如果是基於Android M(6.0)之前的SDK進行開發,而且申請了Dangerous級別的許可權,這一類許可權就是以GRANT_INSTALL_LEGACY型別來授予。

    • GRANT_UPGRADE: 與GRANT_INSTALL_LEGACY一樣,也是為了向下相容。如果應用程式沒有指明一定要執行在Android M(6.0)以前的版本,那申請的Dangerous級別的許可權,將會以GRANT_UPGRADE來授權,表示對於Dangerous許可權,之前授權為install型別的,已經記錄在PMS的Settings中,現在整個系統通過OTA升級到Android M(6.0)了,需要將install型別的授權升級為runtime型別。

    • GRNAT_RUNTIME:如果執行在Android M(6.0)上的應用程式申請了Dangerous許可權,則以runtime型別來授權。

    • GRANT_DENIED:拒絕授權。有兩大類情況會拒絕授權:其中一類是申請保護級別為SignatureOrPrivelege的許可權,但不滿足簽名匹配或為系統應用的約束條件,則拒絕授權。另一類,在下文中見。

  3. 根據授權型別來更新授權狀態PermissionsState。如果需要授予許可權,則會根據授權型別區別對待,最開始初始化的兩個變數changedInstallPermission和changedRuntimePermissionUserIds在這一個步驟中可能會被更新。

    如果拒絕授權,則需要收回。這裡就出現了另一類拒絕授權的場景:對於一個Data分割槽的普通應用而言,之前申請的許可權不存在,但現在申請的許可權又有了,則仍然拒絕授權給這個普通應用,除非這個普通應用申請的許可權時平臺新增的許可權。這一點,其實不難理解,譬如一個普通應用A,申請了普通應用B定義的許可權,A先於B安裝,那麼,在A安裝的時候,所申請的許可權還不存在。B安裝以後,即便許可權有了,也不會再重新授予給A。如果普通應用A申請的是一個Android平臺自身新增的許可權,則會授予。

如果讀者還沒有接觸到Android M(6.0)之後的程式碼,那應用授權機制還不至於這麼複雜,因為Android M(6.0)對擴充套件了授權型別,並做了大量向下相容的設計。所以,這段程式碼讀起來總是不那麼利索。其實,越往Android高版本學習,就越能看到這些向下相容的程式碼,設計上總是不那麼優美,但又無處不體驗向下相容的精神。

沒有一個系統一開始就兼顧到了所有後續發展策略,絕對的優美設計或許只是停留在一個時間段。或許更加好的,只是那些活下來的、不斷髮展的程式碼。

3.4 小結

PMS是伴隨系統程式啟動而啟動的,最終會構造一個PMS物件,此後,PMS便作為Android世界中的包管理者,對外提供包的增/刪/改/查操作。

在PMS的啟動過程中,最重要的是對所有的靜態APK檔案進行掃描,生成一個包在記憶體中的資料結構Package,PMS實際上就是維護著所有包在記憶體中的資料結構。已有包的歷史資訊會寫入磁碟,PMS的Settings專門來管理寫入磁碟的包資訊。

所有包的資訊掃描完以後,需要對應用進行授權,這是Android許可權管理的一部分。隨著Android版本的升級,授權機制略有區別,總體的框架是:每個APK都可以宣告許可權,併為許可權設定保護級別,其他APk需要使用這些許可權的時候,需要先申請,再由系統判定是否進行授權。

4 包查詢服務

在管理所有包的同時,包管理者需要對外提供服務,諸如獲取包的資訊、安裝或刪除包、解析Intent等,都是包管理者在Android世界的職能。

本節先介紹包管理者的服務方式,再分析一個最常見的包查詢例項。

4.1 服務方式

包管理者以什麼形式對外提供服務呢?在寫應用程式時,我們通常會利用應用自身的上下文環境Context來獲取包管理服務:

// 獲取一個PackageManager的物件例項
PackageManager pm = context.getPackageManager();
// 通過PackageManager物件獲取指定包名的包資訊
PackageInfo pi = pm.getPackageInfo("com.android.contacts", 0);

這麼一段簡單的程式碼,其實蘊含著很多深意:

  1. 前文介紹的PMS和其管理的各種資料結構,都是執行在系統程式之中。在應用程式中獲取的PackageManager物件,只是PMS在應用程式中的一個代理,不同的應用程式都不同的代理,意味著不同應用程式中的PackageManager物件是不同的,但管理者PMS只有一個。

  2. 執行在應用程式中的PackageManager要與執行在系統程式中的PMS進行通訊,通訊的手段是什麼嗎?自然是Android中最常見的Binder機制。因此,會有一個IPackageManager.aidl檔案,用於描述兩者通訊的介面。 另外,應用程式中的PackageInfo物件,在上文分析“包資訊體”的時候,我們已經見過它,還記得嗎?包解析後,需要跨程式傳遞的資料結構都實現了Parcelable介面,PackageInfo其實就是由系統程式傳遞到應用程式的物件。

不同應用程式通過Binder機制與PMS通訊的過程如下圖:

PMS Binder Transaction

PMS作為包管理的最核心組成部分,伴隨著系統的啟動而建立,並一直執行系統程式中。當應用程式需要獲取包管理服務時,會生成一個PackageManager物件與PMS進行通訊。在包解析時就會生成包資訊,即XXXInfo這一類資料結構,PMS會將這些資料傳遞給需要的應用程式。

管理者對內設計了複雜的管理機制,對外封裝了簡單的使用介面。這種設計在Android中大量出現,ActivityManagerService、WindowManagerService、PowerManagerService等,基本所有的系統服務都遵循這種設計規範。對於應用程式而言,不需要關心管理者的實現原理,只需要理解介面的使用場景。

應用程式通過Binder介面獲取包管理服務,這僅僅是包管理者提供服務的概貌,接下來可以更深入地思考一個問題:通過Context就能獲取到PackageManager,那麼PackageManager物件是如何生成到應用程式的執行環境中去的呢?我們通過原始碼來回答。

Context.getPackageManager()函式的實現如下:

// frameworks/base/core/java/android/app/ContextImpl.java
public PackageManager getPackageManager() {
    if (mPackageManager != null) {
        return mPackageManager;
    }

    // 從ActivityThread獲取了PackageManager物件
    IPackageManager pm = ActivityThread.getPackageManager();
    if (pm != null) {
        // 通過ApplicationPackageManager對進行了一層封裝
        return (mPackageManager = new ApplicationPackageManager(this, pm));
    }

    return null;
}

ActivityThread.getPackageManager()函式實現如下:

// frameworks/base/core/java/android/app/ActivityThread.java
public static IPackageManager getPackageManager() {
    if (sPackageManager != null) {
        return sPackageManager;
    }
    // 早在系統程式(SystemServer)啟動PMS時,就將PMS新增到了系統服務中
    // 因此,通過ServiceManager便可以獲取到包服務。
    IBinder b = ServiceManager.getService("package");
    sPackageManager = IPackageManager.Stub.asInterface(b);
    return sPackageManager;
}

通過Context獲取PackageManager最終得到的物件是ApplicationPackageManager,它是PackageManager的子類,其實就是對PackageManager的一個封裝,目的是為了方便應用程式程式設計。

現在,可以完整的梳理一下包管理的服務方式了:

  1. 全域性定義了IPackageManager介面,描述了包管理者對外提供的功能;執行在系統程式中的PMS實現了IPackageManager介面,作為包管理的服務端;客戶端通過IPackageManager介面請求包服務;

  2. 為了方便客戶端使用包服務,Android做了多層封裝。應用程式作為客戶端,通過PackageManager便可使用包服務,客戶端實際存在的物件是ApplicationPackageManager,它封裝了IPackageManager的所有介面;

  3. 在應用程式看來,客戶端和服務端的概念是模糊的,明確的只有執行環境的概念,即Context。包服務就存在於應用程式的執行環境中,需要時直接拿出來使用即可

“執行環境(Context)”是Android的設計哲學之一,Android有意弱化程式,強化執行環境,這是面向應用開發者的設計。執行環境是什麼並不是一個好回答的問題,可以將其類比為我們的工作環境,當我們需要辦公裝置時,只需要向管理部門申請,並不需要關心辦公裝置如何採購,辦公裝置對一般的工作人員而言,就像是工作環境中天然存在的東西。

4.2 Intent的解析

Android中,使用Intent來表達意圖,最終會有一個響應者。當系統產生一個Intent後,如何找到它的響應者呢?這需要對Intent進行解析。作為所有包資訊管理者的中樞,PMS自然有義務承擔解析Intent的責任。要解析Intent,就需要先了解Intent的結構,標識一個Intent身份的資訊由兩部分構成:

  • 主要資訊:Action和Data。Action用於表明Intent所要執行的操作,譬如ACTION_VIEW,ACTION_EDIT; Data用於表明執行操作的資料,譬如聯絡人資料,資料是以URI來表達的。再舉兩個Action和Data成對出現的例子:

    ACTION_VIEW: content://contacts/people/1 # 表示檢視聯絡人資料庫中,ID為1的聯絡人資訊

    ACTION_DIAL: tel:119 # 表示撥打電話給119

    以上例子中的URI並不一樣,完整的URI格式為scheme://host:port/path

  • 次要資訊:除了主要的標記資訊,Intent還可以附加很多額外的資訊,Category,Type,Component和Extra:

    • Category表示Intent的類別,譬如CATEGORY_LAUNCHER表示要對屬於桌面圖示這一類的物件執行操作;
    • Type表示Intent所操作的資料型別,就是MIMEType,譬如要操作PNG圖片,那Type就可以設定為png
    • Component表示Intent要操作的物件;
    • Extra表示Intent所傳遞的資料,這些資料都實現了Parcelable介面。

Intent的身份資訊,其實就是Android的一種設計語言,譬如“打電話給119”,只需要發出Action為ACTION_DIAL,URI為tel:119的Intent即可,剩下的就交由Android系統去理解這個意圖。任何元件只要按照規則發聲,都會被Android系統正確的理解。

根據解析Intent方式的不同,可以將Intent分為兩類:

  • 顯式(Explicit): 明確指名需要誰來響應Intent。這一類Intent的解析過程比較簡單。

  • 隱式(Implicit):由系統找到合適的目標來響應Intent。這一類Intent的解析過程比較複雜,由於目標不明確,所以需要經過層層篩選才能找到最合適的響應者。

之所以Intent的資訊有主次之分,是因為解析Intent的規則需要有一個依據,主要資訊是最能表達意圖的,而次要資訊則是解析規則的一個補充。這就像大家在做自我介紹的時候,總是先說姓名、籍貫這些主要的資訊,再額外補充愛好、特長這些次要資訊,這樣一來在和其他人交朋友的時候,其他人就可以先根據姓名、籍貫直接鎖定的我。如果我們只介紹愛好、特長,那別人鎖定的範圍就比較廣,因為有相同愛好或特長的人比較多。

之所以Intent有顯隱之分,是因為解析Intent的方式不同,如果我指定要和某某人交朋友,那發出的這一類請求,就是顯式的Intent;如果沒有指定交朋友的物件,只是說找到跟我愛好相同的人,那發出的這一類請求,就是隱式的Intent。對待這兩種Intent顯然有不同的解析方式。

如同“執行環境(Context)”一樣,Intent也是面向應用程式的設計,同樣是弱化了程式的概念。應用程式只需表明“我想要什麼”,不需要關心所要的東西在什麼地方,如何找到所要的東西。Intent是Android通訊的手段之一,可以承載要傳遞的資訊,至於資訊怎麼從發起程式傳遞到目標程式,應用程式可以毫不關心。

Intent最後的響應者是一個Android元件,Android的元件都可以定義IntentFilter,還記得前文中包解析器的類圖結構嗎?每一個Component類中都有一個IntentInfo物件的陣列,而IntentInfo則是IntentFilter的子類。既然一個Android元件可以定義多個IntentFilter,那麼,Intent想要匹配到最終的元件,則需要通過元件所定義的所有IntentFilter:

Intent and IntentFilter

多個IntentFilter之間是“或”的關係,哪怕其他所有的IntentFilter都匹配失敗,只要有一個IntentFilter通過,最終Intent還是找到了可以響應的元件。

每一個IntentFilter就像是一個定義了白名單規則的過濾器,只有滿足白名單的要求才會放行。IntentFilter的過濾規則,其實就是針對Intent的身份資訊的匹配規則,當Intent的身份資訊與IntentFilter所規定的要求匹配上,則允許通過;否則,Intent就被過濾掉了。IntentFilter的過濾規則包含以下三個方面:

  • Action: 每一個IntentFilter可以定義零個或多個<action>標籤,如果Intent想要通過這個IntentFilter,則Intent所轄的Action需要匹配其中至少一個。

  • Category: 每一個IntentFilter可以定義零個或多個<category>標籤,如果Intent想要通過這個IntentFilter,則Intent所轄的Category必須是IntentFilter所定義的Category的子集,才能通過IntentFilter。譬如Intent設定了兩個Category:CATEGORY_LAUNCHER和CATEGORY_MAIN,只有那些至少定義了這兩項Category的IntentFilter,才會放行該Intent。

    啟動Activity時,會為Intent設定預設的Category,即CATEGORY_DEFAULT。目標Activity需要新增一個category為CATEGORY_DEFAULT的IntentFilter來匹配這一類隱式的Intent。

  • Data:每一個IntentFilter可以定義零個或多個<data>,資料可以通過型別(MIMEType)和位置(URI)來描述,如果Intent想要通過這個IntentFilter,則Intent所轄的Data需要匹配其中至少一個。

在瞭解了Intent的身份資訊和IntentFilter的規則定義之後,就可以介紹Intent解析的過程了,這裡借用類圖來示例一下Intent解析過程中相關的資料結構:

Intent Resolver Class Diagram

PMS中有四大元件的Intent解析器,分別是ActivityIntentResolver用於解析發往Activity或Broadcast的Intent,ServiceIntentResolver用於解析發往Service的Intent,ProviderIntentResolver用於解析發往Provider的Intent,系統每收到一個Intent的解析請求時,就會使用應的解析器,它們都是IntentResolver的子類。

IntentResolver的職能就是解析Intent,它包含了所有的IntentFilter,同時有一個重要的成員函式queryIntent(),接收Intent作為引數,返回查詢結果:一個ResolveInfo物件的陣列。因為可能有多個元件來響應一個Intent,所以返回結果是一個陣列。可想而知,該函式就是針對輸入的Intent,按照前文所述的過濾規則,逐個與IntentFilter進行匹配,直到找到最終的響應者,便加入返回結果的列表。

ResolveInfo是最終的Intent解析結果的資料結構,並不複雜,就是各類元件資訊的一個包裝。需要注意的是,其中的元件資訊ActivityInfo、ProviderInfo、ServiceInfo只有一個不為空,這樣就可以區分不同元件的解析結果。

前文中提到包查詢服務的形式,應用程式通過PackageManager提供的介面,發起跨程式呼叫,最終介面實現是在系統程式的PMS中。下面我們就分析PMS.queryIntentActivities()函式:

public List<ResolveInfo> queryIntentActivities(Intent intent,
        String resolvedType, int flags, int userId) {
    if (!sUserManager.exists(userId)) return Collections.emptyList();
    enforceCrossUserPermission(Binder.getCallingUid(), userId, false, false, "query intent activities");
    // 1. 解析“顯式”的Intent
    ComponentName comp = intent.getComponent();
    if (comp == null) {
        if (intent.getSelector() != null) {
            intent = intent.getSelector();
            comp = intent.getComponent();
        }
    }
    if (comp != null) {
        final List<ResolveInfo> list = new ArrayList<ResolveInfo>(1);
        final ActivityInfo ai = getActivityInfo(comp, flags, userId);
        if (ai != null) {
            final ResolveInfo ri = new ResolveInfo();
            ri.activityInfo = ai;
            list.add(ri);
        }
        return list;
    }
    // 2. 解析“隱式”的Intent
    synchronized (mPackages) {
        ... // 省略多使用者情況下CrossProfileIntentFilter相關的程式碼
        List<ResolveInfo> result = mActivities.queryIntent(
                intent, resolvedType, flags, userId);
        ...
        return result;
    }
}
  1. 解析“顯式”的Intent,如果Intent中有設定Component,則說明已經顯式的指名由誰來響應Intent。根據Component可以獲取到對應的ActivityInfo,再在封裝成ResolveInfo便可作為結果返回了。

  2. 解析“隱式”的Intent,該處省略了大段與多使用者相關的Intent解析邏輯,僅展示了核心的函式呼叫mActivities.queryIntent(),mActivities是ActivityIntentResolver型別的物件,它負責完成對Intent的解析。

其他類似的查詢有queryIntentReceivers(), queryIntentServices(), queryIntentContentProviders()

4.3 小結

包管理對外提供服務的形式基於Binder機制,服務端是執行在系統程式中的PMS。包查詢服務是使用範圍很廣的一類服務,很多其他系統服務都需要用到包的資訊,都是通過PMS獲取的。

包查詢服務的核心是Intent的解析,PMS中實現了不同元件的解析器。針對一個輸入的Intent,解析得到可以響應的元件。Android為此設計了IntentFilter機制,定義了Intent的匹配規則,最終的解析實現在IntentResolver.queryIntent()函式中。

5 APK的安裝過程

APK安裝是一個比較耗時的操作,PMS將這項工作放到了一個服務程式com.android.defcontainer,通過訊息傳遞和跨程式呼叫的方式來驅動整個安裝過程,如下圖所示:

com.android.defcontainer

執行在系統程式中的PMS控制了整個安裝流程,具體的安裝任務由執行在com.android.defconainer程式的DefaultContainerService來完成。

在Android最終的編譯產物中,/system/priv-app/目錄下,有一個DefaultContainerService.apk,對應的就是com.android.defconainer程式,它只執行一個<service>,就是DefaultContainerService。

在Android ICS(4.0)以前的版本中,並沒有DefaultContainerService.apk,整個安裝過程是耦合在PMS中的,直到Android ICS之後的版本,才將其解耦出來。

下面,我們就來分析APK安裝相關的資料結構和整體的安裝流程。

5.1 資料結構

PMS為APK的安裝設計了一個龐大的資料結構,各個資料結構的類圖如下所示:

Install Package Class Diagram
  • APK的安裝是PackageHandler這個訊息處理器來驅動的,通過HandlerParams封裝了訊息所承載的資料。在PMS物件構造時,PackageHandler物件就會隨之構造,它繫結到一個後臺的工作執行緒,執行緒名為PackageManager;

  • HandlerParamsPackageHandler所處理的訊息承載的資料,有兩類:InstallParams對應包安裝的資料;MeasureParams對應到包測量的資料,譬如包的大小;

  • APK可以安裝在內部儲存空間或SD卡上,已經安裝的APK也可以在內部儲存和SD卡之間進行移動,PMS為此設計了InstallArgs這個資料結構,它有不同的子類:FileInstallArgs對應將包安裝到內部儲存空間,即Data分割槽;AsecInstallArgs對應到將包安裝到外部儲存空間,即SD卡;MoveInstallArgs對應將包在內外儲存空間移動,譬如將包從Data分割槽挪到SD卡。

對包安裝相關的資料結構有一個初步認識後,就可以深入具體的安裝流程,看這些資料結構是怎麼串聯起來的。

5.2 安裝流程

安裝APK這個動作可以由具備android.Manifest.permission.INSTALL_PACKAGES授權的程式發起,譬如應用商店,系統程式等。通過adb install命令來安裝APK,其實也是通過一個程式發起呼叫,最終都是PMS來響應。下圖是APK安裝的呼叫時序:

Install Package Sequence

安裝APK都是通過跨程式呼叫到PMS中的,PMS的響應函式是installPackageAsUser()

public void installPackageAsUser(String originPath, IPackageInstallObserver2 observer,
        int installFlags, String installerPackageName, VerificationParams verificationParams,
        String packageAbiOverride, int userId) {
   // 判斷是否具備包安裝許可權:INSTALL_PACKAGES
   mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null);
   final int callingUid = Binder.getCallingUid();
   enforceCrossUserPermission(callingUid, userId, true, true, "installPackageAsUser");
   if (isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
       // 如果使用者安裝受限,則退出安裝過程
       // 在多使用者的場景下,有些使用者可能被禁止安裝
       ...
       return;
   }

   // 根據安裝來源修正安裝引數
   if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) {
       // 通過adb install來安裝,就會帶上INSTALL_FROM_ADB這個安裝引數
       installFlags |= PackageManager.INSTALL_FROM_ADB;
   } else {
       installFlags &= ~PackageManager.INSTALL_FROM_ADB;
       installFlags &= ~PackageManager.INSTALL_ALL_USERS;
   }

   UserHandle user;
   if ((installFlags & PackageManager.INSTALL_ALL_USERS) != 0) {
       user = UserHandle.ALL;
   } else {
       user = new UserHandle(userId);
   }

   if ((installFlags & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0
           && mContext.checkCallingOrSelfPermission(Manifest.permission
           .INSTALL_GRANT_RUNTIME_PERMISSIONS) == PackageManager.PERMISSION_DENIED) {
       throw new SecurityException("You need the "
               + "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS permission "
               + "to use the PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS flag");
   }

   verificationParams.setInstallerUid(callingUid);
   final File originFile = new File(originPath);
   // 生成OriginInfo物件,包含APK的原始路徑等資訊
   final OriginInfo origin = OriginInfo.fromUntrustedFile(originFile);
   final Message msg = mHandler.obtainMessage(INIT_COPY);
   // 將安裝資訊封裝成一個InstallParams物件
   msg.obj = new InstallParams(origin, null, observer, installFlags, installerPackageName,
           null, verificationParams, user, packageAbiOverride, null);
   // 向訊息佇列發出**INIT_COPY**訊息
   mHandler.sendMessage(msg);
}

該函式的邏輯較為簡單,完成安裝許可權檢查後,便開始構造APK安裝資訊的資料結構,發出INIT_COPY訊息。

接下來的流程轉入到了PackageHandler對訊息的處理中,就像一個狀態機,INIT_COPY是安裝APK的初始狀態:

void doHandleMessage(Message msg) {
    switch (msg.what) {
        case INIT_COPY: {
            HandlerParams params = (HandlerParams) msg.obj;
            int idx = mPendingInstalls.size();
            if (!mBound) {
                if (!connectToService()) {
                    params.serviceError();
                    return;
                } else {
                    mPendingInstalls.add(idx, params);
                }
            } else {
                mPendingInstalls.add(idx, params);
                if (idx == 0) {
                    mHandler.sendEmptyMessage(MCS_BOUND);
                }
            }
        }
        break;
    }
}

INIT_COPY這個狀態下,首先需要做的工作是連線DefaultContainerServcie,有一個標識位mBound,用於表示是否已經連線上。當連線成功後,便將APK加入安裝佇列mPendingInstalls,發出MCS_BOUND訊息:

    case MCS_BOUND: {
        if (msg.obj != null) {
            mContainerService = (IMediaContainerService) msg.obj;
        }
        if (mContainerService == null) {
            if (!mBound) {
                ... // DefaultContainerService連線失敗,清除安裝佇列
                mPendingInstalls.clear();
            } else {
               // 繼續等待連線
            }
        } else if (mPendingInstalls.size() > 0) {
            HandlerParams params = mPendingInstalls.get(0);
            if (params != null) {
                if (params.startCopy()) {
                    if (mPendingInstalls.size() > 0) {
                        mPendingInstalls.remove(0);
                    }
                }
                if (mPendingInstalls.size() == 0) {
                    if (mBound) {
                        // 所有APK已經安裝完成,需要斷開與DefaultContainerService連線
                        removeMessages(MCS_UNBIND);
                        Message ubmsg = obtainMessage(MCS_UNBIND);
                        sendMessageDelayed(ubmsg, 10000);
                    }
                } else {
                    // 還有待安裝的APK,繼續處理MSC_BOUND訊息
                    mHandler.sendEmptyMessage(MCS_BOUND);
                }
            }
        }
    }

MCS_BOUND表示MediaContainerService這個服務已經連線上,服務端的實現是DefaultContainerService,其內部實現了IMediaContainerService這個AIDL介面。在該狀態下,需要從安裝佇列中取出一個待安裝的APK,進行安裝操作。安裝完一個APK後,又會迴圈發出MSC_BOUND訊息,繼續安裝下一個APK,知道安裝佇列為空,才斷開與DefaultContainerService的連線。

MCS_BOUND狀態下,會針一個待安裝的APK發起HandlerParam.startCopy()呼叫:

final boolean startCopy() {
    boolean res;
    try {
        if (++mRetries > MAX_RETRIES) {
            mHandler.sendEmptyMessage(MCS_GIVE_UP);
            handleServiceError();
            return false;
        } else {
            handleStartCopy();
            res = true;
        }
    } catch {...}

    handleReturnCode();
    return res;
}

該函式的主幹邏輯是呼叫handleStartCopy()handleReturnCode(),另外有一個檢查邏輯,如果嘗試安裝的次數達到了上線MAX_TRETRIES(4),則會放棄安裝過程。

HandlerParams是一個抽象類,handleStartCopy()handleReturnCode()handleServiceError()都是抽象函式,安裝APK的實現類是InstallParams,我們先來看InstallParams.handleStartCopy()函式:

public void handleStartCopy() throws RemoteException {
    int ret = PackageManager.INSTALL_SUCCEEDED;
    if (origin.staged) {
        ... // 安裝一個新APK時,staged為false
    }

    // 確定APK的安裝位置,onSd表示安裝到SD卡上,onInt表示安裝到內部儲存,即Data分割槽
    final boolean onSd = (installFlags & PackageManager.INSTALL_EXTERNAL) != 0;
    final boolean onInt = (installFlags & PackageManager.INSTALL_INTERNAL) != 0;

    PackageInfoLite pkgLite = null;
    if (onInt && onSd) {
        // APK不能同時安裝在SD卡和Data分割槽
        ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
    } else {
        // 1. 獲取APK的包資訊PackageInfoLite,此處還只是獲取一些少量的包資訊,所以叫Lite
        pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath, installFlags,
                packageAbiOverride);
        if (!origin.staged && pkgLite.recommendedInstallLocation
                == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
            ... // 儲存空間不足,則需要釋放Cache的一些空間
        }
        if (ret == PackageManager.INSTALL_SUCCEEDED) {
            int loc = pkgLite.recommendedInstallLocation;
            // 2. 判定安裝位置
            if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) {
                ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
            }
            ...
        }
    }
    // 3. 根據安裝位置建立InstallArgs物件
    final InstallArgs args = createInstallArgs(this);
    mArgs = args;
    if (ret == PackageManager.INSTALL_SUCCEEDED) {
        // 重新調整INSTALL_FROM_ADB的user標識,用做APK的檢查
        int userIdentifier = getUser().getIdentifier();
        if (userIdentifier == UserHandle.USER_ALL
                && ((installFlags & PackageManager.INSTALL_FROM_ADB) != 0)) {
            userIdentifier = UserHandle.USER_OWNER;
        }
        final int requiredUid = mRequiredVerifierPackage == null ? -1
                : getPackageUid(mRequiredVerifierPackage, userIdentifier);
        if (!origin.existing && requiredUid != -1
                && isVerificationEnabled(userIdentifier, installFlags)) {
            ... // 此處省略大段APK的檢查邏輯。
            // 目前,Android只是定義了檢查邏輯,並沒實現真正的檢查器,所以改段邏輯都不會執行
        } else {
            // 4. 呼叫InstallArgs.copyApk函式
            ret = args.copyApk(mContainerService, true);
        }
    }

    mRet = ret;
}

該函式的主要邏輯如下:

  1. 獲取待安裝APK的資訊,這時候只需要少量的資訊即可,所以建立了一個PackageInfoLite的物件。通過跨程式呼叫getMinimalPackageInfo()後,在DefaultContainerService所在的程式中,會進行一次簡單的包解析操作,得到待安裝APK的包名、版本號和安裝路徑等基本資訊;

  2. 調整安裝位置,InstallParams類的installLocationPolicy()函式用於確定最終APK的安裝位置,本文不展開分析這個函式,讀者可自行參考原始碼;

  3. 根據安裝位置建立InstallArgs物件,前文說過,InstallArgs有多個子類,分別對應不同的安裝位置,FileIntallArgs對應的安裝位置是內部儲存,即Data分割槽。

  4. 在安裝之前會進行APK的檢查,不過Android一直還沒有檢查器的實現者,所有APK的安裝都會直接到InstallArgs.copyApk()函式。

以安裝到Data分割槽的APK為例,實現類是FileInstallArgs,其copyApk()函式的邏輯如下:

int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
    if (origin.staged) {
        ... // 對於新安裝的APK,staged為false
    }
    try {
        // 1. 生成臨時目錄,譬如: /data/app/vmdl239812321.tmp
        final File tempDir = mInstallerService.allocateStageDirLegacy(volumeUuid);
        codeFile = tempDir;
        resourceFile = tempDir;
    } catch (IOException e) {
        // 空間不足,退出安裝流程
        return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
    }

    // 2. 建立一個用於跨程式傳遞的檔案描述符
    final IParcelFileDescriptorFactory target = new IParcelFileDescriptorFactory.Stub() {
        @Override
        public ParcelFileDescriptor open(String name, int mode) throws RemoteException {
            if (!FileUtils.isValidExtFilename(name)) {
                throw new IllegalArgumentException("Invalid filename: " + name);
            }
            try {
                final File file = new File(codeFile, name);
                final FileDescriptor fd = Os.open(file.getAbsolutePath(),
                        O_RDWR | O_CREAT, 0644);
                Os.chmod(file.getAbsolutePath(), 0644);
                return new ParcelFileDescriptor(fd);
            } catch (ErrnoException e) {
                throw new RemoteException("Failed to open: " + e.getMessage());
            }
        }
    };

    int ret = PackageManager.INSTALL_SUCCEEDED;
    // 3. 跨程式呼叫DefaultContainerService程式中的copyPackage()函式
    ret = imcs.copyPackage(origin.file.getAbsolutePath(), target);
    if (ret != PackageManager.INSTALL_SUCCEEDED) {
        return ret;
    }

    // 4. 拷貝Native庫
    final File libraryRoot = new File(codeFile, LIB_DIR_NAME);
    NativeLibraryHelper.Handle handle = null;
    try {
        handle = NativeLibraryHelper.Handle.create(codeFile);
        ret = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,
                abiOverride);
    } catch (IOException e) {...}

    return ret;
}

該函式的主體邏輯如下:

  1. 生成一個臨時目錄,命名如/data/app/vmdl29300388.tmp,其中數字部分29300388在每一次安裝過程都不同,是安裝時的SessionId。由於PMS監控了/data/app目錄,如果該目錄下有字尾名為.apk的檔案生成,便會觸發PMS掃描,為了避免這種情況,安裝APK時,先用了臨時的檔名。

  2. 基於臨時目錄建立拷貝目標target,一個可以跨程式傳遞的檔案描述符;

  3. 發起跨程式呼叫,DefaultContainerService所在的程式,實現了IMediaContainerService.aidl中所定義的介面,其中copyPackage()函式接收兩個引數:

    • 一個是APK的原始檔路徑,如果通過adb install安裝APK,會將APK先拷貝到/data/local/tmp目錄下,原始檔的路徑就是/data/local/tmp/Test.apk,就作為第一個引數; 如果通過Google Play這個應用市場來安裝APK,那麼Google Play會將APK下載到Cache分割槽,原始檔的路徑就是/cache/Test.apk

    • 一個是拷貝的目的路徑,即剛剛建立的target,路徑為/data/app/vmdl29300388.tmp,在DefaultContainerService程式中,會完成實際的拷貝操作,將APK的原始檔拷貝到/data/app/vmdl29300388.tmp/base.apk

  4. 完成Native庫的拷貝,此處不展開分析。

通過以上的過程,就已經將APK拷貝到/data/app目錄下,不過,此時還是臨時的檔案,後續還需要重新命名。如果以上過程沒有異常產生,那最終會返回一個INSTALL_SUCCEEDED(1)這個整數;否則,會返回對應的錯誤碼。

接下來,就會交由InstallParams.handleReturnCode()函式來針對上面的返回值進行處理:

void handleReturnCode() {
    if (mArgs != null) {
        processPendingInstall(mArgs, mRet);
    }
}

private void processPendingInstall(final InstallArgs args, final int currentStatus) {
    // 往訊息佇列丟擲一個可執行任務,完成接下來的安裝過程
    mHandler.post(new Runnable() {
        public void run() {
            mHandler.removeCallbacks(this);
            PackageInstalledInfo res = new PackageInstalledInfo();
            res.returnCode = currentStatus;
            res.uid = -1;
            res.pkg = null;
            res.removedInfo = new PackageRemovedInfo();
            if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
                args.doPreInstall(res.returnCode);
                synchronized (mInstallLock) {
                    // 安裝一個包
                    installPackageLI(args, res);
                }
                args.doPostInstall(res.returnCode, res.uid);
            }
            ...
        }
    }
}

handleReturnCode()將直接呼叫了processPendingInstall(),表示需要繼續處理APK的過程。

在此之前,我們見到的很多函式名都與copy相關,其實就是要將APK拷貝的安裝目錄。到了handleReturnCode()這個函式之後,就正式要將APK納入包管理的範圍了,如何納入呢?前文介紹過開機時的包掃描過程:將靜態的APK檔案解析成動態的資料結構,便完成了Android對一個APK的識別,從而可以方便的管理這個APK。安裝APK時,也需要經過包解析的過程。

processPendingInstall()函式中,有幾處關鍵呼叫:doPreInstall()安裝之前的檢查工作;installPackageLI()實際的安裝過程,下文重點分析;dePostInstall()安裝之後的檢查工作。 正常安裝完之後,還有與APK備份相關的操作,本文不與分析,下面,我們深入installPackageLI(),看看一個APK是如何裝載到系統中去的:

private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {
    // 1. 準備安裝引數
    final int installFlags = args.installFlags;
    final String installerPackageName = args.installerPackageName;
    final String volumeUuid = args.volumeUuid;
    final File tmpPackageFile = new File(args.getCodePath());
    ...
    // 2. 解析APK
    PackageParser pp = new PackageParser();
    pp.setSeparateProcesses(mSeparateProcesses);
    pp.setDisplayMetrics(mMetrics);
    final PackageParser.Package pkg;
    try {
        pkg = pp.parsePackage(tmpPackageFile, parseFlags);
    } catch (PackageParserException e) {
        res.setError("Failed parse during installPackageLI", e);
        return;
    }
    ...
    // 3. 獲取簽名和MD5值
    try {
        pp.collectCertificates(pkg, parseFlags);
        pp.collectManifestDigest(pkg);
    } catch (PackageParserException e) {
        res.setError("Failed collect during installPackageLI", e);
        return;
    }
    ...
    synchronized (mPackages) {
        // 4. 判定是否需要覆蓋安裝
        if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
            ... // 如果待安裝的APK已經存在,則會將replace變數設定為true,表示需要覆蓋安裝
        }

        // 5. 如果Settings中已經記錄了待安裝的APK資訊,需要驗證APK的簽名
        PackageSetting ps = mSettings.mPackages.get(pkgName);
        if (ps != null) {
            ...
        }

        // 6. 檢查待安裝的APK是否有定義新的許可權
        int N = pkg.permissions.size();
        for (int i = N-1; i >= 0; i--) {
            ...
        }
        ...
    }

    // 7. 將之前的臨時檔名vmdl239812321.tmp重名為正式的名字
    if (!args.doRename(res.returnCode, pkg, oldCodePath)) {
        ...
    }
    // 8. 通過PackageHandler發起START_INTENT_FILTER_VERIFICATIONS訊息
    startIntentFilterVerifications(args.user.getIdentifier(), replace, pkg);
    // 9. 替換升級或者安裝一個新的APK
    if (replace) {
        replacePackageLI(pkg, parseFlags, scanFlags | SCAN_REPLACING, args.user,
                installerPackageName, volumeUuid, res);
    } else {
        installNewPackageLI(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES,
                args.user, installerPackageName, volumeUuid, res);
    }
    // 10. 更新APK的所屬使用者
    synchronized (mPackages) {
        final PackageSetting ps = mSettings.mPackages.get(pkgName);
        if (ps != null) {
            res.newUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true);
        }
    }
}

該函式實現了這個邏輯:在安裝一個APK時,需要判斷系統中是否已經存在同包名的APK,如果存在,則需要判斷新舊APK的簽名以及版本資訊,來決定是否需要升級安裝;如果不存在,則安裝一個新的APK。

具體的細節本文不展開分析了,挑幾個關鍵點:

  • 如果相同包名的APK已經安裝過,則在PMS的Settings中,可以根據包名獲取到該APK的資訊,否則獲取到的APK資訊為空。因此,包名可以視為APK的唯一關鍵字。

  • 再次安裝相同包名的APK,需要判斷簽名是否匹配,這對應的很大一類場景就是已有APK的升級。試想,如果簽名不匹配就能完成APK的替換升級,那已有的APK豈不是全都可以被替換為同包名的其他APK嗎?那整個系統毫無安全性可言;

  • 之前拷貝APK時用的臨時檔名需要改成正式的名字,譬如 /data/app/vmdl239817273.tmp/base.apk 需要更名成 /data/app/packagename-1/base.apk。新名字會帶上一個字尾,如果我們不斷的升級一個已有的APK,那這個數字會從1開始不斷累加。這部分邏輯在PMS.getNextCodePath()函式中,讀者可自行查閱;

  • 該函式執行到最後,會根據replace變數判斷是否需要替換已有的APK,還是安裝一個新的APK。replace變數值是之前確定下來的,這一步有不同的兩個函式呼叫:replacePackageLI()installNewPackageLI()

下文以安裝一個全新的APK為例,分析installNewPackageLI()函式:

private void installNewPackageLI(PackageParser.Package pkg, int parseFlags, int scanFlags,
        UserHandle user, String installerPackageName, String volumeUuid,
        PackageInstalledInfo res) {
    ...
    // 判斷是否存在APK的資料。如果一個APK不是經過正常的解除安裝流程,那其歷史資料是可能還保留下來的
    final boolean dataDirExists = Environment
            .getDataUserPackageDirectory(volumeUuid, UserHandle.USER_OWNER, pkgName).exists();
    ...
    try {
        // APK掃描
        PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanFlags,
                System.currentTimeMillis(), user);
        // 更新PMS的Settings
        updateSettingsLI(newPackage, installerPackageName, volumeUuid, null, null, res, user);
        if (res.returnCode != PackageManager.INSTALL_SUCCEEDED) {
            // 如果安裝失敗,則需要刪除APK的資料目錄
            deletePackageLI(pkgName, UserHandle.ALL, false, null, null,
                    dataDirExists ? PackageManager.DELETE_KEEP_DATA : 0,
                            res.removedInfo, true);
        }
    } catch (PackageManagerException e) {...}
}

該函式呼叫了前文分析過的包掃描scanPackageLI()函式,這樣一來APK的各種資訊都會記錄在PMS中;描完以後,會呼叫updateSettingsLI()函式來更新APK的許可權,設定安裝狀態等,如果一切順利,那最終APK的安裝狀態是PKG_INSTALL_COMPLETE(1)。最終APK的資訊會持久化到PMS的Settings中。

5.3 小結

APK的安裝過程可能比大家預想的要複雜。粗略來看,可以分成兩個階段:

  1. 拷貝APK到安裝目錄。譬如通過adb install命令安裝APK,APK檔案會先拷貝到手機的/data/local/tmp目錄,然後拷貝到手機的/data/app目錄。這個過程是由PMS訊息驅動的,INIT_COPY這個訊息會觸發PMS連線DefaultContainerService,MCS_BOUND這個訊息表示已經連線上DefaultContainerService,實際的拷貝操作會在DefaultContainerService所在的程式中完成;

  2. APK拷貝到安裝目錄後,便可以掃描APK檔案。這個過程同開機時的包掃描過程相似,不同的僅僅是掃描一個APK檔案,相同的是需要檢查APK的合法性,判斷APK的簽名是否匹配,更新APK的許可權,更新PMS的Settings等。

6 總結

包管理涉及到的資料結構非常多,在分析原始碼時,很容易陷入各種資料結構之間的關係,難以自拔,以至於看不到包管理的全貌。作為本文最後的彙總,總結一下各資料結構的職能:

  • PackageManagerService #包管理的核心服務
  • com.android.server.pm.Settings # 所有包的管理資訊
    • PackageSetting # 每一個包的資訊
    • BasePermission # 系統中已有的許可權
    • PermissionsState # 授權狀態
  • PackageParser # 包解析器
    • Package # 解析得到的包資訊
    • Component # 元件的基類,其子類對應到<AndroidManifest.xml>中定義的不同元件
      • Activity
      • Provider
      • Service
      • Instrumentation
      • Permission
      • PermissionGroup
    • PackageInfo # 跨程式傳遞的包資料,包解析時生成
      • PackageItemInfo
        • ApplicationInfo
        • InstrumentationInfo
        • PermissionInfo
        • PermissionGroupInfo
        • ComponentInfo
          • ActivityInfo
          • ServiceInfo
          • ProviderInfo
    • PackageLite # 輕量的包資訊
    • ApkLite
  • IntentFilter # Intent過濾器
    • IntentInfo # 元件所定義的<intent-filter>資訊
      • ActivityIntentInfo
      • ServiceIntentInfo
      • ProviderIntentInfo
  • Intent
    • ResolveInfo
    • IntentResolver # Intent解析器,其子類用於不同元件的Intent解析
      • ActivityIntentResolver
      • ServiceIntentResolver
      • ProviderIntentResolver
  • PackageHandler # 包管理的訊息處理器
    • HandlerParams # 訊息的資料載體
      • InstallParams
      • MeasureParams
    • InstallArgs # APK的安裝引數
      • FileInstallArgs
      • AsecInstallArgs
      • MoveInfoArgs

如果讀者肯花時間同這些繁雜的資料結構周旋,那對於包管理的細節一定可以拿捏的很準確。但猛然一下要理解這麼龐大的資料結構設計,實在不是學習包管理機制的上策,畢竟Android也不是一開始就是這麼龐大的,譬如包的拆分機制就是較高版本的Android才引入的。隨著使用場景的不斷豐富,包管理的機制還會更加複雜,建議各位讀者還是抓住包管理的幾條主線:

  • 包掃描的過程:經過這個過程,Android就能將一個APK檔案的靜態資訊轉化為可以管理的資料結構
  • 包查詢的過程:Intent的定義和解析是包查詢的核心,通過包查詢服務可以獲取到一個包的資訊
  • 包安裝的過程:這個過程是包管理者接納一個新入成員的體現

誠然,本文不可能涵蓋整個包管理的內容,諸如包的刪除過程、SELinux等相關的內容,本文都沒有涉及。


相關文章