PackageManagerService的啟動過程分析,packagemanager
尊重原創:http://blog.csdn.net/yuanzeyao/article/details/42215521
在Android中,有幾個比較重要的Service。
ActivityManagerService-------主要負責管理所有的Activity的邏輯
WindowManagerService-------主要負責Android中視窗相關的邏輯
PackageManagerService-------主要是用來處理apk的安裝,卸載和應用程式資訊的擷取的
今天我們主要研究一下PackageManagerService,因為這個Service我們在平時開發過程中會經常的遇到,所以瞭解了它的啟動流程,對於我們App的開發會有很大的協助。說到這裡估計有些同學就會說,我們平時開發很少用到PackageManagerService啊?確實我們很少直接使用PackageManagerService這個類,但是我們會經常使用PackageManager這個類吧,比如我們要拿到當前應用版本時,通常會使用如下代碼:
PackageManager packageManager = getPackageManager();// getPackageName()是你當前類的包名,0代表是擷取版本資訊PackageInfo packInfo = packageManager.getPackageInfo(getPackageName(),0);
PackageManager是什嗎?它僅僅是一個介面而已,它的實作類別是ApplicationPackageManager,但是當你去研究ApplicationPackageManager的源碼的時候,你會發現,它的功能其實都是通過一個mPM的變數完成的,它的類型是IPackageManager類型,如果知道Android中Binder機制(關於Binder機制可以參考我的另外一篇文章:http://blog.csdn.net/yuanzeyao/article/details/12954641)的同學相信已經知道這個mPM是什麼東西了,它就是PackageManagerService在用戶端的一個代理,通過這個代理用戶端可以調用到PackageManagerService中的一些方法,如擷取某一個應用的版本號碼,其實版本號碼這些資訊最終都是儲存在PackageManagerService中的,我們只有通過mPM這個代理才能拿到這些資訊。下面我給出一個類圖,大致的描述一下PackageManager,ApplicationpackageManager和PackageManagerService的關係。
通過上面拿到版本號碼的例子,我們知道其實手機中所有App的資訊都是儲存在了PackageManagerService中的,我們今天研究PackageManagerService的目的其實就是熟悉PackageManagerService在啟動的過程中到底做了什麼,是如何儲存這些資訊的。PackageManagerService是通過SystemService啟動的,主要是通過調用PackageManagerService的main函數開始,在main函數中,其實就是建立了一個PackageManagerService的執行個體並且在ServiceManager中註冊,進入PackageManagerService的建構函式,你會發現這裡做了很多重量級的操作,這個也是Android系統啟動比較慢的一個主要原因。在開始學習之前我需要提醒大家,這裡涉及到的資料結構非常多,我們沒有必要知道每一個資料結構有什麼,因為那樣可能導致我們越陷越深,最終走不出來,我們可以先從宏觀分析它的啟動過程,然後再去分析它涉及的資料結構,所以為了讓大家有一個比較清晰的輪廓,我先給出兩個時序圖,然後我們可以跟著這些時序圖走,我們就永遠不會迷路了。之所以給兩個時序圖,是因為我覺得PackageManagerServiec的啟動過程大致可以分為兩部分:
建議:看文章時,最好手邊有一份Android源碼,因為這裡涉及到的代碼非常多,我不方便將代碼都貼出來
1、掃描xml檔案並解析成對應的資料結構
2、掃描apk檔案並解析成對應的資料結構
好了,我們先看第一階段的時序圖
圖 1-1
根據圖1-1,發現這裡引入了一個資料結構Settings,這個類主要功能就是儲存第一階段掃描xml後解析結果的,具體的結構我們後面會分析的,建立了Setttings執行個體之後,調用addSharedUserLPw方法,當你查看該函數的實現時,你會發現這裡有引入了一個SharedUserSetting類型的結構,這個我們暫且不用去分析,接下來就調用readPermissions去掃描/system/etc/permissions/中的所有的xml檔案,解析結果都儲存到了上面建立的Settings類型變數了,最後調用readLPw函數讀取/data/system/packages.xml檔案,第一階段就這麼簡單,就是掃描xml並解析。接下來我們詳細分析一下Settings這個資料結構吧,我先給出這個類的類圖
這裡我列出了Settings這個類中比較重要的欄位:
mSettingFilename:這個欄位就是/data/system/packages.xm檔案
mBackupSettingsFilename:這個欄位就是/data/system/packages_backup.xml檔案,這個檔案不一定存在,如果存在,那麼說明上次跟新packages.xml檔案出錯了。
mPackageListFilename:這個欄位是/data/system/packages.list
mPackages:這個欄位是一個HashMap,key 是包名,值是一個新的資料結構PackageSetting,主要包含了一個app的基本資料,如安裝位置,lib位置等等
mSharedUsers:這個欄位也是一個HashMap,key是類似"android.ui.system"這樣的欄位,在Android中每一個應用都有一個uid,兩個有相同uid的應用可以運行在同一個進程中的,所以為了讓兩個應用運行在用一個進程中,往往會在Androidmanifest.xml檔案中設定shareUserId這個屬性,這個屬性就是一個字串,但是我們知道在Linux系統中一個uid是一個整型,所以為了將字串和整型對應起來,就有了SharedUserSetting類型,剛才說key是shareUserId這個屬性的值,那麼值就是SharedUserSetting類型了,ShareUserdSetting中除了name(其實就是key),uid(對應Linux系統的uid),還有一個列表欄位,記錄了當前系統中有相同shareUserId的應用。
mPermissions:這個欄位主要儲存的是/system/etc/permissions/platform.xml中的的permission標籤的內容,因為Android系統是基於Linux系統的,所以也有使用者組的概念,在platform.xml中定義了一些許可權,並且指定了哪些使用者組具有這些許可權,一旦一個應用屬於某一個使用者組,那麼它就擁有了這個使用者組的所有許可權
mPermissionTrees:這個欄位對應packages.xml檔案中的permission-trees標籤
Settings的結構就簡單的介紹到這裡吧,下面開始第二個階段:apk檔案的掃描並解析。
在Android系統中,apk主要存在以下三個目錄:
/system/app:存放的是系統app
/vender/app:存放時廠商預裝app
/data/app:使用者自己安裝的app
其實還有一個地方會放一個apk檔案,只不過這個apk檔案很特殊,只有資源檔,沒有代碼,/system/framework/framework-res.apk
另外對於apk的解析,其實就是解析Androidmanifest.xml檔案,例如版本號碼,包名,Application的各種屬性,包含哪些Activity,Service等等,定義了哪些許可權以及運行該應用需要哪些許可權等等。同樣也是先來看一看時序圖吧,如:
圖1-2
根據圖1-2,可以看出掃描並解析apk檔案其實調用的就是scanDirLI方法,我們就進入該方法看看吧
private void scanDirLI(File dir, int flags, int scanMode, long currentTime) {//拿到指定目錄中所有的檔案 String[] files = dir.list(); if (files == null) { Log.d(TAG, "No files in app dir " + dir); return; } int i; for (i=0; i<files.length; i++) { File file = new File(dir, files[i]);//過濾掉非apk檔案 if (!isPackageFilename(files[i])) { // Ignore entries which are not apk's continue; } PackageParser.Package pkg = scanPackageLI(file, flags|PackageParser.PARSE_MUST_BE_APK, scanMode, currentTime); // 如果解析失敗,並且非系統app,那麼刪掉 if (pkg == null && (flags & PackageParser.PARSE_IS_SYSTEM) == 0 && mLastScanError == PackageManager.INSTALL_FAILED_INVALID_APK) { // Delete the apk Slog.w(TAG, "Cleaning up failed install of " + file); file.delete(); } }}
其實這個方法的邏輯非常簡單,首先拿到指定目錄下面的所有檔案,並過濾掉非apk檔案,然後調用scanPackageLI方法進行解析,注意這裡的scanPackageLI方法第一個參數是File,從圖1-2可以看到後面還有一個scanPackageLI方法,它的第一個參數是PackageParser.Package。我們繼續看第一個scanPackageLI做了什麼吧,從時序圖可以看到首先建立了PackageParser.Package對象,並調用了parsePackage方法,這裡同樣要注意的一點就是這個parsePackage方法第一個參數是File類型,後面同樣也有一個重載的方法,第一個參數類型是Resources,我們先看第一個parsePackage,它的邏輯其實也很簡單,就是通過AssertManager拿到了apk檔案的Resource檔案,然後調用重載的parsePackage方法,在重載的parsePackage方法中,通過傳入的Resources對象,拿到Androidmanifest.xml檔案,並進行解析,並將結果以PackageParser.Package的形式返回給PackageManagerService,PackageManagerService為了方便以後的訪問,需要將這個Package對象儲存起來,於是調用了第二個scanPackageLI方法,我們現在以Package例的activity資料為例,看看怎麼儲存Package中的資料
N = pkg.activities.size();r = null;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"); if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) { if (r == null) { r = new StringBuilder(256); } else { r.append(' '); } r.append(a.info.name); }}這裡調用了mActivitys.addActivity方法,這裡的mActivitys是ActivityIntentResolver類型,我們看看addActivity具體幹了什麼
public final void addActivity(PackageParser.Activity a, String type) { final boolean systemApp = isSystemApp(a.info.applicationInfo);//private final HashMap<ComponentName, PackageParser.Activity> mActivities // = new HashMap<ComponentName, PackageParser.Activity>(); mActivities.put(a.getComponentName(), a); if (DEBUG_SHOW_INFO) Log.v( TAG, " " + type + " " + (a.info.nonLocalizedLabel != null ? a.info.nonLocalizedLabel : a.info.name) + ":"); if (DEBUG_SHOW_INFO) Log.v(TAG, " Class=" + a.info.name); final int NI = a.intents.size(); for (int j=0; j<NI; j++) { PackageParser.ActivityIntentInfo intent = a.intents.get(j); if (!systemApp && intent.getPriority() > 0 && "activity".equals(type)) { intent.setPriority(0); Log.w(TAG, "Package " + a.info.applicationInfo.packageName + " has activity " + a.className + " with priority > 0, forcing to 0"); } if (DEBUG_SHOW_INFO) { Log.v(TAG, " IntentFilter:"); intent.dump(new LogPrinter(Log.VERBOSE, TAG), " "); } if (!intent.debugCheck()) { Log.w(TAG, "==> For Activity " + a.info.name); }// addFilter(intent); } }
這裡其實是將activity資料儲存到了一個HashMap資料中,這裡涉及到了一個Intent機制的一些代碼,我將會在下面一篇文章中詳解介紹怎麼通過Intent啟動Android中的組件的。這裡就不再介紹了。