本文轉載於:http://www.iloveandroid.net/2016/06/20/Android_PackageManagerService-2/
前面介紹了pm命令如何使用以及PMS運行時的一些規則和行爲,現在就可以盡情享受PMS的代碼了。
PMS的入口點
PMS是由SystemServer啓動的。
1
|
Android6.0/frameworks/base/services/java/com/android/server/SystemServer.java
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
private void startBootstrapServices(){ // Wait for installd to finish starting up so that it has a chance to // create critical directories such as /data/user with the appropriate // permissions. We need this to complete before we initialize other services. Installer installer = mSystemServiceManager.startService(Installer.class); ...................................... // Start the package manager. Slog.i(TAG, "Package Manager"); mPackageManagerService = PackageManagerService.main(mSystemContext, installer, mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore); mFirstBoot = mPackageManagerService.isFirstBoot(); mPackageManager = mSystemContext.getPackageManager(); ...................................... } |
這樣就找到了分析PMS的入口點:
1
|
Android6.0/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java的main方法
|
main函數中的第二個參數installer負責和native層中的installd守護進程進行socket通信,這裏在啓動Installer 的時候會有installd創建一些關鍵目錄,後續會詳細介紹installd守護進程。
第四個參數mOnlyCore用於判斷是否僅僅掃描系統的目錄,只有在與data分區加解密時纔會設置爲true,其他情況一般都爲false。
main函數也很簡單如下所示:
1 2 3 4 5 6 7 |
public static PackageManagerService main(Context context, Installer installer, boolean factoryTest, boolean onlyCore) { PackageManagerService m = new PackageManagerService(context, installer, factoryTest, onlyCore); ServiceManager.addService("package", m); return m; } |
這裏主要做了兩件事:
-
創建PackageManagerService對象,也就是PMS的實體
-
將PMS向SMS中註冊,即加入SMS中,方便後續其他進程或者app通過SMS獲得PMS服務
PMS的構造函數中,做了大量的工作,總結起來就是掃描Android系統中的apk,並且建立相應的數據結構去管理Package的信息,四大組件的信息,權限信息等內容。
PMS構造函數分析之Settings
源碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
mLazyDexOpt = "eng".equals(SystemProperties.get("ro.build.type")); mMetrics = new DisplayMetrics(); 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); |
首先判斷build類型,build有三種:user,userdebug,eng。當爲eng時,意味着不需要進行odex優化。
接着創建一個DisplayMetrics對象,用於保存屏幕像素參數。
然後就是本次分析的重點Settings了.
創建一個Settings對象。
源碼路徑:
1
|
Android6.0/frameworks/base/services/core/java/com/android/server/pm/Settings.java
|
其構造方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
Settings(Object lock) { this(Environment.getDataDirectory(), lock); } Settings(File dataDir, Object lock) { mLock = lock; mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock); mSystemDir = new File(dataDir, "system"); mSystemDir.mkdirs(); FileUtils.setPermissions(mSystemDir.toString(), FileUtils.S_IRWXU|FileUtils.S_IRWXG |FileUtils.S_IROTH|FileUtils.S_IXOTH, -1, -1); mSettingsFilename = new File(mSystemDir, "packages.xml"); mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml"); mPackageListFilename = new File(mSystemDir, "packages.list"); FileUtils.setPermissions(mPackageListFilename, 0640, SYSTEM_UID, PACKAGE_INFO_GID); // Deprecated: Needed for migration mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml"); mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml"); } |
dataDir爲
1
|
/data
|
所以mSystemDir爲
1
|
/data/system
|
該構造函數中主要創建了前面文章中介紹的關於PMS的若干配置文件的File對象,並且設置了權限等。
mSettings.addSharedUserLPw
addSharedUserLPw方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) { SharedUserSetting s = mSharedUsers.get(name); if (s != null) { if (s.userId == uid) { return s; } PackageManagerService.reportSettingsProblem(Log.ERROR, "Adding duplicate shared user, keeping first: " + name); return null; } s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags); s.userId = uid; if (addUserIdLPw(uid, s, name)) { mSharedUsers.put(name, s); return s; } return null; } |
該方法主要是創建共享UID的相關信息。
這裏先以name爲key在mSharedUsers中查看找是否已經有名爲name的共享UID了。如果有的話,判斷此UID和傳入的uid是否相等,不相等的就報錯。這意味着,不能對已有的共享UID信息,綁定新的uid。
如果沒有的話,創建一個新的SharedUserSetting,並將其加入mSharedUsers中去。
pkgFlags爲ApplicationInfo.FLAG_SYSTEM表明該app是system app.
pkgPrivateFlags爲ApplicationInfo.PRIVATE_FLAG_PRIVILEGED表明具備system權限。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
private boolean addUserIdLPw(int uid, Object obj, Object name) { if (uid > Process.LAST_APPLICATION_UID) { return false; } if (uid >= Process.FIRST_APPLICATION_UID) { int N = mUserIds.size(); final int index = uid - Process.FIRST_APPLICATION_UID; while (index >= N) { mUserIds.add(null); N++; } if (mUserIds.get(index) != null) { PackageManagerService.reportSettingsProblem(Log.ERROR, "Adding duplicate user id: " + uid + " name=" + name); return false; } mUserIds.set(index, obj); } else { if (mOtherUserIds.get(uid) != null) { PackageManagerService.reportSettingsProblem(Log.ERROR, "Adding duplicate shared id: " + uid + " name=" + name); return false; } mOtherUserIds.put(uid, obj); } return true; } |
主要針對普通uid和system uid進行不同處理。
每個普通的app安裝的時候,都會被分配一個uid,這和Android系統一些系統服務和具備高權限的app的uid是不在一個區間的。
普通的app的uid加入mUsrIds,其他的加入mOtherUserIds.
PMS構造方法中創建了android.uid.system,android.uid.phone,android.uid.log,android.uid.nfc,android.uid.bluetooth,android.uid.shell這些共享uid。主要給以下場景使用提供便利:
在一些系統app的AndroidManifest.xml中會有下面的信息:
1
|
android:sharedUserId="android.uis.system"
|
或者android.uid.phone等,如果不提前創建好,就沒辦法擁有這些共享uid所具備的權限了。
創建dexopt優化器對象
源碼:
1 2 3 4 5 6 |
mInstaller = installer; mPackageDexOptimizer = new PackageDexOptimizer(this); mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper()); mOnPermissionChangeListeners = new OnPermissionChangeListeners( FgThread.get().getLooper()); |
mInstaller是傳入的與installd守護進程通信的installer。
創建PackageDexOptimizer對象,該類主要用來執行ART中的patchoat命令,用來對oat文件的偏移值進行隨機化。該類是Android M 中才有的。
創建監聽權限更顯的監聽者。因爲Android M中允許動態修改App權限。
創建和初始化SystemConfig
源碼:
1 2 3 4 |
SystemConfig systemConfig = SystemConfig.getInstance(); mGlobalGids = systemConfig.getGlobalGids(); mSystemPermissions = systemConfig.getSystemPermissions(); mAvailableFeatures = systemConfig.getAvailableFeatures(); |
SystemConfig會讀取
1
|
/system/etc/permissions
|
文件夾中的相關文件。
看看其構造方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
SystemConfig() { // Read configuration from system readPermissions(Environment.buildPath( Environment.getRootDirectory(), "etc", "sysconfig"), false); // Read configuration from the old permissions dir readPermissions(Environment.buildPath( Environment.getRootDirectory(), "etc", "permissions"), false); // Only read features from OEM config readPermissions(Environment.buildPath( Environment.getOemDirectory(), "etc", "sysconfig"), true); readPermissions(Environment.buildPath( Environment.getOemDirectory(), "etc", "permissions"), true); } |
其中rootDirectory是“/system”.oemDirectory是”/oem”.也就是會嘗試依次讀取
1
|
/system/etc/sysconfig
/system/etc/permissions
/oem/etc/sysconfig
/oem/etc/permissions
|
這四個目錄中的permission文件。
permission文件的讀取是通過readPermissions函數完成的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
void readPermissions(File libraryDir, boolean onlyFeatures) { // Read permissions from given directory. if (!libraryDir.exists() || !libraryDir.isDirectory()) { if (!onlyFeatures) { Slog.w(TAG, "No directory " + libraryDir + ", skipping"); } return;//如果文件夾不存在,或者其不是一個文件夾,退出 } if (!libraryDir.canRead()) { Slog.w(TAG, "Directory " + libraryDir + " cannot be read"); return;//如果不可讀,則退出 } // Iterate over the files in the directory and scan .xml files File platformFile = null; for (File f : libraryDir.listFiles()) { // We'll read platform.xml last if (f.getPath().endsWith("etc/permissions/platform.xml")) { platformFile = f; continue;//先不處理platform.xml,最後會單獨處理 } if (!f.getPath().endsWith(".xml")) { Slog.i(TAG, "Non-xml file " + f + " in " + libraryDir + " directory, ignoring"); continue;//只處理xml文件 } if (!f.canRead()) { Slog.w(TAG, "Permissions library file " + f + " cannot be read"); continue;//如果xml文件不可讀,跳過 } readPermissionsFromXml(f, onlyFeatures);//解析xml文件 } // Read platform permissions last so it will take precedence if (platformFile != null) { //最後單獨處理platform.xml文件 readPermissionsFromXml(platformFile, onlyFeatures); } |
readPermissions方法的作用就是讀取指定目錄下的xml文件,然後調用readPermissionsFromXml方法來解析xml文件。
這些xml文件中指明瞭當前設備的硬件特性:
1 2 3 |
<permissions> <feature name="android.hardware.nfc" /> </permissions> |
解析後,存儲在ArrayMap類型的變量mAvailableFeatures中。
還可能指明瞭一些運行行時除了加載framework中的庫之外,還要加載的一些java庫:
1 2 3 4 |
<permissions> <library name="com.qualcomm.qcrilhook" file="/system/framework/qcrilhook.jar"/> </permissions> |
這些library字段中的name屬性值,將會存放到PMS的成員變量mSharedLibrsries中。
其中platform中,指明瞭當前設備中定義的相關權限:
1 2 3 4 5 6 7 8 9 |
.................. <permission name="android.permission.BLUETOOTH_ADMIN" > <group gid="net_bt_admin" /> </permission> .................. <assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="media" /> .................. |
其中permission表示name中的字符串表示的權限賦予group標籤中的屬性gid中的用戶組。解析的時候,創建一個PermissionEntry,它是SystemConfig的一個內部類
1 2 3 4 5 6 7 8 9 10 |
public static final class PermissionEntry { public final String name; public int[] gids; public boolean perUser; PermissionEntry(String name, boolean perUser) { this.name = name; this.perUser = perUser; } } |
SystemConfig中有一個ArrayMap的mPermissions變量,創建的PermissionEntry會存入該變量中。而group中的uid則會保存在SystemConfig中的mGlobalGids整型數組中。
assign-permission表示把屬性name中的字符串表示的權限賦予屬性uid中的用戶。uid和name則存入SystemConfig中的SparseArray<arrayset> 類型的mSystemPermissions變量中。