Android PackageManagerService詳細分析

概要
本篇主要分析瞭系統啟動階段包管理服務的啟動流程,其中的幾個接口在 apk 安裝時也會被調用。包管理服務啟動時主要做的工作大致有如下幾方面:

   1. 建立 java 層的 installer 與 c 層的 installd 的 socket 聯接,使得在上層的 install,remove,dexopt等功能最終由 installd 在底層實現
   2. 建 立 PackageHandler 消 息 循 環 , 用 於 處 理 外 部 的 apk 安 裝 請 求 消 息 , 如 adbinstall,packageinstaller 安裝 apk 時會發送消息
   3. 解析/system/etc/permission 下 xml 文件(framework/base/data/etc/),包括 platform.xml 和系統支持的各種硬件模塊的 feature.主要工作:
           (1) 建立底層 user ids 和 group ids 同上層 permissions 之間的映射;可以指定一個權限與幾個組 ID 對應。當一個 APK 被授予這個權限時,它也同時屬於這幾個組。
           (2) 給一些底層用戶分配權限,如給 shell 授予各種 permission 權限;把一個權限賦予一個UID,當進程使用這個 UID 運行時,就具備瞭這個權限。
           (3) library,系統增加的一些應用需要 link 的擴展 jar 庫;
           (4) feature, 系 統 每 增 加 一 個 硬 件 , 都 要 添 加 相 應 的 feature. 將 解 析 結 果mSystemPermissions,mSharedLibraries,mSettings.mPermissions,mAvailableFeatures  等幾個集合中供系統查詢和權限配置使用。
 4. 檢查/data/system/packages.xml 是否存在,這個文件是在解析 apk 時由writeLP()創建的,裡面記錄瞭系統的 permissions,以及每個 apk  name,codePath,flags,ts,version,uesrid 等信息,這些信息主要通過 apk 的AndroidManifest.xml 解析獲取,解析完 apk 後將更新信息寫入這個文件並保存到 flash,下次開機直接從裡面讀取相關信息添加到內存相關列表中。當有 apk升級,安裝或刪除時會更新這個文件。
 5. 檢查 BootClassPath,mSharedLibraries 及/system/framework 下的 jar是否需要 dexopt,需要的則通過 dexopt 進行優化
 6. 啟動 AppDirObserver 線程監測/system/framework,/system/app,/data/app,/data/app-private 目錄的事件,主要監聽 add 和 remove 事件。對於目錄監聽底層通過inotify 機制實現,inotify 是一種文件系統的變化通知機制,如文件增加、刪除等事件可以立刻讓用戶態得知,它為用戶態監視文件系統的變化提供瞭強大的支持。當有 add event 時調用 scanPackageLI(File , int , int)處理;當有 remove event 時調用 removePackageLI()處理;
 7. 對於以上幾個目錄下的 apk 逐個解析,主要是解析每個 apk 的 AndroidMa-nifest.xml 文件,處理 asset/res 等資源文件,建立起每個 apk 的配置結構信息,並將每個 apk 的配置信息添加到全局列表進行管理。調用 installer.install()進行安裝工作,檢查 apk 裡的 dex 文件是否需要再優化,如果需要優化則通過輔助工具 dexopt 進行優化處理;將解析出的 componet 添加到 pkg 的對應列表裡;對 apk 進行簽名和證書校驗,進行完整性驗證。
 8. 將解析的每個 apk 的信息保存到 packages.xml 和 packages.list 文件裡,
packages.list 記錄瞭如下數據:pkgName,userId,debugFlag,dataPath(包的數據路徑)

圖 1 主流程圖

詳細分析
在 systemserver.java 中啟動包管理服務
pm=PackageManagerService.main(context,factoryTest != SystemServer.FACTORY_TEST_OFF);
main 函數主要功能是構造 PackageManagerService 實例,然後添加到 ServiceManager 中。
    public static final IPackageManager main(Context context, boolean factoryTest) {
        PackageManagerService m = new PackageManagerService(context, factoryTest);
        ServiceManager.addService("package", m);
        return m;
    }
PackageManagerService(context, factoryTest) 是 包 管 理 服 務 的 主 進 程 。 它 完 成 瞭對/system/app,/data/app,/system/framework,/data/app-private 下的 apk 文件的解析。詳細流程如下:
              初始化過程:
              判斷 ro.build.type 是否等於 eng;
              創建系統顯示像素實例 mMetrics = new DisplayMetrics();
              創建 mSettings 實例 mSettings = new Settings(),Settings 類是 PackageManagerService 的一個
              靜態子類,它的作用主要是保持動態設置的信息,通過 Settings()構造函數在/data/system 下
              創建瞭三個文件名:packages.xml,packages-backup.xml(這個文件在 mSettings.writeLP()裡被刪除瞭),packages.list。
              mSettings 增加 android.uid.system,android.uid.phone,android.uid.log 三個共享用戶 ID,同時授予其系統權限。
             //建立 installer 與 installd 的 socket 聯接
             Installer installer = new Installer();
             installer.ping() && Process.supportsProcesses();
             installd 完成以下一些命令
            struct cmdinfo cmds[] = {
                 { "ping",             0, do_ping },
                 { "install",          3, do_install },
                 { "dexopt",          3, do_dexopt },
                 { "movedex",          2, do_move_dex },
                 { "rmdex",            1, do_rm_dex },
                 { "remove",           1, do_remove },
                 { "rename",          2, do_rename },
                 { "freecache",       1, do_free_cache },
                 { "rmcache",               1, do_rm_cache },
                 { "protect",               2, do_protect },
                 { "getsize",               3, do_get_size },
                 { "rmuserdata",           1, do_rm_user_data },
                 { "movefiles",         0, do_movefiles },
           };
//獲取當前缺省的顯示像素
                   WindowManager wm=(WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
                  Display d = wm.getDefaultDisplay();
                  d.getMetrics(mMetrics);
建立一個消息循環,用於處理 apk 安裝時的請求消息處理(這些請求來自 adb install/push,包安裝器,android market 下載安裝 apk 時發送的)
                 mHandlerThread.start();
                mHandler = new PackageHandler(mHandlerThread.getLooper());
這個消息循環處理的消息事件如下:
    static final int SEND_PENDING_BROADCAST = 1;
    static final int MCS_BOUND = 3;
    static final int END_COPY = 4;
    static final int INIT_COPY = 5;
    static final int MCS_UNBIND = 6;
    static final int START_CLEANING_PACKAGE = 7;
    static final int FIND_INSTALL_LOC = 8;
    static final int POST_INSTALL = 9;
    static final int MCS_RECONNECT = 10;
    static final int MCS_GIVE_UP = 11;
    static final int UPDATED_MEDIA_STATUS = 12;
    static final int WRITE_SETTINGS = 13;
//創建/data/data 和/data/app-private 目錄
File dataDir = Environment.getDataDirectory();//獲得/data 目錄
mAppDataDir = new File(dataDir, "data");
mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
// Read permissions from /system/etc/permission directory.
//這些文件在 framework/base/data/etc
Void readPermissions()
{
       //解析/system/etc/permission/下的*.xml 文件,獲取權限信息
       //最後解析該目錄下的 platform.xml 文件,使該文件裡的權限在棧頂出現,以便預先處理
       //這個文件記錄瞭系統級應用的 uid 及其擁有的權限
        File permFile = new File(Environment.getRootDirectory(),"etc/permissions/platform.xml");
        readPermissionsFromXml(permFile);
        //該函數的功能如下:
          通過 xml 解 析 器 解 釋 *.xml 文 件 , 提 取 標 簽 名 “ group” , "permission" , "assign-permission","library","feature"並進行相應處理。在 platform.xml 中對底層的系統用戶和組ID(group ids)同上層的由平臺管理的 permission 名字之間進行瞭關系映射,使它們關聯起來。當一個應用被授予某個權限後,同時屬於已知的組 ID,這個應用就可以進行允許這個組的文件系統操作,如(read,write,execute )。這裡記錄瞭一些系統級的應用的 uid 對應的permission
     //每個標簽的含義:
group:安裝到系統中的所有 APK 都具備的組 ID。
permission:可以指定一個權限與幾個組 ID 對應。當一個 APK 被授予這個權限時,它也同時屬於這幾個組。
assign-permission:把一個權限賦予一個 UID,當進程使用這個 UID 運行時,就具備瞭這個權限。
library:為系統添加一些擴展庫用的。對應的.jar 文件放在/system/framework/目錄下。比如Google Map 相關的庫。
feature : 每 添 加 一 個 硬 件 , 都 要 增 加 對 應 的 feature 。 將 以 上 解 析 的 結 果 對 應 放 入mGlobalGids,mSettings.mPermissions,mSystemPermissions,mSharedLibraries,,mAvailableFeatures 等幾個 list 中供系統查詢和權限配置使用。
}
//readLP()會判斷/data/system/packages.xml 文件是否存在,如果不存在則返回 false,如果存在則進行解析,在系統第一次啟動時 packages.xml 文件是不存在的,由 writeLP()創建該文件,並將該文件寫到 nand 上,下次開機會直接讀取並解析這個文件。解析的過程即是按照 xml定義的標簽,將對應的屬性和值添加到全局列表中。packages.xml 文件中記錄瞭系統安裝的所有 apk 的屬性權限的信息,當系統中的 apk 安裝,刪除或升級時,改文件就會被更新。
<permissions> 標 簽 定 義 瞭 目 前 系 統 中 定 義 的 所 有 權 限 。 主 要 分 為 兩 類 : 系 統 定 義 的(package 屬性為 Android)和 APK 定義的(package 屬性為 APK 的包名)
sharedUserId/userId:Android 系統啟動一個普通的 APK 時,會為這個 APK 分配一個獨立的UID , 這 就 是 userId 。 如 果 APK 要 和 系 統 中 其 它 APK 使 用 相 同 的 UID 的 話 , 那 就 是sharedUserId。perms:APK 的 AndroidManifest.xml 文件中,每使用一個<uses-permission>標簽,<perms>標簽中就會增加一項。
<shared-user> 代 表 一 個 共 享 UID , 通 常 , 共 同 實 現 一 系 列 相 似 功 能 的 APK 共 享 一 個UID。<perms>標簽中的 權限代表瞭這個共享 UID 的權限,所有使用的同一個共享 UID 的APK 運行在同一進程中,這個進程的 UID 就是這個共享 UID,這些 APK 都具有這個共享UID 的權限。
name:共享 UID 的名字,在 APK 的 Android:sharedUserId 屬性中使用。
userId:使用這個共享 UID 的所有 APK 運行時所在的進程的 UID。
mRestoredSettings = mSettings.readLP();
// 判 斷 boot class path 裡 的 文 件 ( jar 文 件 ) 是 否 需 要 dexopt( 判 斷 標 準 是 檢 查DvmGlobals.bootClassPath 是否已經包含這個文件),如果需要先將文件添加到 libFiles 裡,同時 進 行 dexopt : 由 Installer 通 過 socket 將 命 令 傳 給 installd 的 run_dexopt, 最 終 調 用 的是/system/bin/dexopt 對 jar 包進行處理。如果已經進行瞭 dexopt 動作,則將/data/dalvik-cache下的以 data 開頭的文件刪除,後續重新建立。如果外部庫 mSharedLibraries 列表存在,也要檢 查 列 表 中 的 元 素 是 否 需 要 dexopt, 如 果 需 要 則 和 boot class path 進 行 相 同 處 理 。 對於/system/framework 下 apk 和 jar 文件檢查是否需要 dexopt.
String bootClassPath = System.getProperty("java.boot.class.path");
 if (bootClassPath != null) {
       String[] paths = splitString(bootClassPath, ':');
            for (int i=0; i<paths.length; i++) {
               try {
                         if (dalvik.system.DexFile.isDexOptNeeded(paths[i])) {//是否需要 dexopt
                              libFiles.add(lib);//添加到 libFiles 列表
                              mInstaller.dexopt(paths[i], Process.SYSTEM_UID, true);//進行 dexopt
                           }
                      }
             }
  }
//對 framework-res.apk 不進行 dexopt 直接添加到 libFiles
       libFiles.add(mFrameworkDir.getPath() + "/framework-res.apk");
// 啟 動 AppDirObserver 線 程 監 測 /system/framework , /system/app , /data/app , /data/app-private 幾個目錄的事件,主要監聽的是 add 和 remove 事件。對於目錄監聽底層通過 inotify機制實現,inotify 是在 2.6.13 中引入的新功能,它為用戶態監視文件系統的變化提供瞭強大的支持;inotify 是一種文件系統的變化通知機制,如文件增加 ,刪除等事件可以立刻讓用戶態得知,當監測到事件發生時該線程做何處理呢?
MframeworkInstallObserver =
                new AppDirObserver(mFrameworkDir.getPath(),OBSERVER_EVENTS, true);
mFrameworkInstallObserver.startWatching();
//調用 scanDirLI 解析以上目錄下的 apk 文件,該函數是包管理服務的重要函數,在後面有詳細的分析
    private void scanDirLI(File dir, int flags, int scanMode) {
       String[] files = dir.list();
       for (i=0; i<files.length; i++)
       {
          File file = new File(dir, files[i]);
          PackageParser.Package pkg =
                 scanPackageLI(file,flags|PackageParser.PARSE_MUST_BE_APK, scanMode);
          if (pkg == null && (flags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
                   mLastScanError == PackageManager.INSTALL_FAILED_INVALID_APK) {
               // Delete the apk
               file.delete();
          }
       }
     }
//對於不存在的 system apk 調用以下函數刪除掉
    Iterator<PackageSetting> psit = mSettings.mPackages.values().iterator();
    PackageSetting ps = psit.next();
    if ((ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) != 0
                         &&!mPackages.containsKey(ps.name)
                         && !mSettings.mDisabledSysPackages.containsKey(ps.name))
    {
           psit.remove();
           mInstaller.remove(ps.name);
    }
//在解析完以上目錄下的 apk 後,更新應用的權限
        updatePermissionsLP(null, null, true, regrantPermissions, regrantPermissions);
//writeLP 會 生 成 packages.xml 和 packages.list 文 件 ,packages.list 的 數 據 格 式 是 :pkgName,userId,debugFlag,dataPath(包的數據路徑),packages.xml 保存瞭每個已經安
裝 apk 的詳盡的信息
        mSettings.writeLP();
以上是包管理服務在系統啟動時做的全部工作。
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///
下面解析其中一個比較重要的函數 scanDirLI:
private void scanDirLI(File dir, int flags, int scanMode) {
         String[] files = dir.list();
         for (i=0; i<files.length; i++)
         {
              File file = new File(dir, files[i]);
              PackageParser.Package pkg =
                         scanPackageLI(file,flags|PackageParser.PARSE_MUST_BE_APK, scanMode);
              if (pkg == null && (flags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
                       mLastScanError == PackageManager.INSTALL_FAILED_INVALID_APK) {
                  // Delete the apk
                  file.delete();
              }
         }
     }
這 個 函 數 結 構 比 較 簡 單 , 對 監 測 的 幾 個 目 錄 下 的 每 一 個 apk 文 件 繼 續 通 過scanPackageLI(file,flags|PackageParser.PARSE_MUST_BE_APK, scanMode) 進 行 解 析 , 對 不存在且安裝失敗已經無效的非系統 apk 直接刪除。


 
                                                      

                                                            圖 2 scanDirLI 流程分析

scanPackageLI 是 一 個 重 定 義 函 數 , 它 的 作 用 是 : 用 PackageParser 的 兩 個 重 定 義 函 數parsePackage 解析 package 的 asset,res,建立 asset 資源文件路徑;解析 AndroidManifest.xml文件,建立 PackageParser.Package 結構,這個結構保存瞭從 AndroidManifest.xml 解析出的package 的信息。對 package 進行數字簽名及完整性校驗,
private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanMode)
{
      //實例化一個 PackageParser 對象
          PackageParser pp = new PackageParser(scanPath);
       // parsePackage 也是一個重定義函數,它主要做瞭三件事,一個是解析 apk 中的 asset下 的 文 件 , 一 個 是 解 析 res 下 的 文 件 。 然 後 通 過 重 定 義 函 數     parsePackage(Resources res, XmlResourceParser parser, int flags, String[] outError) 對 apk 的AndroidManifest.xml 進行解析,將每個標簽對應的信息添加到每個包的相關列表中,如將標簽 application 下 的 activity 通 過 pkg.activities.add(a) 添 加 到 package 的 activities 列 表 , 將service 添加到 owner.services.add(s)。
     PackageParser.Package pkg = pp.parsePackage(scanFile, scanPath, mMetrics, parseFlags);
    //檢查這個 package 是否已經存在,以及是否重命名過,以及該系統 package 是否可以被更新,如果可以被更新,則對比系統分區和 data 分區的 package 版本,如果系統分區的
     package 高於 data 分區的版本,則保留系統分區的 package
     //對 package 進行簽名認證,如果是 system img 裡的,隻是通過 AndroidManifest.xml 獲得簽名,對簽名校驗,不會對全部文件進行有效性檢查;否則,就要結合 META-INF/進行
       簽名和有效性校驗collectCertificatesLI(pp, ps, pkg, scanFile, parseFlags);
     //調用重定義函數繼續進行解析,將每個 apk 解析出的標簽信息添加到全局的列表裡。如將 每 個 apk 的 recervers 列 表 裡 的 元 素 pkg.receivers.get(i), 通 過                  mReceivers.addActivity(a,"receiver")添加到全局列表 mReceivers 裡
     return scanPackageLI(pkg, parseFlags, scanMode | SCAN_UPDATE_SIGNATURE);
}
<補充知識>
res/raw 和 assets 區別
*res/raw 和 assets 的相同點:
            1. 兩者目錄下的文件在打包後會原封不動的保存在 apk 包中,不會被編譯成二進制。
 *res/raw 和 assets 的不同點:
            1. res/raw 中 的 文 件 會 被 映 射 到 R.java 文 件 中 , 訪 問 的 時 候 直 接 使 用 資 源 ID 即R.id.filename ; assets 文 件 夾 下 的 文 件 不 會 被 映 射 到 R.java 中 , 訪 問 的  時 候 需 要AssetManager 類。
            2.res/raw 不可以有目錄結構,而 assets 則可以有目錄結構,也就是 assets 目錄下可以再建立文件夾
*讀取文件資源:
1.讀取 res/raw 下的文件資源,通過以下方式獲取輸入流來進行寫操作InputStream is = getResources().openRawResource(R.id.filename);
2.讀取 assets 下的文件資源,通過以下方式獲取輸入流來進行寫操作
AssetManager am = null;
am = getAssets();
InputStream is = am.open("filename");
(用於內置文件但不知道文件名稱,需要篩選出想要的文件然後拷貝到目標目錄中,推薦內置在 assets 文件夾中)
1.res/raw 目錄:
通過反射的方式得到 R.java 裡面 raw 內部類裡面所有的資源 ID 的名稱,然後通過名稱獲取資源 ID 的值來讀取我們想要的文件。
2.assets 目錄:
getAssets().list("");來獲取 assets 目錄下所有文件夾和文件的名稱,再通過這些名稱讀取我們想要的文件。另,在處理 asset 時,android 限制最大的數據是 1M,超出後會報錯誤。
</>
public Package parsePackage(File sourceFile, String destCodePath, DisplayMetrics metrics, int
flags)
{
          //解析/asset 下的文件
          assmgr = new AssetManager();
          int cookie = assmgr.addAssetPath(mArchiveSourcePath);
          parser = assmgr.openXmlResourceParser(cookie, "AndroidManifest.xml");
          //解析/Res 下的文件,通過 parsePackage 函數解析 AndroidManifest.xml 文件
          Resources res = new Resources(assmgr, metrics, null);
          pkg = parsePackage(res, parser, flags, errorText);
          // 設置代碼路徑和資源路徑
          pkg.mPath = destCodePath;
          pkg.mScanPath = mArchiveSourcePath;
}
Package parsePackage(Resources res, XmlResourceParser parser, int flags, String[] outError)
{
解析 AndroidManifest.xml 裡的各個標簽,並對 pkg 的 mVersionCode,mSharedUserId,mSharedUserLabel,installLocation 等 變 量 賦 值 。 對 於 application , permission-
group , permission , permission-tree , uses-permission , uses-configuration , uses-feature , uses-sdk , supports-screens , protected-broadcast , instrumentation , original-
package,adopt-permissions,eat-comment 等標簽調用相關函數進行處理 解析出每個標簽下的子標簽的信息,然後將這些信息添加到每個 package 的對應列表中,如將 application 下的activity 通過 pkg.activities.add(a)添加到 package 的 activities 列表。
      //將 pkg 返回
      return pkg;
}
private PackageParser.Package scanPackageLI(PackageParser.Package pkg, int parseFlags, int
scanMode) {
       (1)Check all shared libraries and map to their actual file path.
       (2)check pkg.reqFeatures in mAvailableFeatures
       (3)Check and note if we are renaming from an original package name
       (4)Check if we are renaming from an original package name.
       對於 original package 不是太瞭解,還需要繼續研究
       判斷新裝應用的 content providers 是否與已經安裝應用的產生沖突。
       if (mPlatformPackage == pkg) {//如果包名以 android 開頭的,則將應用的 dataDir 設為/data/system
          // The system package is special.
          dataPath = new File (Environment.getDataDirectory(), "system");
          pkg.applicationInfo.dataDir = dataPath.getPath();
     }else {
          // This is a normal package, need to make its data directory.
          dataPath = getDataPathForPackage(pkg);
     if (dataPath.exists()) {//如果路徑存在,使用 FileUtils.getPermissions 獲取 dataPath 的權限,
          if (mOutPermissions[1] == pkg.applicationInfo.uid || !Process.supportsProcesses())
              {
                 pkg.applicationInfo.dataDir = dataPath.getPath();
              } else {
                   mInstaller.remove(pkgName);//將該包刪除
                   mInstaller.install(pkgName, pkg.applicationInfo.uid, pkg.applicationInfo.uid);
                    //重新安裝該應用
              }
             pkg.applicationInfo.dataDir = dataPath.getPath();//dataDir 重新賦值
     }
    else
              //如果路徑不存在則直接 install 安裝
     {
                mInstaller.install(pkgName, pkg.applicationInfo.uid, pkg.applicationInfo.uid);
      }
      // Perform shared library installation and dex validation and
     // optimization, if this is not a system app.
     performDexOptLI(pkg, forceDex);
         //如果新的應用已經安裝,請求 ActivityManager 將舊的 kill 掉,以免使用時造成混亂
        if ((parseFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
          killApplication(pkg.applicationInfo.packageName,pkg.applicationInfo.uid);
     }
          // Add the new setting to mSettings
          mSettings.insertPackageSettingLP(pkgSetting, pkg);
          // Add the new setting to mPackages
          mPackages.put(pkg.applicationInfo.packageName, pkg);
          // Make sure we don't accidentally delete its data.
          mSettings.mPackagesToBeCleaned.remove(pkgName);
          以下將每個包的 provider,service,activity 等信息添加到全局列表中
          mServices.addService(s);
  mReceivers.addActivity(a, "receiver");
  mActivities.addActivity(a, "activity");
  mPermissionGroups.put(pg.info.name, pg);
  permissionMap.put(p.info.name, bp);
  mInstrumentation.put(a.getComponentName(), a);
  mProtectedBroadcasts.add(pkg.protectedBroadcasts.get(i));
}

摘自 andy_android的專欄

發佈留言