Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android7.0 PackageManagerService (2) PKMS構造函數的主要工作

Android7.0 PackageManagerService (2) PKMS構造函數的主要工作

編輯:關於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 ArraySet packages = 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
                ArraySet perms = 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
    List policies = 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 ArrayMap apks = 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 Pair packageSplit = 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, Set acceptedTags, 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構造函數主要功能比較清晰,但隱藏了許多細節。我們關注的是它大體的流程,及形成的數據結構。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved