編輯:關於Android編程
從本篇博客開始,我們開始分析PKMS的構造函數,看看PKMS到底是如何解析和管理手機中APK的信息的。
由於PKMS的構造函數較長,我們會分段進行研究。
public PackageManagerService(Context context, Installer installer, boolean factoryTest, boolean onlyCore) { ........ mContext = context; mFactoryTest = factoryTest; //假定為false,運行在非工廠模式下 mOnlyCore = onlyCore; //假定為false,即掃描所有的APK 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); ................... }
一、PKMS中的Settings
剛進入到PKMS的構造函數,我們就遇到了Settings對象,及一大堆的addSharedUserLPw調用。
我們看看Settings的構造函數:
Settings(Object lock) { this(Environment.getDataDirectory(), lock); } Settings(File dataDir, Object lock) { mLock = lock; mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock); //目錄指向"data/system" 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); //packages.xml和packages-backup.xml為一組,用於描述系統所安裝的Package信息,其中packages-backup.xml是packages.xml的備份 //PKMS寫把數據寫到backup文件中,信息全部寫成功後在改名為非backup文件,以防止在寫文件的過程中出錯,導致信息丟失 mSettingsFilename = new File(mSystemDir, "packages.xml"); mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml"); //packages.list保存系統中存在的所有非系統自帶的APK信息,即UID大於10000的apk mPackageListFilename = new File(mSystemDir, "packages.list"); FileUtils.setPermissions(mPackageListFilename, 0640, SYSTEM_UID, PACKAGE_INFO_GID); //感覺是sdcardfs相關的文件 final File kernelDir = new File("/config/sdcardfs"); mKernelMappingFilename = kernelDir.exists() ? kernelDir : null; // Deprecated: Needed for migration //packages-stopped.xml用於描述系統中強行停止運行的package信息,backup也是備份文件 mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml"); mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml"); }
從代碼可以看出,Settings的構造函數主要用於創建一些目錄和文件,並配置相應的權限。其中:
* PKMS掃描完目標文件夾後,會創建packages.xml。當系統進行程序安裝、卸載和更新等操作時,均會更新該文件;
* packages-list用於描述系統中存在的所有非系統自帶的APK信息。當這些APK有變化時,PKMS就會更新該文件;
* packages-stopped.xml記錄被用戶強行停止的應用的Package信息(例如,從設置進入某個應用,然後點擊強行停止,那麼應用的Package信息就會被記錄)。
因此,我們可以推測出Settings主要用於保存一些信息,實際上它確實是用於管理Android系統運行過程中的一些設置信息。
我們繼續跟進Settings的addSharedUserLPw函數:
//name和uid一一對應,例如:"android.uid.system":Process.SYSTEM_UID(1000) // "android.uid.phone" :RADIO_UID(Process.PHONE_UID, 1001) //pkgFlags均為:ApplicationInfo.FLAG_SYSTEM //pkgPrivateFlags均為:ApplicationInfo.PRIVATE_FLAG_PRIVILEGED 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; } //目的就是利用參數構造出SharedUserSetting s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags); s.userId = uid; if (addUserIdLPw(uid, s, name)) { //按 <名稱---SharedUserSettings> 存入map中 mSharedUsers.put(name, s); return s; } return null; } private boolean addUserIdLPw(int uid, Object obj, Object name) { //LAST_APPLICATION_UID = 19999 if (uid > Process.LAST_APPLICATION_UID) { return false; } //普通APK的uid 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; }
PKMS創建Settings後,調用一系列的addSharedUserLPw函數,將形成如上圖所示的數據結構。
如圖所示,PKMS將根據參數構建出SharedUserSettings對象,可以通過兩個維度來引用創建出的對象,即名稱和uid。
在Settings中mSharedUsers是一個map對象,利用名稱作為索引管理SharedUserSettings對象。
Settings中的mOtherUserIds和mUserIds,均是利用userId作為索引管理SharedUserSettings對象。不同的是mOtherUserIds是SparseArray,以系統uid作為鍵值;mUserIds是ArrayList,普通APK的uid為ArrayList的下標。
說了這麼多,SharedUserSettings到底是什麼?PKMS為什麼要花這麼大的力氣,創建和管理SharedUserSettings?接下來,我們就來逐步揭曉答案。
1.1 SharedUserSettings
我們看看SharedUserSettings類:
final class SharedUserSetting extends SettingBase { final String name; int userId; // flags that are associated with this uid, regardless of any package flags int uidFlags; int uidPrivateFlags; //關鍵點 final ArraySetpackages = new ArraySet (); final PackageSignatures signatures = new PackageSignatures(); SharedUserSetting(String _name, int _pkgFlags, int _pkgPrivateFlags) { super(_pkgFlags, _pkgPrivateFlags); uidFlags = _pkgFlags; uidPrivateFlags = _pkgPrivateFlags; name = _name; } ............ void removePackage(PackageSetting packageSetting) { if (packages.remove(packageSetting)) { ....... } } void addPackage(PackageSetting packageSetting) { if (packages.add(packageSetting)) { ........ } } }
從上面的代碼來看,SharedUserSettings將持有一組PackageSetting。
從SharedUserSettings的命名來看,這一組PackageSetting應該有相似的共性。
為了進一步分析,我們舉個例子來看看。
在packages/apps/Settings的AndroidManifest.xml中,有以下內容:
...............
如上所示,在xml文件中,聲明了一個名為android:sharedUserId的屬性,其值為”android.uid.system”。
實際上多個聲明了同一種sharedUserId的APK可共享彼此的數據,並且可運行在同一進程中。更重要的是,通過聲明特點的sharedUserId,該APK所在的進程將被賦予指定UID對應的權限。
我們知道Android系統中的UID表示用戶ID,GID表示用戶組ID,均與Linux系統中進程的權限管理有關。一般來說,每一個進程都會有一個對應的UID,針對不同的UID可以有不同的權限;同時,每個進程也可以分屬於不同的用戶組,即有對應的GID,針對不同的GID也可以有不同的權限。
通過上面這個例子及UID/GID的用途,SharedUserSettings的作用就可以體現出來了:
SharedUserSettings將“android:sharedUserId”屬性的名稱和對應的uid關聯起來,同時持有所有聲明相同sharedUserId的APK的PackageSettings,因此PKMS可以為同一類APK設置相同的權限。
除了在AndroidManifest.xml中聲明sharedUserId外,APK在編譯時還必須使用對應的證書簽名。例如Settings.apk,對應的Android.mk中就聲明了LOCAL_CERTIFICATE := platform。這樣Settings.apk就具有系統權限了。
1.2 SharedUserSettings相關的類圖
在這一部分的最後,我們來簡單回顧一下SharedUserSettings相關的類圖。
如上圖所示,Settings對象中持有多個SharedUserSettings對象,每個SharedUserSettings對象由會持有多個PackageSettings對象。
從繼承關系來看,SharedUserSettings和PackageSettings對象,最終都將繼承SettingsBase對象。
從圖上可以看出,SettingsBase對象持有PermissionsState對象,用於表示可用的權限。
因此,SharedUserSettings對象和PackageSettings對象中都將包含有PermissionsState。
可以據此推測出,SharedUserSettings中持有的是一組Package共有的權限;PackageSettings中持有的是單個Package獨有的權限。
PKMS中Settings除去SharedUserSettings之外,還管理了其它重要的數據結構,我們暫時略過,等流程涉及到時,再作分析。
二、讀取XML文件中系統配置信息
我們回到PKMS的構造函數,看下一段代碼:
//debug相關 ....... //構造函數傳入的InstallerService,與底層Installd通信 mInstaller = installer; mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context, "*dexopt*"); //定義一些回調函數 mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper()); mOnPermissionChangeListeners = new OnPermissionChangeListeners( FgThread.get().getLooper()); //存儲顯示信息 getDefaultDisplayMetrics(context, mMetrics); //獲取系統配置信息 SystemConfig systemConfig = SystemConfig.getInstance(); //將系統配置信息,存儲到PKMS中 mGlobalGids = systemConfig.getGlobalGids(); mSystemPermissions = systemConfig.getSystemPermissions(); mAvailableFeatures = systemConfig.getAvailableFeatures(); ..........
在這一段代碼中,PKMS創建了許多對象,暫時可以先不管它們,重點看看SystemConfig相關的函數。
//單例模式 public static SystemConfig getInstance() { synchronized (SystemConfig.class) { if (sInstance == null) { sInstance = new SystemConfig(); } return sInstance; } } SystemConfig() { // Read configuration from system //從“system”目錄下讀取 readPermissions(Environment.buildPath( Environment.getRootDirectory(), "etc", "sysconfig"), ALLOW_ALL); // Read configuration from the old permissions dir readPermissions(Environment.buildPath( Environment.getRootDirectory(), "etc", "permissions"), ALLOW_ALL); // Allow ODM to customize system configs around libs, features and apps //從"/odm"目錄下讀取 int odmPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_APP_CONFIGS; readPermissions(Environment.buildPath( Environment.getOdmDirectory(), "etc", "sysconfig"), odmPermissionFlag); readPermissions(Environment.buildPath( Environment.getOdmDirectory(), "etc", "permissions"), odmPermissionFlag); // Only allow OEM to customize features //從“oem”目錄下讀取 readPermissions(Environment.buildPath( Environment.getOemDirectory(), "etc", "sysconfig"), ALLOW_FEATURES); readPermissions(Environment.buildPath( Environment.getOemDirectory(), "etc", "permissions"), ALLOW_FEATURES); }
從上面的代碼可以看出,創建SystemConfig時,將從不同的“etc”目錄下讀取權限信息,包括root目錄、odm和oem目錄,不同目錄對應的可讀取權限的范圍不同。
我們看看readPermissions函數:
void readPermissions(File libraryDir, int permissionFlag) { //檢測目錄是否存在,是否可讀 .......... // Iterate over the files in the directory and scan .xml files File platformFile = null; for (File f : libraryDir.listFiles()) { // We'll read platform.xml last if (f.getPath().endsWith("etc/permissions/platform.xml")) { platformFile = f; continue; } //僅讀取可讀的xml文件 .......... readPermissionsFromXml(f, permissionFlag); } // Read platform permissions last so it will take precedence if (platformFile != null) { readPermissionsFromXml(platformFile, permissionFlag); } }
現在我們知道了,readPermissions就是從指定目錄下,讀取xml中的配置的權限信息。實際的手機上,可能沒有代碼中指定的所有目錄,例如沒有“odm”等,但system/etc/permissions目錄一般都是有的。
1、xml文件內容舉例
我手邊有一台root過的android 6.0的手機,以system目錄為例,看看其system/etc/permissions下的xml文件:
android.hardware.bluetooth.xml android.hardware.camera.xml //中間略去了一些。在中間甚至有廠商自己添加的xml //與Android的設計初衷不符,可能是考慮到PKMS限制了"oem"目錄下可以定義的權限種類,才添加到這個位置的 ....... platform.xml
1.1 platform.xml
platform.xml優先級最高,我們先看看platform.xml中的內容:
........ ............ ..........
從上面的xml文件可以看出,platform.xml主要作用是:
* permission和group字段用於建立Linux層GID和Android層permission字段之間的映射關系;
* assign-permission用於向指定的uid賦予相應的權限;
* library字段用於可鏈接的指定系統庫
* allow-in-power-save-except-idle用於指定進程在省電模式下(非Idle)仍可上網
* backup-transport-whitelisted-service用於指定服務具有傳輸備份數據的權利
1.2 一般的xml
了解了platform.xml後,再看看其它的xml文件,這裡以android.hardware.bluetooth.xml為例:
這種類型的xml文件包含了一些feature標簽,用於描述一個手持終端應該支持的硬件特性,例如上面的feature表示一個終端應該支持藍牙功能。
最後需要說明的是,不同設備支持的硬件特性不一樣。
同一套代碼可能需要適配不同的設備,此時通過定義mk文件,可以在編譯階段根據當前硬件平台的配置信息,復制相關的xml文件到system/etc/permission目錄下。
2、 readPermissionsFromXml
了解了xml文件的定義後,我們來看看readPermissionsFromXml函數:
private void readPermissionsFromXml(File permFile, int permissionFlag) { FileReader permReader = null; try { //利用file構造fileReader permReader = new FileReader(permFile); } catch (FileNotFoundException e) { ....... } //讀取系統屬性"ro.config.low_ram",如果該屬性為true,不會加載指定notLowRam的feature屬性 //自己曾經試過,將大量的文件利用adb push導入到/data目錄下,直到手機內存僅剩10幾M,不能再導入任何文件 //此時,手機提示內存耗盡,部分系統功能可能無法正常使用 //個人感覺和這裡的屬性比較類似,一旦手機low_ram,此時終端重啟後,將不再支持一些必須工作在內存足夠條件下的特性 //不知道這個理解是否正確?? final boolean lowRam = ActivityManager.isLowRamDeviceStatic(); try { XmlPullParser parser = Xml.newPullParser(); //Xml解析器的輸入為fileReader讀取的內容 parser.setInput(permReader); //找到解析的起點 ......... //根據傳入的flag,決定當前目錄下,從xml文件中解析內容的范圍 //對於system目錄,allowAll boolean allowAll = permissionFlag == ALLOW_ALL; boolean allowLibs = (permissionFlag & ALLOW_LIBS) != 0; boolean allowFeatures = (permissionFlag & ALLOW_FEATURES) != 0; boolean allowPermissions = (permissionFlag & ALLOW_PERMISSIONS) != 0; boolean allowAppConfigs = (permissionFlag & ALLOW_APP_CONFIGS) != 0; while (true) { XmlUtils.nextElement(parser); if (parser.getEventType() == XmlPullParser.END_DOCUMENT) { break; } String name = parser.getName(); //解析group標簽,前面介紹的xml文件中沒有單獨使用該標簽的地方 if ("group".equals(name) && allowAll) { String gidStr = parser.getAttributeValue(null, "gid"); if (gidStr != null) { //將Gid字符串轉化成整形,保存到mGlobalGids中 int gid = android.os.Process.getGidForName(gidStr); mGlobalGids = appendInt(mGlobalGids, gid); } else { ......... } XmlUtils.skipCurrentTag(parser); continue; } else if ("permission".equals(name) && allowPermissions) { String perm = parser.getAttributeValue(null, "name"); ....... perm = perm.intern(); //調用readPermission解析permission標簽 readPermission(parser, perm); } else if ("assign-permission".equals(name) && allowPermissions) { //得到權限名 String perm = parser.getAttributeValue(null, "name"); ........ //得到uid字符串 String uidStr = parser.getAttributeValue(null, "uid"); ...... //將uid字符串轉變為整形 int uid = Process.getUidForName(uidStr); ....... perm = perm.intern(); //得到保存uid當前已有的所有權限的ArraySet ArraySetperms = mSystemPermissions.get(uid); if (perms == null) { perms = new ArraySet (); mSystemPermissions.put(uid, perms); } //將uid新增的權限,加入到它的ArraySet perms.add(perm); XmlUtils.skipCurrentTag(parser); } else if ("library".equals(name) && allowLibs) { String lname = parser.getAttributeValue(null, "name"); String lfile = parser.getAttributeValue(null, "file"); if (lname == null) { ...... } else if (lfile == null) { ..... } else { //保存library標簽對應的內容 mSharedLibraries.put(lname, lfile); } } else if ("feature".equals(name) && allowFeatures) { String fname = parser.getAttributeValue(null, "name"); int fversion = XmlUtils.readIntAttribute(parser, "version", 0); if (!lowRam) { allowed = true; } else { //內存不足時,指定notLowRam的feature不再加載 String notLowRam = parser.getAttributeValue(null, "notLowRam"); allowed = !"true".equals(notLowRam); } if (fname == null) { ..... } else if (allowed) { //將feature構造成featureInfo,加入到mAvailableFeatures對象中 addFeature(fname, fversion); } ....... } else if ("unavailable-feature".equals(name) && allowFeatures) { //mUnavailableFeatures保存不支持的feature ......... } else if ("allow-in-power-save-except-idle".equals(name) && allowAll) { // These are the packages that are white-listed to be able to run in the // background while in power save mode (but not whitelisted from device idle modes), // as read from the configuration files. //mAllowInPowerSaveExceptIdle中保存省電模式下(非Idle),可上網的應用 ......... } else if ("allow-in-power-save".equals(name) && allowAll) { // These are the packages that are white-listed to be able to run in the // background while in power save mode, as read from the configuration files. //mAllowInPowerSave與mAllowInPowerSaveExceptIdle類似,權限更高 //這與Android M新特性Doze and App Standby模式有關 //DeviceIdleController用於判斷設備是否進入Idle狀態,進入Idle狀態時,mAllowInPowerSaveExceptIdle中的應用要被禁掉 //但mAllowInPowerSave中的應用仍可運行 ............ } else if ("allow-in-data-usage-save".equals(name) && allowAll) { // These are the packages that are white-listed to be able to run in the // background while in data-usage save mode, as read from the configuration files. //mAllowInDataUsageSave保存此標簽對應的packageName //貌似android 7新增了一個節省數據流量的能力,有此標簽的應用在節省數據流量時,仍可訪問網絡 ............ } else if ("app-link".equals(name) && allowAppConfigs) { // These are the package names of apps which should be in the 'always' // URL-handling state upon factory reset. //mLinkedApps保存此標簽對應的packageName //這個不太明白,好像是指定可以一直處於URL-handling state的app ....... } else if ("system-user-whitelisted-app".equals(name) && allowAppConfigs) { // These are the packages that are whitelisted to be able to run as system user //mSystemUserWhitelistedApps保存此標簽對應的packageName //指定以system user權限運行的app ....... } else if ("system-user-blacklisted-app".equals(name) && allowAppConfigs) { // These are the packages that should not run under system user //mSystemUserBlacklistedApp保存此標簽對應的packageName //指定在system user權限下,不應該運行的app ......... }else if ("default-enabled-vr-app".equals(name) && allowAppConfigs) { // These are the components that are enabled by default as VR mode listener services. //mDefaultVrComponents保存此標簽對應的packageName //指定默認運行在VR模式下的components ....... } else if ("backup-transport-whitelisted-service".equals(name) && allowFeatures) { // These are the permitted backup transport service components //mBackupTransportWhitelist保存此標簽對應的packageName //保存能夠傳輸備份數據的服務 ........ } else { ....... } } } catch (XmlPullParserException e) {PullParserException e) { ....... } catch (IOException e) { ....... } finally { IoUtils.closeQuietly(permReader); } // Some devices can be field-converted to FBE, so offer to splice in // those features if not already defined by the static config //加密相關的feature if (StorageManager.isFileEncryptedNativeOnly()) { addFeature(PackageManager.FEATURE_FILE_BASED_ENCRYPTION, 0); addFeature(PackageManager.FEATURE_SECURELY_REMOVES_USERS, 0); } for (String featureName : mUnavailableFeatures) { //從mAvailableFeatures移除不支持的feature removeFeature(featureName); } }
從上面的代碼可以看出readPermissions函數就是將xml文件中的標簽轉換成對應的數據結構,此處重要的是理解各種標簽的作用。
對於”permission”標簽,還調用了readPermission函數:
void readPermission(XmlPullParser parser, String name) throws IOException, XmlPullParserException { if (mPermissions.containsKey(name)) { throw new IllegalStateException("Duplicate permission definition for " + name); } final boolean perUser = XmlUtils.readBooleanAttribute(parser, "perUser", false); final PermissionEntry perm = new PermissionEntry(name, perUser); //將permission name和permissionEntry結合起來 mPermissions.put(name, perm); ........ while(.....) { ....... String tagName = parser.getName(); if ("group".equals(tagName)) { String gidStr = parser.getAttributeValue(null, "gid"); if (gidStr != null) { int gid = Process.getGidForName(gidStr); //對應gid存入permissionEntry結構體中,於是permission name與gid對應起來 perm.gids = appendInt(perm.gids, gid); } else { ...... } } ....... } }
PKMS創建的SystemConfig負責解析系統的xml配置文件,最終將形成上圖所示的數據結構(列舉了主要數據)。
在此之後,PKMS取出並保存了SystemConfig中的權限和feature等信息。
三、加載簽名策略
我們回到PKMS的構造函數,看下一段代碼:
.............. synchronized (mInstallLock) { synchronized (mPackages) { //mHandlerThread將負責Apk的安裝和卸載 mHandlerThread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/); mHandlerThread.start(); //PackageHandler、ProcessLoggingHandler共用ServiceThread mHandler = new PackageHandler(mHandlerThread.getLooper()); mProcessLoggingHandler = new ProcessLoggingHandler(); //Watchdog監控ServiceThread是否長時間阻塞 Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT); //創建/data下下一系列的目錄 File dataDir = Environment.getDataDirectory(); mAppInstallDir = new File(dataDir, "app"); mAppLib32InstallDir = new File(dataDir, "app-lib"); mEphemeralInstallDir = new File(dataDir, "app-ephemeral"); mAsecInternalPath = new File(dataDir, "app-asec").getPath(); mDrmAppPrivateInstallDir = new File(dataDir, "app-private"); //針對Android系統中多用戶場景 sUserManager = new UserManagerService(context, this, mPackages); //Propagate permission configuration in to package manager. //取出SystemConfig中的mPermissions ArrayMap<string, systemconfig.permissionentry=""> permConfig = systemConfig.getPermissions(); for (int i=0; i<permconfig.size(); systemconfig.permissionentry="" perm="permConfig.valueAt(i);" basepermission="" bp="=" if="" perm.gids="" string=""> libConfig = systemConfig.getSharedLibraries(); for (int i=0; i<libconfig.size(); new="" mfoundpolicyfile="SELinuxMMAC.readInstallPolicy();" pre="">
我們看看代碼:
/** * Load the mac_permissions.xml file containing all seinfo assignments used to * label apps. The loaded mac_permissions.xml file is determined by the * MAC_PERMISSIONS class variable which is set at class load time which itself * is based on the USE_OVERRIDE_POLICY class variable. For further guidance on * the proper structure of a mac_permissions.xml file consult the source code * located at system/sepolicy/mac_permissions.xml. */ public static boolean readInstallPolicy() { // Temp structure to hold the rules while we parse the xml file Listpolicies = new ArrayList<>(); FileReader policyFile = null; XmlPullParser parser = Xml.newPullParser(); try { //MAC_PERMISSIONS為SELinuxMMAC中的靜態變量,保存"system/etc/security/mac_permissions.xml"對應的file //源碼7.0中路徑為"system/sepolicy/mac_permissions.xml",應該是編譯後拷入到etc目錄的 policyFile = new FileReader(MAC_PERMISSIONS); ............. while (parser.next() != XmlPullParser.END_TAG) { ......... switch (parser.getName()) { case "signer": //加載簽名策略 //readSignerOrThrow負責解析xml,構造出policy policies.add(readSignerOrThrow(parser)); break; .......... } } } ...... // Now sort the policy stanzas PolicyComparator policySort = new PolicyComparator(); Collections.sort(policies, policySort); .......... synchronized (sPolicies) { //加載完簽名策略後存入靜態變量 sPolicies = policies; ..... } return true; }
從上面的代碼可以看出,readInstallPolicy其實也是解析xml文件,以讀出相應的簽名策略。 我們看看”system/sepolicy/mac_permissions.xml”:
seinfo決定了Android中進程所在的domain,以及其數據文件在安全上下文中的Type,linux將根據此制定訪問策略。這些內容涉及到SEAndroid安全機制,自己其實也是一知半解,有機會再做分析。
根據mac_permissions.xml的定義,如果App是在Android源碼編譯環境下,其Android.mk中指定了LOCAL_CERTIFICATE : = platform的話,它的 seinfo就是platform。如果Android.mk中不進行對應的設置,setinfo為默認值default。對於第三方APK,其seinfo值通常為default。
mac_permissions.xml編譯進system/etc目錄時,@PLATFORM將被實際的簽名信息替換,以下是我從android6.0機器中導出的文件內容:
了解mac_permissions.xml的內容後,最後再看看解析xml使用的函數:
private static Policy readSignerOrThrow(XmlPullParser parser) throws IOException, XmlPullParserException { parser.require(XmlPullParser.START_TAG, null, "signer"); //策略構造器 Policy.PolicyBuilder pb = new Policy.PolicyBuilder(); // Check for a cert attached to the signer tag. We allow a signature // to appear as an attribute as well as those attached to cert tags. String cert = parser.getAttributeValue(null, "signature"); if (cert != null) { pb.addSignature(cert); } while (parser.next() != XmlPullParser.END_TAG) { ............. String tagName = parser.getName(); if ("seinfo".equals(tagName)) { String seinfo = parser.getAttributeValue(null, "value"); pb.setGlobalSeinfoOrThrow(seinfo); readSeinfo(parser); } else if ("package".equals(tagName)) { readPackageOrThrow(parser, pb); } else if ("cert".equals(tagName)) { String sig = parser.getAttributeValue(null, "signature"); pb.addSignature(sig); readCert(parser); } else { skip(parser); } } //構造出實際的policy return pb.build(); }
容易看出,上面的函數就是根據標簽信息,構造出對應的Selinux Policy。
四、掃描Package 我們回到PKMS的構造函數,看下一段代碼:
........ //解析Settings構造函數中提及的文件:"packages.xml"、"packages-stopped.xml"等 //此處將通過解析XML文件,得到之前系統保存的Package相關的信息,暫時不深入分析函數 mRestoredSettings = mSettings.readLPw(sUserManager.getUsers(false)); .............. long startTime = SystemClock.uptimeMillis(); .............. // Set flag to monitor and not change apk file paths when // scanning install directories. //定義掃描參數 final int scanFlags = SCAN_NO_PATHS | SCAN_DEFER_DEX | SCAN_BOOTING | SCAN_INITIAL; ......... /** * Ensure all external libraries have had dexopt run on them. */ //這一部分代碼,應該是利用installd對所有platform.xml定義的鏈接庫文件進行dex優化 if (mSharedLibraries.size() > 0) { // NOTE: For now, we're compiling these system "shared libraries" // (and framework jars) into all available architectures. It's possible // to compile them only when we come across an app that uses them (there's // already logic for that in scanPackageLI) but that adds some complexity. ...................... } //指向system/framework目錄 File frameworkDir = new File(Environment.getRootDirectory(), "framework"); //處理系統升級相關的問題 .............. // Collect vendor overlay packages. // (Do this before scanning any apps.) // For security and version matching reason, only consider // overlay packages if they reside in VENDOR_OVERLAY_DIR. File vendorOverlayDir = new File(VENDOR_OVERLAY_DIR); //掃描目標目錄下的Package scanDirTracedLI(vendorOverlayDir, mDefParseFlags | PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR | PackageParser.PARSE_TRUSTED_OVERLAY, scanFlags | SCAN_TRUSTED_OVERLAY, 0); //利用scanDirTracedLI掃描system/framework、system/priv-app、system/app、vendor/app等目錄,傳入的parseFlag不一樣 ........
我們跟進一下scanDirTracedLI:
private void scanDirTracedLI(File dir, final int parseFlags, int scanFlags, long currentTime) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir"); try { //此處進行實際的掃描工作 scanDirLI(dir, parseFlags, scanFlags, currentTime); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } } private void scanDirLI(File dir, final int parseFlags, int scanFlags, long currentTime) { final File[] files = dir.listFiles(); ....... for (File file : files) { final boolean isPackage = (isApkFile(file) || file.isDirectory()) && !PackageInstallerService.isStageName(file.getName()); if (!isPackage) { // Ignore entries which are not packages continue; } try { //處理目錄下每一個package文件 scanPackageTracedLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK, scanFlags, currentTime, null); } catch (PackageManagerException e) { ......... } } } private PackageParser.Package scanPackageTracedLI(File scanFile, final int parseFlags, int scanFlags, long currentTime, UserHandle user) throws PackageManagerException { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanPackage"); try { return scanPackageLI(scanFile, parseFlags, scanFlags, currentTime, user); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } }
我們看一下此時調用的scanPackageLI函數:
private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags, long currentTime, UserHandle user) throws PackageManagerException { //創建出PackageParser對象 PackageParser pp = new PackageParser(); ........... final PackageParser.Package pkg; try { pkg = pp.parsePackage(scanFile, parseFlags); } catch (PackageParserException e) { .......... } finally { .......... } //調用另一個scanPackageLI return scanPackageLI(pkg, scanFile, parseFlags, scanFlags, currentTime, user); }
從上面的代碼,可以看出scanPackageLI先調用PackageParser對APK文件進行解析,完成從物理文件從對應數據結構的轉換。 我們先來看看對應的parsePackage函數。
1、 PackageParser的parsePackage函數
/** * Parse the package at the given location. Automatically detects if the * package is a monolithic style (single APK file) or cluster style * (directory of APKs). */ public Package parsePackage(File packageFile, int flags) throws PackageParserException { if (packageFile.isDirectory()) { return parseClusterPackage(packageFile, flags); } else { return parseMonolithicPackage(packageFile, flags); } }
從上面的代碼可以看出,對於單一APK文件和多APK文件的package,分別調用了不同的函數進行處理。實際上,兩個函數中的關鍵部分是一致的,我們以第一個函數為例,繼續分析:
/** * Parse all APKs contained in the given directory, treating them as a * single package. This also performs sanity checking, such as requiring * identical package name and version codes, a single base APK, and unique * split names. * / private Package parseClusterPackage(File packageDir, int flags) throws PackageParserException { //1、解析出簡化信息,例如名稱、路徑之類的 final PackageLite lite = parseClusterPackageLite(packageDir, 0); ......... final AssetManager assets = new AssetManager(); try { // Load the base and all splits into the AssetManager // so that resources can be overriden when parsing the manifests. //2、將APK的一些信息放入資源管理器中 loadApkIntoAssetManager(assets, lite.baseCodePath, flags); if (!ArrayUtils.isEmpty(lite.splitCodePaths)) { for (String path : lite.splitCodePaths) { loadApkIntoAssetManager(assets, path, flags); } } final File baseApk = new File(lite.baseCodePath); //3、解析主要APK信息 final Package pkg = parseBaseApk(baseApk, assets, flags); ......... if (!ArrayUtils.isEmpty(lite.splitNames)) { final int num = lite.splitNames.length; pkg.splitNames = lite.splitNames; pkg.splitCodePaths = lite.splitCodePaths; pkg.splitRevisionCodes = lite.splitRevisionCodes; pkg.splitFlags = new int[num]; pkg.splitPrivateFlags = new int[num]; for (int i = 0; i < num; i++) { //4、解析其它分離的APK信息 parseSplitApk(pkg, i, assets, flags); } } pkg.setCodePath(packageDir.getAbsolutePath()); pkg.setUse32bitAbi(lite.use32bitAbi); return pkg; } finally { IoUtils.closeQuietly(assets); } }
上面的代碼可以分為4個主要的步驟,我們現在來一一分析:
1.1 parseClusterPackageLite
private static PackageLite parseClusterPackageLite(File packageDir, int flags) throws PackageParserException { final File[] files = packageDir.listFiles(); ................ String packageName = null; int versionCode = 0; final ArrayMapapks = new ArrayMap<>(); for (File file : files) { if (isApkFile(file)) { //執行實際的parse工作 final ApkLite lite = parseApkLite(file, flags); // Assert that all package names and version codes are // consistent with the first one we encounter. if (packageName == null) { packageName = lite.packageName; versionCode = lite.versionCode; } else { //檢查名稱一致性 if (!packageName.equals(lite.packageName)) { //throw exception .............. } //檢查版本號一致性 if (versionCode != lite.versionCode) { //throw exception .............. } } // Assert that each split is defined only once if (apks.put(lite.splitName, lite) != null) { //throw exception .......... } } } //baseApk的splitName為null,因此remove後被移出 final ApkLite baseApk = apks.remove(null); .......... // Always apply deterministic ordering based on splitName final int size = apks.size(); String[] splitNames = null; String[] splitCodePaths = null; int[] splitRevisionCodes = null; //splitAPK信息排序後,存儲 if (size > 0) { splitNames = new String[size]; splitCodePaths = new String[size]; splitRevisionCodes = new int[size]; splitNames = apks.keySet().toArray(splitNames); Arrays.sort(splitNames, sSplitNameComparator); for (int i = 0; i < size; i++) { splitCodePaths[i] = apks.get(splitNames[i]).codePath; splitRevisionCodes[i] = apks.get(splitNames[i]).revisionCode; } } final String codePath = packageDir.getAbsolutePath(); //構造出PackageLite並返回 return new PackageLite(codePath, baseApk, splitNames, splitCodePaths, splitRevisionCodes); }
容易看出,上述代碼中進行實際解析操作的函數是parseApkLite:
public static ApkLite parseApkLite(File apkFile, int flags) throws PackageParserException { final String apkPath = apkFile.getAbsolutePath(); AssetManager assets = null; XmlResourceParser parser = null; try { assets = new AssetManager(); assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT); //資源管理器中存儲路徑信息 int cookie = assets.addAssetPath(apkPath); ........ final DisplayMetrics metrics = new DisplayMetrics(); metrics.setToDefaults(); final Resources res = new Resources(assets, metrics, null); //獲取一個XML資源解析文件,該對象解析的是APK中的AndroidManifest.xml文件 parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME); //獲取APK中的簽名信息 .......... //XmlResourceParser繼承自AttributeSet final AttributeSet attrs = parser; return parseApkLite(apkPath, res, parser, attrs, flags, signatures, certificates); } catch (XmlPullParserException | IOException | RuntimeException e) { ....... } finally { IoUtils.closeQuietly(parser); IoUtils.closeQuietly(assets); } } private static ApkLite parseApkLite(String codePath, Resources res, XmlPullParser parser, AttributeSet attrs, int flags, Signature[] signatures, Certificate[][] certificates) throws IOException, XmlPullParserException, PackageParserException { //得到packageName+splitName final PairpackageSplit = parsePackageSplitNames(parser, attrs); //以下變量設為默認值 int installLocation = PARSE_DEFAULT_INSTALL_LOCATION; int versionCode = 0; int revisionCode = 0; boolean coreApp = false; boolean multiArch = false; boolean use32bitAbi = false; boolean extractNativeLibs = true; //利用XML資源解析器,從xml中取出上述變量對應的值(未定義則用默認值) ................ //利用上述變量構成ApkLite返回 return new ApkLite(codePath, packageSplit.first, packageSplit.second, versionCode, revisionCode, installLocation, verifiers, signatures, certificates, coreApp, multiArch, use32bitAbi, extractNativeLibs); }
上面的代碼做了多次封裝,但本質是獲取AndroidManifest.xml對應的XML資源解析器,解析出其中部分屬性,然後形成ApkLite對象返回。
1.2 loadApkIntoAssetManager
private static int loadApkIntoAssetManager(AssetManager assets, String apkPath, int flags) throws PackageParserException { ........... // The AssetManager guarantees uniqueness for asset paths, so if this asset path // already exists in the AssetManager, addAssetPath will only return the cookie // assigned to it //前一部分實際上已經調用過AssetManager添加apkPath int cookie = assets.addAssetPath(apkPath); ........... return cookie; } /** * Add an additional set of assets to the asset manager. This can be * either a directory or ZIP file. Not for use by applications. Returns * the cookie of the added asset, or 0 on failure. */ public final int addAssetPath(String path) { return addAssetPathInternal(path, false); } private final int addAssetPathInternal(String path, boolean appAsLib) { synchronized (this) { //依賴Native函數,完成實際的添加 int res = addAssetPathNative(path, appAsLib); ......... return res; } }
上面的主要是APK對應的資源文件的路徑,加入到資源管理器中。最終還是依賴於Native層的函數完成實際的工作,在此處先不做進一步分析。
1.3 parseBaseApk
private Package parseBaseApk(Resources res, XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException, IOException { //創建Package對象,填充部分構造Package對象需要的信息 ......... return parseBaseApkCommon(pkg, null, res, parser, flags, outError); } /** * This is the common parsing routing for handling parent and child * packages in a base APK. The difference between parent and child * parsing is that some tags are not supported by child packages as * well as some manifest attributes are ignored. The implementation * assumes the calling code has already handled the manifest tag if needed * (this applies to the parent only). * / private Package parseBaseApkCommon(Package pkg, SetacceptedTags, Resources res, XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException, IOException { ........... while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { ........... String tagName = parser.getName(); ............ if (tagName.equals(TAG_APPLICATION)) { ........ //解析“application”標簽 f (!parseBaseApplication(pkg, res, parser, flags, outError)) { return null; } } else if (tagName.equals(TAG_OVERLAY)) { //填充overlay資源對應信息 ........... }......... //解析一系列AndroidManifest.xml中定義的標簽 ................ } }
parseBaseApk主要就是構造Package對象,然後解析AndroidManifest.xml中的標簽,形成對應的數據結構。 由於AndroidManifest.xml可使用的標簽太多,不一一列舉。
1.4 parseSplitApk parseSplitApk的內容與parseBaseApk基本一致:
private void parseSplitApk(Package pkg, int splitIndex, AssetManager assets, int flags) throws PackageParserException { final String apkPath = pkg.splitCodePaths[splitIndex]; ........... //路徑進入到資源管理器中 final int cookie = loadApkIntoAssetManager(assets, apkPath, flags); .......... try { res = new Resources(assets, mMetrics, null); assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT); //同樣構造出AndroidManifest.xml對應的資源解析器 parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME); //同樣是解析AndroidManifest.xml中的標簽信息,只是解析的標簽內容不同 pkg = parseSplitApk(pkg, res, parser, flags, splitIndex, outError); ......... } ....... ....... }
以上就是parseClusterPackage的主要內容,實際上就是解析出Package對應的數據結構。 代碼看起來相當繁瑣,但實際思想確實很簡單的,無非就是解析AndroidManifest.xml對應的標簽項,然後形成對應的數據結構插入到Package中。
前面提到過parseClusterPackage是用於解析存在多個APK文件的Package,parseMonolithicPackage用於解析單個APK文件的Package。 實際上parseMonolithicPackage就是靠parseBaseApk函數完成解析工作的,是parseClusterPackage函數對應工作的一個子集。
圖片鏈接
掃描Package的第一部分工作,難度不大,但極其的繁瑣,跟著流程走一邊真是想死的心都有了。不過正如Torvalds大神所說的,”RTFSC, read the fucking source code”,耐著性子多看看,是提高的基礎條件。
上圖畫出了PackageParser解析Apk文件,得到的主要的數據結構,實際的內容遠多於這些,我們僅保留了四大組件和權限相關的內容。 上面這些類,全部是定義於PackageParser中的內部類,這些內部類主要的作用就是保存AndroidManifest.xml解析出的對應信息。 以PackageParser.Activity為例,注意到該類持有ActivityInfo類,繼承自Component< ActivityIntentInfo>。其中,ActivityInfo用於保存Activity的信息;Component類是一個模板,對應元素類型是ActivityIntentInfo,頂層基類為IntentFilter。四大組件中的其它成員,也有類似的繼承結構。 這種設計的原因是:Package除了保存信息外,還需要支持Intent匹配查詢。例如,當收到某個Intent後,由於ActivityIntentInfo繼承自IntentFilter,因此它能判斷自己是否滿足Intent的要求。如果滿足,則返回對應的ActivityInfo。
最後,我們結合上圖回憶一下整個掃描過程: * PackageParser首先解析出了ApkLite,得到每個Apk文件的簡化信息(對於具有多個Apk文件的Package來說,將得到多個ApkLite); * 利用所有的ApkLite及XML中的其它信息,解析出PackageLite; * 利用PackageLite中的信息及XML中的其它信息,解析出Package信息;Package中就基本上涵蓋了AndroidManifest.xml中涉及的所有信息。 注意在上述的解析過程中,PackageParser利用AssetManager存儲了Package中資源文件的地址。
2、另一個scanPackageLI函數 通過上述的掃描過程,我們得到了當前Apk文件對應的Package信息。但這部分信息是存儲在PackageParser中的,必須將這部分信息上交到PKMS中。畢竟最終的目的是:讓PKMS能得到所有目錄下Package的信息。
private PackageParser.Package scanPackageLI(PackageParser.Package pkg, File scanFile, final int policyFlags, int scanFlags, long currentTime, UserHandle user) throws PackageManagerException { // If the package has children and this is the first dive in the function // we recursively scan the package with the SCAN_CHECK_ONLY flag set to see // whether all packages (parent and children) would be successfully scanned // before the actual scan since scanning mutates internal state and we want // to atomically install the package and its children //有childPackage時,第一次只執行檢查的工作 if ((scanFlags & SCAN_CHECK_ONLY) == 0) { //當解析一個Package的AndroidManifest.xml時,如果該XML文件中使用了"package"的tag //那麼該tag對應的package是當前XML文件對應package的childPackage if (pkg.childPackages != null && pkg.childPackages.size() > 0) { scanFlags |= SCAN_CHECK_ONLY; } } else { //第二次進入,才開始實際的解析 scanFlags &= ~SCAN_CHECK_ONLY; } final PackageParser.Package scannedPkg; try { // Scan the parent //scanFlags將決定這一次是否僅執行檢查工作 scannedPkg = scanPackageLI(pkg, policyFlags, scanFlags, currentTime, user); final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0; for (int i = 0; i < childCount; i++) { PackageParser.Package childPkg = pkg.childPackages.get(i); scanPackageLI(childPkg, policyFlags, scanFlags, currentTime, user); } } finally { ......... } if ((scanFlags & SCAN_CHECK_ONLY) != 0) { //第一次檢查完畢後,再次調用函數 return scanPackageTracedLI(pkg, policyFlags, scanFlags, currentTime, user); } return scannedPkg; } private PackageParser.Package scanPackageLI(PackageParser.Package pkg, final int policyFlags, int scanFlags, long currentTime, UserHandle user) throws PackageManagerException { boolean success = false; try { //實際的解析函數,長達1000行......我覺得要是我來寫的話,應該無法通過代碼審查 final PackageParser.Package res = scanPackageDirtyLI(pkg, policyFlags, scanFlags, currentTime, user); success = true; return res; } finally { ........... } }
我們跟進一下scanPackageDirtyLI函數:
2.1 特殊處理”Android” package
private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, final int policyFlags, final int scanFlags, long currentTime, UserHandle user) throws PackageManagerException { final File scanFile = new File(pkg.codePath); .......... //根據policyFlags設置package及其中applicationInfo等成員的信息 .......... //mCustomResolverComponentName是從系統資源中讀出的,可以配置 if (mCustomResolverComponentName != null && mCustomResolverComponentName.getPackageName().equals(pkg.packageName)) { //這裡的用途和下面判斷packageName是否為"android有關" //Replacing default ResolverActivity setUpCustomResolverActivity(pkg); } if (pkg.packageName.equals("android")) { synchronized (mPackages) { ........ if ((scanFlags & SCAN_CHECK_ONLY) == 0) { // Set up information for our fall-back user intent resolution activity. mPlatformPackage = pkg; pkg.mVersionCode = mSdkVersion; mAndroidApplication = pkg.applicationInfo; //上面的setUpCustomResolverActivity被調用時,mResolverReplaced就為true if (!mResolverReplaced) { mResolveActivity.applicationInfo = mAndroidApplication; mResolveActivity.name = ResolverActivity.class.getName(); mResolveActivity.packageName = mAndroidApplication.packageName; mResolveActivity.processName = "system:ui"; mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE; mResolveActivity.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NEVER; mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS; mResolveActivity.theme = R.style.Theme_Material_Dialog_Alert; mResolveActivity.exported = true; mResolveActivity.enabled = true; mResolveActivity.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE; mResolveActivity.configChanges = ActivityInfo.CONFIG_SCREEN_SIZE | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE | ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_ORIENTATION | ActivityInfo.CONFIG_KEYBOARD | ActivityInfo.CONFIG_KEYBOARD_HIDDEN; mResolveInfo.activityInfo = mResolveActivity; mResolveInfo.priority = 0; mResolveInfo.preferredOrder = 0; mResolveInfo.match = 0; mResolveComponentName = new ComponentName( mAndroidApplication.packageName, mResolveActivity.name); } } } ............. }
在這一部分代碼中,scanPackageDirtyLI函數單獨處理了名為”android”的Package。 和該Pacakge對應的APK是framework-res.apk,定義於frameworks/base/core/res中,對應的AndroidManifest.xml為:
..................
實際上,framework-res.apk還包含了以下常用的Activity: * ChooserActivity:當多個Activity符合某個Intent的時候,系統會彈出此Activity,由用戶選擇合適的應用來處理。 *ShutdownActivity:關機前彈出的系統對話框。
現在很多做ROM的廠商,應該就會修改這些Activity,以滿足自己的Feature。
該Package和系統息息相關,因此得到了PKMS的特變青睐,主要提現在以下幾點: * PKMS中的mPlatformPackage成員用於保存該Package信息。 * mAndroidApplication用於保存此Package中的ApplicationInfo。 * mResolveActivity指向用於表示ChooserActivity信息的ActivityInfo。 * mResolveInfo為ResolveInfo類型,它用於存儲系統解析Intent(經IntentFilter過濾)後得到得到的結果信息,例如滿足某個Intent的Activity的信息。
在從PKMS中查詢滿足某個Intent的Activity時,返回的就是ResolveInfo,再根據ResolveInfo的信息得到具體的Activity。 可能是因為ChooserActivity使用的地方較多,因此PKMS在此處保存這些信息,以提高運行過程中的效率。
在PKMS的構造函數中,有以下代碼:
.............. String customResolverActivity = Resources.getSystem().getString( R.string.config_customResolverActivity); if (TextUtils.isEmpty(customResolverActivity)) { ustomResolverActivity = null; } else { mCustomResolverComponentName = ComponentName.unflattenFromString( customResolverActivity); } ...........
因此可以通過改變配置信息,使得setUpCustomResolverActivity被調用,從而替換默認的ResolverActivity。
2.2 正常處理流程 我們回到scanPackageDirtyLI函數:
......... synchronized (mPackages) { //mPackages用於保存系統內所有Package,以pacakgeName為key if (mPackages.containsKey(pkg.packageName) || mSharedLibraries.containsKey(pkg.packageName)) { throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE, "Application package " + pkg.packageName + " already installed. Skipping duplicate."); } // If we're only installing presumed-existing packages, require that the // scanned APK is both already known and at the path previously established // for it. Previously unknown packages we pick up normally, but if we have an // a priori expectation about this package's install presence, enforce it. // With a singular exception for new system packages. When an OTA contains // a new system package, we allow the codepath to change from a system location // to the user-installed location. If we don't allow this change, any newer, // user-installed version of the application will be ignored. //這一段注釋和代碼都不是很懂........ ............ } // Initialize package source and resource directories File destCodeFile = new File(pkg.applicationInfo.getCodePath()); File destResourceFile = new File(pkg.applicationInfo.getResourcePath()); //代表該Package的SharedUserSettings對象 SharedUserSetting suid = null; //代表該Pacakge的PacakgeSettings對象 PackageSetting pkgSetting = null; .......... synchronized (mPackages) { if (pkg.mSharedUserId != null) { //創建Package對應的ShareduserSetting,然後加入到PKMS中Settings對象維護的數據結構中 suid = mSettings.getSharedUserLPw(pkg.mSharedUserId, 0, 0, true); if (suid == null) { //創建失敗,拋出異常 ......... } } //創建出Package對應的PackageSettings,必要時還要處理Package新舊信息的轉換 ............. if ((policyFlags&PackageParser.PARSE_IS_SYSTEM_DIR) == 0) { // Check all shared libraries and map to their actual file path. // We only do this here for apps not on a system dir, because those // are the only ones that can fail an install due to this. We // will take care of the system apps by updating all of their // library paths after the scan is done. //如果Package申明需要library或option-library, PKMS要確保這些library已經被加載到mSharedLibraries中 updateSharedLibrariesLPw(pkg, null); } //根據policy文件,找到Pacakge對應的seinfo,然後存入Pacakge的applicationInfo中 if (mFoundPolicyFile) { SELinuxMMAC.assignSeinfoValue(pkg); } //處理Package的簽名信息,還包括更新和驗證 ............ // Verify that this new package doesn't have any content providers // that conflict with existing packages. Only do this if the // package isn't already installed, since we don't want to break // things that are installed. if ((scanFlags & SCAN_NEW_INSTALL) != 0) { //如果是新安裝的Pacakge,需要檢查其中的Provider是否與之前安裝的Package沖突 ........... } //還是處理權限相關的,不太懂mAdoptPermissions if ((scanFlags & SCAN_CHECK_ONLY) == 0 && pkg.mAdoptPermissions != null) { // This package wants to adopt ownership of permissions from // another package. ................. } } ......... //設置運行該Pacakge的進程的進程名,一般為PackageName pkg.applicationInfo.processName = fixProcessName( pkg.applicationInfo.packageName, pkg.applicationInfo.processName, pkg.applicationInfo.uid); if (pkg != mPlatformPackage) { // Get all of our default paths setup //看代碼,此處只是為Pacakge賦予了安裝路徑 pkg.applicationInfo.initForUser(UserHandle.USER_SYSTEM); } //處理Native庫和CPU ABI ................ //處理系統APK更新時,鏈接庫的改變 synchronized (mPackages) { .............. // New library entries can only be added through the // system image. This is important to get rid of a lot // of nasty edge cases: for example if we allowed a non- // system update of the app to add a library, then uninstalling // the update would make the library go away, and assumptions // we made such as through app install filtering would now // have allowed apps on the device which aren't compatible // with it. Better to just have the restriction here, be // conservative, and create many fewer cases that can negatively // impact the user experience. .................. } ........... //將Package中的信息加入到PKMS的Settings對象中 //在此之前,四大組件的信息都是屬於Package的私有財產,現在同一注冊到PKMS中 //於是PKMS就可以對外提供統一的組件信息了 synchronized (mPackages) { // Add the new setting to mSettings mSettings.insertPackageSettingLPw(pkgSetting, pkg); // Add the new setting to mPackages mPackages.put(pkg.applicationInfo.packageName, pkg); .............. // Add the package's KeySets to the global KeySetManagerService ksms.addScannedPackageLPw(pkg); //處理Provider信息 int N = pkg.providers.size(); ......... for (i=0; i<n; packageparser.provider="" p="pkg.providers.get(i);" ........="" ..............="" n="pkg.services.size();" .........="" for="" i="0;" receiver="" .......="" activity="" permissiongroup="" .....="" ......="" return="" pre="">
PKMS掃描Pacakge的過程終於整理完畢,其實整個邏輯可以整理成上圖。 我們從代碼也可以看出,整個過程從大的邏輯上來看,其實並不復雜。但其中很多地方,例如每個標簽的含義、對某些字段的處理細節,還是需要進一步分析才談的上深入理解。此處,我們就像PKMS中提到的SCAN_CHECK_ONLY一樣,先做一個大致的了解。需要實際問題時,再作詳細分析。
五、最後的工作 我們再次回到PKMS的構造函數:
.............. // Prune any system packages that no longer exist. //以下代碼會清除一些Pacakge,例如不能使用的或不完整的,同時清除PKMS中保留的對應信息 ............... if (!mOnlyCore) { .......... //掃描第三方APK的Pacakge scanDirTracedLI(mAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0); scanDirTracedLI(mDrmAppPrivateInstallDir, mDefParseFlags | PackageParser.PARSE_FORWARD_LOCK, scanFlags | SCAN_REQUIRE_KNOWN, 0); scanDirLI(mEphemeralInstallDir, mDefParseFlags | PackageParser.PARSE_IS_EPHEMERAL, scanFlags | SCAN_REQUIRE_KNOWN, 0); ............. } // Resolve protected action filters. Only the setup wizard is allowed to // have a high priority filter for these actions. //為開機向導的action filter保留高優先級,不知原因 mSetupWizardPackage = getSetupWizardPackageName(); if (mProtectedFilters.size() > 0) { ........... for (ActivityIntentInfo filter : mProtectedFilters) { if (filter.activity.info.packageName.equals(mSetupWizardPackage)) { ......... continue; } ...... filter.setPriority(0); } } mDeferProtectedFilters = false; mProtectedFilters.clear(); // Now that we know all of the shared libraries, update all clients to have // the correct library paths. updateAllSharedLibrariesLPw(); //最後做一些其它的更新操作,例如Pacakge使用時間、權限 //做一些其它檢查 ........... // can downgrade to reader //將信息寫到package.xml、package.lsit及pacakge-stopped.xml文件中 mSettings.writeLPr(); // Perform dexopt on all apps that mark themselves as coreApps. We do this pretty // early on (before the package manager declares itself as early) because other // components in the system server might ask for package contexts for these apps. // if ((isFirstBoot() || isUpgrade() || VMRuntime.didPruneDalvikCache()) && !onlyCore) { .......... //對所有coreApp進行dexopt優化 int[] stats = performDexOpt(coreApps, false, getCompilerFilterForReason(REASON_CORE_APP)); .......... } //最後完成PKMS中一些變量的賦值、內存清理等工作 ...............
最後一部分比較重要的其實還是解析非系統Apk的AndroidManifest.xml,形成對應的Package信息加入到PKMS中。 其它部分比較細節,此處不做詳述。
六、總結 從邏輯的角度來看,PKMS構造函數主要功能比較清晰,但隱藏了許多細節。我們關注的是它大體的流程,及形成的數據結構。
Android開發的重要方面之Makefile分析 隨著移動互聯網的發展,移動開發也越來越吃香了,目前最火的莫過於android,android是什麼就不用說了,andr
使用OpenAtlas進行插件化開發,插件的開發幾乎可以按照正常程序的開發流程進行,無需添加額外的東西。為了驗證四大組件是否能夠正常工作,這裡編寫一個插件,驗證其功能。除
Location Strategies注:本指南中描述的策略適用於平台定位API中android.location。該谷歌位置服務API,谷歌Play的一部分服務,提供了
組件式開發,融入android**引言**在app中經常能看到底部彈出式菜單的應用,比如手機qq和微信中頭像的選擇。這一組件非常常用。所以,將這一組件進行封裝後,就可以