1. 程式人生 > >Android之應用程式的安裝過程原始碼分析

Android之應用程式的安裝過程原始碼分析

Package管理服務PackageManagerService在安裝一個應用程式的過程中,會對這個應用程式的配置檔案AndroidManifest.xml進行解析,以便可以獲得它的安裝資訊。

Android系統中每一個應用程式都有一個Linux使用者ID,一個應用程式除了擁有一個Linux使用者ID之外,還可以擁有若干個Linux使用者組ID,以便可以在系統中獲得更多的資源訪問許可權,如讀取聯絡人資訊、使用攝像頭等許可權。

PMS在安裝一個應用程式時,如果發現它沒有與其他應用程式共享同一個Linux使用者ID,那麼就會為它分配一個唯一的Linux使用者ID,以便它可以在系統中獲得合適的執行許可權。如果發現它申請了一個特定的資源訪問許可權,那麼就會為它分配一個相應的Linux使用者組ID。

分析應用程式的安裝過程,主要是關注它的元件資訊的解析過程,以及Linux使用者ID和Linux使用者組ID的分配過程。

System程序在啟動時,會呼叫PMS類的靜態成員函式main方法將系統的PMS啟動起來。由於PMS在啟動過程中會對系統中的應用程式進行安裝,因此,接下來就從PMS類的main方法開始分析應用程式的安裝過程:

public static PackageManagerService main(Context context, Installer installer,
        boolean factoryTest, boolean onlyCore) {
    // 例項化PMS
    PackageManagerService m = new PackageManagerService(context, installer,
            factoryTest, onlyCore);
    // 把PMS新增到ServiceManager中
    ServiceManager.addService("package", m);
    return m;
}


PMS的構造方法完成的主要功能是,掃描Android系統中幾個目標資料夾中的apk檔案,從而建立合適的資料結構以管理諸如Package資訊、四大元件資訊、許可權資訊等各種資訊。PMS的工作流程相對簡單,複雜的是其中用於儲存各種資訊的資料結構和它們之間的關係,以及影響最終結果的策略控制。

由於程式碼量較大,採用分段的方法進行分析。

1.掃描目標資料夾之前的準備工作

先看下時序圖:

final int mSdkVersion = Build.VERSION.SDK_INT;
final Context mContext;
final boolean mFactoryTest;
final boolean mOnlyCore;
final boolean mLazyDexOpt;
final DisplayMetrics mMetrics;
final Settings mSettings;
// Keys are String (package name), values are Package.  This also serves
// as the lock for the global state.  Methods that must be called with
// this lock held have the prefix "LP".
@GuardedBy("mPackages")
final ArrayMap<String, PackageParser.Package> mPackages =
        new ArrayMap<String, PackageParser.Package>();
private static final int RADIO_UID = Process.PHONE_UID;
private static final int LOG_UID = Process.LOG_UID;
private static final int NFC_UID = Process.NFC_UID;
private static final int BLUETOOTH_UID = Process.BLUETOOTH_UID;
private static final int SHELL_UID = Process.SHELL_UID;

public PackageManagerService(Context context, Installer installer,
        boolean factoryTest, boolean onlyCore) {

    // 編譯的SDK版本,若沒有定義則APK就無法知道自己執行在Android的哪個版本上
    if (mSdkVersion <= 0) {
        Slog.w(TAG, "**** ro.build.version.sdk not set!");
    }

    mContext = context;
    // 是否執行在工廠模式下
    mFactoryTest = factoryTest;
    // 用於判斷是否只掃描系統目錄
    mOnlyCore = onlyCore;
    // 如果此係統是eng版本,則掃描Package後,不對其做dex優化
    mLazyDexOpt = "eng".equals(SystemProperties.get("ro.build.type"));
    // 用於儲存與顯示屏相關的一些屬性,如螢幕的寬高、解析度等資訊
    mMetrics = new DisplayMetrics();
    // Settings是用來管理應用程式的安裝資訊的
    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);
    . . .
}


剛進入建構函式就會遇到第一個較為複雜的資料結構Settings以及它的addSharedUserLPw方法。先看Settings類的構造方法,主要是建立相關的資料夾並設定資料夾的讀寫許可權:

Settings(Object lock) {
    // Environment.getDataDirectory()獲取到的是/data目錄
    this(Environment.getDataDirectory(), lock);
}

private final Object mLock;
private final RuntimePermissionPersistence mRuntimePermissionsPersistence;
private final File mSystemDir;
private final File mSettingsFilename;
private final File mBackupSettingsFilename;
private final File mPackageListFilename;
private final File mStoppedPackagesFilename;
private final File mBackupStoppedPackagesFilename;

Settings(File dataDir, Object lock) {
    mLock = lock;

    mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock);

    mSystemDir = new File(dataDir, "system");
    // 建立/data/system資料夾
    mSystemDir.mkdirs();
    // 設定/data/system資料夾的讀寫許可權
    FileUtils.setPermissions(mSystemDir.toString(),
            FileUtils.S_IRWXU|FileUtils.S_IRWXG
            |FileUtils.S_IROTH|FileUtils.S_IXOTH,
            -1, -1);
    mSettingsFilename = new File(mSystemDir, "packages.xml");
    mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml");
    mPackageListFilename = new File(mSystemDir, "packages.list");
    FileUtils.setPermissions(mPackageListFilename, 0640, SYSTEM_UID, PACKAGE_INFO_GID);

    // Deprecated: Needed for migration
    mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml");
    mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml");
}


Settings的構造方法會在data目錄下建立system目錄,用來儲存多個系統檔案。主要有:

packages.xml:記錄系統中所有安裝的應用資訊,包括基本資訊、簽名和許可權。

packages-backup.xml:packages.xml檔案的備份。寫檔案之前會先備份,寫成功後再刪除掉備份檔案,如果寫的時候出現問題,則重啟後再讀取這兩個檔案時,如果發現備份檔案存在,就會使用備份檔案的內容,因為原檔案可能已經損壞了。

packages.list:儲存普通應用的資料目錄和uid等資訊。

packages-stopped.xml:記錄系統中被強制停止執行的應用資訊。系統在強制停止某個應用時,會將應用的資訊記錄在該檔案中。

packages-stopped-backup.xml:packages-stopped.xml檔案的備份檔案。

final ArrayMap<String, SharedUserSetting> mSharedUsers = new ArrayMap<String, SharedUserSetting>();

// 新增共享使用者
SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) {
    // 根據key從map中獲取值
    SharedUserSetting s = mSharedUsers.get(name);
    // 如果值不為null並且儲存的uid和傳遞過來的一致,就直接返回結果。uid不一致則返回null
    if (s != null) {
        if (s.userId == uid) {
            return s;
        }
        PackageManagerService.reportSettingsProblem(Log.ERROR,
                "Adding duplicate shared user, keeping first: " + name);
        return null;
    }
    // 若s為null,則根據傳遞過來的引數新建立物件
    s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags);
    s.userId = uid;
    // 在系統中儲存值為uid的Linux使用者ID,成功返回true
    if (addUserIdLPw(uid, s, name)) {
        // 儲存到map中
        mSharedUsers.put(name, s);
        return s;
    }
    return null;
}

private final ArrayList<Object> mUserIds = new ArrayList<Object>();
private final SparseArray<Object> mOtherUserIds = new SparseArray<Object>();

private boolean addUserIdLPw(int uid, Object obj, Object name) {
    // LAST_APPLICATION_UID = 19999
    if (uid > Process.LAST_APPLICATION_UID) {
        return false;
    }

    // FIRST_APPLICATION_UID = 10000
    if (uid >= Process.FIRST_APPLICATION_UID) {
        // 獲取陣列的長度
        int N = mUserIds.size();
        // 計算目標索引值
        final int index = uid - Process.FIRST_APPLICATION_UID;
        // 如果目標索引值大於陣列長度,則在陣列索引值之前的位置都新增null的元素
        while (index >= N) {
            mUserIds.add(null);
            N++;
        }
        // 如果陣列的目標索引值位置有不為null的值,說明已經新增過
        if (mUserIds.get(index) != null) {
            PackageManagerService.reportSettingsProblem(Log.ERROR,
                    "Adding duplicate user id: " + uid
                    + " name=" + name);
            return false;
        }
        // 把值放在陣列的目標索引位置
        mUserIds.set(index, obj);
    } else {
        // 如果uid < 10000,則把對應的值儲存在mOtherUserIds變數中
        if (mOtherUserIds.get(uid) != null) {
            PackageManagerService.reportSettingsProblem(Log.ERROR,
                    "Adding duplicate shared id: " + uid
                            + " name=" + name);
            return false;
        }
        mOtherUserIds.put(uid, obj);
    }
    return true;
}


至此,對Settings的分析就告一段落了。下面繼續分析PMS的構造方法:

private static final long WATCHDOG_TIMEOUT = 1000*60*10; 

public PackageManagerService(Context context, Installer installer,
        boolean factoryTest, boolean onlyCore) {

    . . .

    // 應用安裝器
    mInstaller = installer;
    // 例項化類優化器
    mPackageDexOptimizer = new PackageDexOptimizer(this);
    mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());

    mOnPermissionChangeListeners = new OnPermissionChangeListeners(
            FgThread.get().getLooper());

    // 獲取當前顯示屏資訊
    getDefaultDisplayMetrics(context, mMetrics);

    // 獲取系統的初始化變數
    SystemConfig systemConfig = SystemConfig.getInstance();
    mGlobalGids = systemConfig.getGlobalGids();
    mSystemPermissions = systemConfig.getSystemPermissions();
    mAvailableFeatures = systemConfig.getAvailableFeatures();

    // 建立一個HandlerThread子類的例項,主要處理應用的安裝和解除安裝
    mHandlerThread = new ServiceThread(TAG,
            Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
    mHandlerThread.start();
    // 以mHandlerThread執行緒的looper建立的Handler例項,該Handler執行在mHandlerThread執行緒
    mHandler = new PackageHandler(mHandlerThread.getLooper());
    // 把mHandler加入到watchdog中
    Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);

    // /data目錄
    File dataDir = Environment.getDataDirectory();
    // /data/data目錄
    mAppDataDir = new File(dataDir, "data");
    // /data/app目錄,儲存的是使用者自己安裝的app
    mAppInstallDir = new File(dataDir, "app");
    mAppLib32InstallDir = new File(dataDir, "app-lib");
    mAsecInternalPath = new File(dataDir, "app-asec").getPath();
    mUserAppDataDir = new File(dataDir, "user");
    // /data/app-parivate目錄,儲存的是受DRM保護的私有app
    mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
    mRegionalizationAppInstallDir = new File(dataDir, "app-regional");

    // 例項化使用者管理服務,多使用者時使用
    sUserManager = new UserManagerService(context, this,
            mInstallLock, mPackages);

    // 通過systemConfig獲取系統中定義的許可權,這些許可權儲存在/system/etc/permissions目錄下的檔案中
    ArrayMap<String, SystemConfig.PermissionEntry> permConfig
            = systemConfig.getPermissions();
    for (int i=0; i<permConfig.size(); i++) {
        SystemConfig.PermissionEntry perm = permConfig.valueAt(i);
        // 根據許可權名獲取基本許可權資訊
        BasePermission bp = mSettings.mPermissions.get(perm.name);
        if (bp == null) {
            bp = new BasePermission(perm.name, "android", BasePermission.TYPE_BUILTIN);
            // 如果基本許可權資訊為null,則根據許可權名建立它並儲存到mSettings.mPermissions中
            mSettings.mPermissions.put(perm.name, bp);
        }
        if (perm.gids != null) {
            bp.setGids(perm.gids, perm.perUser);
        }
    }

    // 通過systemConfig獲取系統的共享庫列表,並儲存在mSharedLibraries中
    ArrayMap<String, String> libConfig = systemConfig.getSharedLibraries();
    for (int i=0; i<libConfig.size(); i++) {
        mSharedLibraries.put(libConfig.keyAt(i),
                new SharedLibraryEntry(libConfig.valueAt(i), null));
    }

    // 解析SELinux的策略檔案
    mFoundPolicyFile = SELinuxMMAC.readInstallPolicy();

    // 恢復上一次的應用程式安裝資訊,掃描package.xml檔案
    mRestoredSettings = mSettings.readLPw(this, sUserManager.getUsers(false),
            mSdkVersion, mOnlyCore);

    // 這裡獲取的customResolverActivity為空字串
    String customResolverActivity = Resources.getSystem().getString(
            R.string.config_customResolverActivity);
    if (TextUtils.isEmpty(customResolverActivity)) {
        customResolverActivity = null;
    } else {
        mCustomResolverComponentName = ComponentName.unflattenFromString(
                customResolverActivity);
    }
    . . .

}


下面分析下SystemConfig類的方法:

static SystemConfig sInstance;

public static SystemConfig getInstance() {
    synchronized (SystemConfig.class) {
        if (sInstance == null) {
            sInstance = new SystemConfig();
        }
        return sInstance;
    }
}

SystemConfig() {
    // Read configuration from system
    // Environment.getRootDirectory()獲取到的是/system目錄
    readPermissions(Environment.buildPath(
            Environment.getRootDirectory(), "etc", "sysconfig"), false);
    // Read configuration from the old permissions dir
    readPermissions(Environment.buildPath(
            Environment.getRootDirectory(), "etc", "permissions"), false);
    // Only read features from OEM config
    readPermissions(Environment.buildPath(
            Environment.getOemDirectory(), "etc", "sysconfig"), true);
    readPermissions(Environment.buildPath(
            Environment.getOemDirectory(), "etc", "permissions"), true);
}

void readPermissions(File libraryDir, boolean onlyFeatures) {
    // Read permissions from given directory.
    if (!libraryDir.exists() || !libraryDir.isDirectory()) {
        if (!onlyFeatures) {
            Slog.w(TAG, "No directory " + libraryDir + ", skipping");
        }
        return;
    }
    if (!libraryDir.canRead()) {
        Slog.w(TAG, "Directory " + libraryDir + " cannot be read");
        return;
    }

    // 遍歷目標資料夾下所有的.xml檔案
    File platformFile = null;
    for (File f : libraryDir.listFiles()) {
        // 最後解析platform.xml檔案
        if (f.getPath().endsWith("etc/permissions/platform.xml")) {
            platformFile = f;
            continue;
        }

        if (!f.getPath().endsWith(".xml")) {
            Slog.i(TAG, "Non-xml file " + f + " in " + libraryDir + " directory, ignoring");
            continue;
        }
        if (!f.canRead()) {
            Slog.w(TAG, "Permissions library file " + f + " cannot be read");
            continue;
        }

        // 解析xml檔案,儲存到相應的變數中
        readPermissionsFromXml(f, onlyFeatures);
    }

    // 最後解析platform.xml檔案,該檔案的優先順序最高
    if (platformFile != null) {
        readPermissionsFromXml(platformFile, onlyFeatures);
    }
}


public int[] getGlobalGids() {
    // 該變數儲存xml中group標籤定義的gid
    return mGlobalGids;
}

public SparseArray<ArraySet<String>> getSystemPermissions() {
    // uid為索引,儲存一個字串集合,用於描述指定UID所擁有的許可權。解析assign-permission標籤而來
    return mSystemPermissions;
}

public ArrayMap<String, String> getSharedLibraries() {
    // 解析library標籤而來,key為庫的名字,value為庫檔案的位置
    return mSharedLibraries;
}

public ArrayMap<String, FeatureInfo> getAvailableFeatures() {
    // 解析feature標籤而來。key為feature的字串描述,value為FeatureInfo物件
    return mAvailableFeatures;
}

public ArrayMap<String, PermissionEntry> getPermissions() {
    // 解析permission標籤而來,key為許可權名,value為PermissionEntry物件。
    return mPermissions;
}


Android系統每次啟動時,都會重新安裝一遍系統中的應用程式,但是有些應用程式資訊每次安裝都是需要保持一致的,如應用程式的Linux使用者ID等。否則應用程式每次在系統重啟後表現可能不一致。因此PMS每次在安裝完成應用程式之後,都需要將它們的資訊儲存下來,以便下次安裝時可以恢復回來。恢復上一次的應用程式安裝資訊是通過Settings類的readLPw方法實現的。下面就分析下該方法掃描packages.xml檔案或其備份檔案的過程:

// 讀取應用程式的安裝資訊
boolean readLPw(PackageManagerService service, List<UserInfo> users, int sdkVersion,
        boolean onlyCore) {
    FileInputStream str = null;
    // 先檢查/data/system/packages-backup.xml檔案是否存在,
    // 如果存在就將它的內容作為上一次的應用程式安裝資訊
    if (mBackupSettingsFilename.exists()) {
        try {
            str = new FileInputStream(mBackupSettingsFilename);
            mReadMessages.append("Reading from backup settings file\n");
            PackageManagerService.reportSettingsProblem(Log.INFO,
                    "Need to read from backup settings file");
            if (mSettingsFilename.exists()) {
                // 備份檔案和原檔案都存在時,原檔案可能已經損壞了,故這裡要刪除掉原檔案
                Slog.w(PackageManagerService.TAG, "Cleaning up settings file "
                        + mSettingsFilename);
                mSettingsFilename.delete();
            }
        } catch (java.io.IOException e) {
            // We'll try for the normal settings file.
        }
    }

    mPendingPackages.clear();
    mPastSignatures.clear();
    mKeySetRefs.clear();

    try {
        // 如果str為null,說明沒有讀取備份檔案,下面要讀取原檔案
        if (str == null) {
            if (!mSettingsFilename.exists()) {
                mReadMessages.append("No settings file found\n");
                PackageManagerService.reportSettingsProblem(Log.INFO,
                        "No settings file; creating initial state");
                // 如果原檔案不存在,根據預設值建立版本資訊。
                findOrCreateVersion(StorageManager.UUID_PRIVATE_INTERNAL);
                findOrCreateVersion(StorageManager.UUID_PRIMARY_PHYSICAL);
                return false;
            }
            str = new FileInputStream(mSettingsFilename);
        }
        // 建立解析器
        XmlPullParser parser = Xml.newPullParser();
        parser.setInput(str, StandardCharsets.UTF_8.name());

        int type;
        while ((type = parser.next()) != XmlPullParser.START_TAG
                && type != XmlPullParser.END_DOCUMENT) {
            ;
        }

        if (type != XmlPullParser.START_TAG) {
            mReadMessages.append("No start tag found in settings file\n");
            PackageManagerService.reportSettingsProblem(Log.WARN,
                    "No start tag found in package manager settings");
            Slog.wtf(PackageManagerService.TAG,
                    "No start tag found in package manager settings");
            return false;
        }

        int outerDepth = parser.getDepth();
        // 開始解析xml檔案
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                continue;
            }

            String tagName = parser.getName();
            if (tagName.equals("package")) {
                // 解析標籤為package的元素,一個應用一個package標籤.
                // 獲取上一次安裝這個應用程式時所分配給它的Linux使用者ID
                readPackageLPw(parser);
            } else if (tagName.equals("permissions")) {
                // 解析系統定義了哪些許可權,由哪個包定義
                readPermissionsLPw(mPermissions, parser);
            } else if (tagName.equals("permission-trees")) {
                readPermissionsLPw(mPermissionTrees, parser);
            } else if (tagName.equals("shared-user")) {
                // shared-user標籤是以sharedUserId的名字為name屬性,然後為它分配一個userId賦值給userId屬性。
                // 其他應用用到該sharedUserId的,userId都是shared-user標籤中的userId屬性值
                // 解析上一次應用程式安裝資訊中的共享Linux使用者資訊
                readSharedUserLPw(parser);
            }
            . . .
            else {
                Slog.w(PackageManagerService.TAG, "Unknown element under <packages>: "
                        + parser.getName());
                XmlUtils.skipCurrentTag(parser);
            }
        }

        str.close();

    } catch (XmlPullParserException e) {
        mReadMessages.append("Error reading: " + e.toString());
        PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e);
        Slog.wtf(PackageManagerService.TAG, "Error reading package manager settings", e);

    } catch (java.io.IOException e) {
        mReadMessages.append("Error reading: " + e.toString());
        PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e);
        Slog.wtf(PackageManagerService.TAG, "Error reading package manager settings", e);
    }

    . . .
	
	// 先看readPackageLPw方法再往下面看
    final int N = mPendingPackages.size();

    // 迴圈遍歷mPendingPackages中的PendingPackage物件,以便為它們所描述的應用程式儲存上一次安裝時所使用的Linux使用者ID
    for (int i = 0; i < N; i++) {
        final PendingPackage pp = mPendingPackages.get(i);
        // 根據uid獲取對應的物件,如果在mUserIds或mOtherUserIds中存在一個與userId對應的Object物件,
        // 且該物件是SharedUserSetting的型別,則說明pp所描述的應用程式上一次所使用的Linux使用者ID是有效的
        Object idObj = getUserIdLPr(pp.sharedId);
        if (idObj != null && idObj instanceof SharedUserSetting) {
            // 為該應用程式分配一個Linux使用者ID
            PackageSetting p = getPackageLPw(pp.name, null, pp.realName,
                    (SharedUserSetting) idObj, pp.codePath, pp.resourcePath,
                    pp.legacyNativeLibraryPathString, pp.primaryCpuAbiString,
                    pp.secondaryCpuAbiString, pp.versionCode, pp.pkgFlags, pp.pkgPrivateFlags,
                    null, true /* add */, false /* allowInstall */);
            if (p == null) {
                PackageManagerService.reportSettingsProblem(Log.WARN,
                        "Unable to create application package for " + pp.name);
                continue;
            }
            p.copyFrom(pp);
        } else if (idObj != null) {
            String msg = "Bad package setting: package " + pp.name + " has shared uid "
                    + pp.sharedId + " that is not a shared uid\n";
            mReadMessages.append(msg);
            PackageManagerService.reportSettingsProblem(Log.ERROR, msg);
        } else {
            String msg = "Bad package setting: package " + pp.name + " has shared uid "
                    + pp.sharedId + " that is not defined\n";
            mReadMessages.append(msg);
            PackageManagerService.reportSettingsProblem(Log.ERROR, msg);
        }
    }
    mPendingPackages.clear();
    . . .

    mReadMessages.append("Read completed successfully: " + mPackages.size() + " packages, "
            + mSharedUsers.size() + " shared uids\n");

    return true;
}

private static final String ATTR_NAME = "name";
// 解析標籤為package的元素,可以獲取到上一次安裝這個應用程式時所分配給它的Linux使用者ID
private void readPackageLPw(XmlPullParser parser) throws XmlPullParserException, IOException {
    String name = null;
    String idStr = null;
    String sharedIdStr = null;
    . . .
    try {
        // 應用程式的包名
        name = parser.getAttributeValue(null, ATTR_NAME);
        // 應用程式所使用的獨立Linux使用者ID
        idStr = parser.getAttributeValue(null, "userId");
        // 應用程式所使用的共享Linux使用者ID
        sharedIdStr = parser.getAttributeValue(null, "sharedUserId");
        . . .

        int userId = idStr != null ? Integer.parseInt(idStr) : 0;
        . . .
        if (name == null) {
            // 安裝的應用程式包名不能為null,否則上報錯誤資訊
            PackageManagerService.reportSettingsProblem(Log.WARN,
                    "Error in package manager settings: <package> has no name at "
                            + parser.getPositionDescription());
        }
        . . .
        else if (userId > 0) {
            // 檢查該應用是否被分配了一個獨立的uid,如果是就在系統中儲存值為userId的Linux使用者ID
            packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr),
                    new File(resourcePathStr), legacyNativeLibraryPathStr, primaryCpuAbiString,
                    secondaryCpuAbiString, cpuAbiOverrideString, userId, versionCode, pkgFlags,
                    pkgPrivateFlags);
            . . .
        } else if (sharedIdStr != null) {
            // 如果sharedIdStr不為null,說明安裝該應用時PMS給它分配了一個共享的uid。此時不能馬上儲存該uid,
            // 因為這個uid不屬於它自己所有,等解析完shared-user節點之後,再為它儲存上一次所使用的Linux使用者ID
            userId = sharedIdStr != null ? Integer.parseInt(sharedIdStr) : 0;
            if (userId > 0) {
                // 建立PendingPackage物件並儲存在mPendingPackages中,用來描述一個Linux使用者ID還未確定的應用程式
                packageSetting = new PendingPackage(name.intern(), realName, new File(
                        codePathStr), new File(resourcePathStr), legacyNativeLibraryPathStr,
                        primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString,
                        userId, versionCode, pkgFlags, pkgPrivateFlags);
                . . .
                mPendingPackages.add((PendingPackage) packageSetting);
                . . .
            }
            . . .
        } else {
            PackageManagerService.reportSettingsProblem(Log.WARN,
                    "Error in package manager settings: package " + name + " has bad userId "
                            + idStr + " at " + parser.getPositionDescription());
        }
    } catch (NumberFormatException e) {
        PackageManagerService.reportSettingsProblem(Log.WARN,
                "Error in package manager settings: package " + name + " has bad userId "
                        + idStr + " at " + parser.getPositionDescription());
    }
}

// 在系統中儲存值為userId的Linux使用者ID
// 在PMS中,每一個應用程式的安裝資訊都是使用一個PackageSetting物件來描述的。這些物件儲存在mPackages中。
PackageSetting addPackageLPw(String name, String realName, File codePath, File resourcePath,
        String legacyNativeLibraryPathString, String primaryCpuAbiString, String secondaryCpuAbiString,
        String cpuAbiOverrideString, int uid, int vc, int pkgFlags, int pkgPrivateFlags) {
    // 根據包名獲取應用程式的安裝資訊
    PackageSetting p = mPackages.get(name);
    if (p != null) {
        // 如果應用程式的安裝資訊不為null,並且uid和目標uid相同則說明PMS已經為該應用程式分配過uid了
        if (p.appId == uid) {
            return p;
        }
        PackageManagerService.reportSettingsProblem(Log.ERROR,
                "Adding duplicate package, keeping first: " + name);
        return null;
    }
    // 如果mPackages中不存在與該包名對應的PackageSetting物件,則為該應用程式分配一個引數uid所描述的Linux使用者ID
    p = new PackageSetting(name, realName, codePath, resourcePath,
            legacyNativeLibraryPathString, primaryCpuAbiString, secondaryCpuAbiString,
            cpuAbiOverrideString, vc, pkgFlags, pkgPrivateFlags);
    p.appId = uid;
    // 在系統中儲存儲存值為uid的Linux使用者ID
    if (addUserIdLPw(uid, p, name)) {
        mPackages.put(name, p);
        return p;
    }
    return null;
}

// 解析上一次的應用程式安裝資訊中的共享Linux使用者資訊
private void readSharedUserLPw(XmlPullParser parser) throws XmlPullParserException,IOException {
    String name = null;
    String idStr = null;
    int pkgFlags = 0;
    int pkgPrivateFlags = 0;
    SharedUserSetting su = null;
    try {
        // 獲取sharedUserId的名字
        name = parser.getAttributeValue(null, ATTR_NAME);
        // 所有共享該sharedUserId名字的應用程式uid
        idStr = parser.getAttributeValue(null, "userId");
        int userId = idStr != null ? Integer.parseInt(idStr) : 0;
        // system屬性用來描述這個共享uid是分配給一個系統型別的應用程式使用的還是分配給一個使用者型別的應用程式使用的。
        if ("true".equals(parser.getAttributeValue(null, "system"))) {
            pkgFlags |= ApplicationInfo.FLAG_SYSTEM;
        }
        if (name == null) {
            PackageManagerService.reportSettingsProblem(Log.WARN,
                    "Error in package manager settings: <shared-user> has no name at "
                            + parser.getPositionDescription());
        } else if (userId == 0) {
            PackageManagerService.reportSettingsProblem(Log.WARN,
                    "Error in package manager settings: shared-user " + name
                            + " has bad userId " + idStr + " at "
                            + parser.getPositionDescription());
        } else {
            // 呼叫addSharedUserLPw方法在系統中為名稱為name的共享Linux使用者儲存一個值為userId的Linux使用者
            if ((su = addSharedUserLPw(name.intern(), userId, pkgFlags, pkgPrivateFlags))
                    == null) {
                PackageManagerService
                        .reportSettingsProblem(Log.ERROR, "Occurred while parsing settings at "
                                + parser.getPositionDescription());
            }
        }
    } catch (NumberFormatException e) {
        PackageManagerService.reportSettingsProblem(Log.WARN,
                "Error in package manager settings: package " + name + " has bad userId "
                        + idStr + " at " + parser.getPositionDescription());
    }

    . . .
}

// 在系統中為名稱為name的共享Linux使用者儲存一個值為userId的Linux使用者
// 在PMS中,每一個共享Linux使用者都是使用一個SharedUserSetting物件來描述的。這些物件儲存在mSharedUsers中。
SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) {
    // 根據包名獲取共享應用程式的安裝資訊,如果存在並且userId等於引數uid的值,則說明PMS已經為該應用程式分配過uid了
    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;
    }
    // 如果mSharedUsers中不存在與該包名對應的SharedUserSetting物件,則為該應用程式分配一個引數uid所描述的Linux使用者ID
    s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags);
    s.userId = uid;
    // 在系統中儲存儲存值為uid的Linux使用者ID
    if (addUserIdLPw(uid, s, name)) {
        mSharedUsers.put(name, s);
        return s;
    }
    return null;
}

// 為應用程式分配一個Linux使用者ID
private PackageSetting getPackageLPw(String name, PackageSetting origPackage,
        String realName, SharedUserSetting sharedUser, File codePath, File resourcePath,
        String legacyNativeLibraryPathString, String primaryCpuAbiString,
        String secondaryCpuAbiString, int vc, int pkgFlags, int pkgPrivateFlags,
        UserHandle installUser, boolean add, boolean allowInstall) {
    // 根據name從mPackages中獲取PackageSetting物件
    PackageSetting p = mPackages.get(name);
    UserManagerService userManager = UserManagerService.getInstance();
    if (p != null) {
        . . .

        // 判斷已經存在的p物件的sharedUser是否和傳遞來的一致,若不一致說明物件p已不能描述應用的安裝資訊,需要重新分配
        if (p.sharedUser != sharedUser) {
            PackageManagerService.reportSettingsProblem(Log.WARN,
                    "Package " + name + " shared user changed from "
                    + (p.sharedUser != null ? p.sharedUser.name : "<nothing>")
                    + " to "
                    + (sharedUser != null ? sharedUser.name : "<nothing>")
                    + "; replacing with new");
            p = null;
        } else {
            // 判斷當前分配的應用是否是系統應用/私有特權應用
            p.pkgFlags |= pkgFlags & ApplicationInfo.FLAG_SYSTEM;
            p.pkgPrivateFlags |= pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
        }
    }
    // p為null說明要重新分配Linux使用者ID
    if (p == null) {
        if (origPackage != null) {
            // origPackage不為null說明該應用程式在系統中有一箇舊版本
            // 根據舊版本的應用程式建立一個新的PackageSetting物件
            p = new PackageSetting(origPackage.name, name, codePath, resourcePath,
                    legacyNativeLibraryPathString, primaryCpuAbiString, secondaryCpuAbiString,
                    null /* cpuAbiOverrideString */, vc, pkgFlags, pkgPrivateFlags);
            . . .
            // 把舊安裝包的userId分配給該應用程式
            p.appId = origPackage.appId;
            . . .
        } else {
            // origPackage為null,說明該應用是一個新安裝的應用程式
            // 根據傳遞過來的引數新建一個PackageSetting物件
            p = new PackageSetting(name, realName, codePath, resourcePath,
                    legacyNativeLibraryPathString, primaryCpuAbiString, secondaryCpuAbiString,
                    null /* cpuAbiOverrideString */, vc, pkgFlags, pkgPrivateFlags);
            p.setTimeStamp(codePath.lastModified());
            p.sharedUser = sharedUser;
            // 如果該應用不是系統應用,則新安裝後是停止執行狀態
            if ((pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) {
                List<UserInfo> users = getAllUsers();
                final int installUserId = installUser != null ? installUser.getIdentifier() : 0;
                if (users != null && allowInstall) {
                    for (UserInfo user : users) {
                        final boolean installed = installUser == null
                                || (installUserId == UserHandle.USER_ALL
                                    && !isAdbInstallDisallowed(userManager, user.id))
                                || installUserId == user.id;
                        // 設定該應用是停止執行狀態
                        p.setUserState(user.id, COMPONENT_ENABLED_STATE_DEFAULT,
                                installed,
                                true, // stopped,
                                true, // notLaunched
                                false, // hidden
                                null, null, null,
                                false, // blockUninstall
                                INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED, 0);
                        writePackageRestrictionsLPr(user.id);
                    }
                }
            }
            if (sharedUser != null) {
                // 如果sharedUser不為null,說明該新安裝應用程式設定了sharedUserId屬性,
                // 則把sharedUserId的userId分配給該應用
                p.appId = sharedUser.userId;
            } else {
                // 如果該新安裝應用程式沒有設定sharedUserId屬性,則需要為該應用程式分配新的Linux使用者ID
                
                // 根據name值從已經禁用的系統應用程式列表中獲取PackageSetting物件
                PackageSetting dis = mDisabledSysPackages.get(name);
                if (dis != null) {
                    // 如果dis不為null,說明該應用程式是一個已經禁用的系統應用,此時
                    // 只需用把它原來的userId分配給它即可
                    . . .
                    // 把原來的userId分配給該應用程式
                    p.appId = dis.appId;
                    . . .
                    // 在系統中儲存儲存值為uid的Linux使用者ID
                    addUserIdLPw(p.appId, p, name);
                } else {
                    // 如果dis為null,說明需要為該新安裝應用程式新分配一個Linux使用者ID
                    p.appId = newUserIdLPw(p);
                }
            }
        }
        if (p.appId < 0) {
            PackageManagerService.reportSettingsProblem(Log.WARN,
                    "Package " + name + " could not be assigned a valid uid");
            return null;
        }
        if (add) {
            // 呼叫addPackageSettingLPw方法把p儲存到mPackages中,
            // 表示它所描述的應用程式已經成功地安裝在系統中了
            addPackageSettingLPw(p, name, sharedUser);
        }
    } else {
        // p不為null說明不需要重新分配Linux使用者ID
        if (installUser != null && allowInstall) {
            // The caller has explicitly specified the user they want this
            // package installed for, and the package already exists.
            // Make sure it conforms to the new request.
            List<UserInfo> users = getAllUsers();
            if (users != null) {
                for (UserInfo user : users) {
                    if ((installUser.getIdentifier() == UserHandle.USER_ALL
                                && !isAdbInstallDisallowed(userManager, user.id))
                            || installUser.getIdentifier() == user.id) {
                        boolean installed = p.getInstalled(user.id);
                        if (!installed) {
                            p.setInstalled(true, user.id);
                            writePackageRestrictionsLPr(user.id);
                        }
                    }
                }
            }
        }
    }
    return p;
}

// 為該新安裝應用程式新分配一個Linux使用者ID
private int newUserIdLPw(Object obj) {
    // 獲取之前已經分配出去的Linux使用者ID的數量
    final int N = mUserIds.size();
    for (int i = mFirstAvailableUid; i < N; i++) {
        // 如果第i個物件值為null,說明值為Process.FIRST_APPLICATION_UID + i
        // 的Linux使用者ID還未分配出去,這裡就將該值分配給新安裝的應用程式使用
        if (mUserIds.get(i) == null) {
            mUserIds.set(i, obj);
            return Process.FIRST_APPLICATION_UID + i;
        }
    }

    // 如果已經沒有值可分配時,則返回-1
    if (N > (Process.LAST_APPLICATION_UID-Process.FIRST_APPLICATION_UID)) {
        return -1;
    }

    mUserIds.add(obj);
    // 如果不能在mUserIds中找到空閒的Linux使用者ID,且沒有超過分配限制,則把
    // Process.FIRST_APPLICATION_UID + N分配給新安裝應用程式使用
    return Process.FIRST_APPLICATION_UID + N;
}


到這裡,掃描目標資料夾之前的準備工作就介紹完了。主要是掃描並解析xml檔案將上一次的應用程式安裝資訊恢復完成。

2.掃描目標資料夾

先看下時序圖:

下面主要是掃描系統中的apk檔案了。繼續分析PMS的構造方法:

private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay";

public PackageManagerService(Context context, Installer installer,
        boolean factoryTest, boolean onlyCore) {

    . . .
    // 記錄開始掃描的時間
    long startTime = SystemClock.uptimeMillis();

    // 初始化掃描引數
    final int scanFlags = SCAN_NO_PATHS | SCAN_DEFER_DEX | SCAN_BOOTING | SCAN_INITIAL;

    // /system/framework目錄儲存的應用程式是資源型的。資源型的應用程式是用來打包資原始檔的,不包含有執行程式碼
    File frameworkDir = new File(Environment.getRootDirectory(), "framework");
    . . .
    // Collect vendor overlay packages.
    // (Do this before scanning any apps.)
    // 收集供應商提供的覆蓋應用程式
    File vendorOverlayDir = new File(VENDOR_OVERLAY_DIR);
    scanDirLI(vendorOverlayDir, PackageParser.PARSE_IS_SYSTEM
            | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags | SCAN_TRUSTED_OVERLAY, 0);

    // Find base frameworks (resource packages without code).
    scanDirLI(frameworkDir, PackageParser.PARSE_IS_SYSTEM
            | PackageParser.PARSE_IS_SYSTEM_DIR
            | PackageParser.PARSE_IS_PRIVILEGED,
            scanFlags | SCAN_NO_DEX, 0);

    // Collected privileged system packages./system/priv-app系統自帶應用程式
    final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
    scanDirLI(privilegedAppDir, PackageParser.PARSE_IS_SYSTEM
            | PackageParser.PARSE_IS_SYSTEM_DIR
            | PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0);

    // Collect ordinary system packages./system/app目錄儲存的是系統自帶的應用程式
    final File systemAppDir = new File(Environment.getRootDirectory(), "app");
    scanDirLI(systemAppDir, PackageParser.PARSE_IS_SYSTEM
            | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);

    // Collect all vendor packages.供應商提供的應用程式
    File vendorAppDir = new File("/vendor/app");
    try {
        vendorAppDir = vendorAppDir.getCanonicalFile();
    } catch (IOException e) {
        // failed to look up canonical path, continue with original one
    }
    scanDirLI(vendorAppDir, PackageParser.PARSE_IS_SYSTEM
            | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
    if (DEBUG_UPGRADE) Log.v(TAG, "Running installd update commands");
    mInstaller.moveFiles();

    // 刪除各種無效安裝包
    
    if (!mOnlyCore) {
        EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
                SystemClock.uptimeMillis());
        scanDirLI(mAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0);

        scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK,
                scanFlags | SCAN_REQUIRE_KNOWN, 0);
        . . .

    }

    // Now that we know all the packages we are keeping,
    // read and update their last usage times.
    mPackageUsage.readLP();// 更新各安裝包的上次使用時間

    EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SCAN_END,
            SystemClock.uptimeMillis());
    // 輸出掃描用的時間
    Slog.i(TAG, "Time to scan packages: "
            + ((SystemClock.uptimeMillis()-startTime)/1000f)
            + " seconds");
	. . .
}


上面這段程式碼主要是掃描系統中各相關目錄下的apk檔案。

// 掃描給定目錄下的apk檔案
private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {
    final File[] files = dir.listFiles();
    if (ArrayUtils.isEmpty(files)) {
        Log.d(TAG, "No files in app dir " + dir);
        return;
    }

    Log.d(TAG, "start scanDirLI:"+dir);
    // 使用多執行緒提高掃描速度
    int iMultitaskNum = SystemProperties.getInt("persist.pm.multitask", 6);
    Log.d(TAG, "max thread:" + iMultitaskNum);
    final MultiTaskDealer dealer = (iMultitaskNum > 1) ? MultiTaskDealer.startDealer(
            MultiTaskDealer.PACKAGEMANAGER_SCANER, iMultitaskNum) : null;

    for (File file : files) {
        final boolean isPackage = (isApkFile(file) || file.isDirectory())
                && !PackageInstallerService.isStageName(file.getName());
        if (!isPackage) {
            continue;
        }

        final File ref_file = file;
        final int ref_parseFlags = parseFlags;
        final int ref_scanFlags = scanFlags;
        final long ref_currentTime = currentTime;
        Runnable scanTask = new Runnable() {
            public void run() {
                try {
                    // 解析apk檔案
                    scanPackageLI(ref_file, ref_parseFlags | PackageParser.PARSE_MUST_BE_APK,
                            ref_scanFlags, ref_currentTime, null);
                } catch (PackageManagerException e) {
                    Slog.w(TAG, "Failed to parse " + ref_file + ": " + e.getMessage());

                    // 解析失敗則刪除無效的apk檔案
                    if ((ref_parseFlags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
                            e.error == PackageManager.INSTALL_FAILED_INVALID_APK) {
                        logCriticalInfo(Log.WARN, "Deleting invalid package at " + ref_file);
                        if (ref_file.isDirectory()) {
                            mInstaller.rmPackageDir(ref_file.getAbsolutePath());
                        } else {
                            ref_file.delete();
                        }
                    }
                }
            }
        };

        if (dealer != null)
            dealer.addTask(scanTask);
        else
            scanTask.run();
    }

    if (dealer != null)
        dealer.waitAll();
    Log.d(TAG, "end scanDirLI:"+dir);
}

// 解析apk檔案
private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,
        long currentTime, UserHandle user) throws PackageManagerException {
    if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanFile);
    parseFlags |= mDefParseFlags;
    PackageParser pp = new PackageParser();
    . . .

    final PackageParser.Package pkg;
    try {
        // 解析apk檔案後返回一個PackageParser.Package物件
        pkg = pp.parsePackage(scanFile, parseFlags);
    } catch (PackageParserException e) {
        throw PackageManagerException.from(e);
    }
    . . .

    // Set application objects path explicitly.
    pkg.applicationInfo.volumeUuid = pkg.volumeUuid;
    pkg.applicationInfo.setCodePath(pkg.codePath);
    pkg.applicationInfo.setBaseCodePath(pkg.baseCodePath);
    pkg.applicationInfo.setSplitCodePaths(pkg.splitCodePaths);
    pkg.applicationInfo.setResourcePath(resourcePath);
    pkg.applicationInfo.setBaseResourcePath(baseResourcePath);
    pkg.applicationInfo.setSplitResourcePaths(pkg.splitCodePaths);

    // 對剛解析apk檔案返回的pkg物件所描述的apk檔案進行安裝,以便獲取它的元件資訊,以及為它分配Linux使用者ID等。
    PackageParser.Package scannedPkg = scanPackageLI(pkg, parseFlags, scanFlags
            | SCAN_UPDATE_SIGNATURE, currentTime, user);

    return scannedPkg;
}


接下來先看PackageParser類的parsePackage方法:

// 解析apk檔案
public Package parsePackage(File packageFile, int flags) throws PackageParserException {
    if (packageFile.isDirectory()) {
        // 解析資料夾下的所有apk檔案
        return parseClusterPackage(packageFile, flags);
    } else {
        // 解析獨立apk檔案
        return parseMonolithicPackage(packageFile, flags);
    }
}

// 解析獨立apk檔案
public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
    if (mOnlyCoreApps) {
        // 根據傳遞來的引數生成一個PackageLite物件,並對它進行是否為核心應用判斷,若不是則拋異常
        final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);
        if (!lite.coreApp) {
            throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
                    "Not a coreApp: " + apkFile);
        }
    }

    final AssetManager assets = new AssetManager();
    try {
        // 解析apk檔案
        final Package pkg = parseBaseApk(apkFile, assets, flags);
        pkg.codePath = apkFile.getAbsolutePath();
        return pkg;
    } finally {
        IoUtils.closeQuietly(assets);
    }
}

private static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";
// 解析apk檔案
private Package parseBaseApk(File apkFile, AssetManager assets, int flags)
        throws PackageParserException {
    . . .

    Resources res = null;
    XmlResourceParser parser = null;
    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);
        parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);

        final String[] outError = new String[1];
        // 解析AndroidManifest.xml檔案
        final Package pkg = parseBaseApk(res, parser, flags, outError);
        if (pkg == null) {
            throw new PackageParserException(mParseError,
                    apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]);
        }
		. . .
        return pkg;

    } catch (PackageParserException e) {
        throw e;
    } catch (Exception e) {
        throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                "Failed to read manifest from " + apkPath, e);
    } finally {
        IoUtils.closeQuietly(parser);
    }
}

// 解析AndroidManifest.xml檔案
private Package parseBaseApk(Resources res, XmlResourceParser parser, int flags,
        String[] outError) throws XmlPullParserException, IOException {
    . . .
    try {
        // 解析apk的包名
        Pair<String, String> packageSplit = parsePackageSplitNames(parser, attrs, flags);
        pkgName = packageSplit.first;
        splitName = packageSplit.second;
    } catch (PackageParserException e) {
        mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
        return null;
    }

    final Package pkg = new Package(pkgName);
    boolean foundApp = false;

    // 解析apk的版本號、versionName、等
    . . .
    // 解析manifest標籤中的android:sharedUserId屬性
    String str = sa.getNonConfigurationString(
            com.android.internal.R.styleable.AndroidManifest_sharedUserId, 0);
    if (str != null && str.length() > 0) {
        . . .
         // 把該屬性值賦值給mSharedUserId
        pkg.mSharedUserId = str.intern();
        . . .
    }
    . . .
    sa.recycle();
    . . .

    // 迴圈解析manifest的各個子標籤
    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
            && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
        if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
            continue;
        }

        String tagName = parser.getName();
        if (tagName.equals("application")) {
            . . .
            // 解析application標籤,該標籤用來描述與應用程式元件相關的資訊
            if (!parseBaseApplication(pkg, res, parser, attrs, flags, outError)) {
                return null;
            }
        } else if (tagName.equals("uses-permission")) {
            // 解析uses-permission標籤,一個標籤對應一個資源訪問許可權
            if (!parseUsesPermission(pkg, res, parser, attrs)) {
                return null;
            }
        }
        . . .
        else {
            Slog.w(TAG, "Unknown element under <manifest>: " + parser.getName()
                    + " at " + mArchiveSourcePath + " "
                    + parser.getPositionDescription());
            XmlUtils.skipCurrentTag(parser);
            continue;
        }
    }
    . . .
    return pkg;
}

// 解析application標籤
private boolean parseBaseApplication(Package owner, Resources res,
        XmlPullParser parser, AttributeSet attrs, int flags, String[] outError)
    throws XmlPullParserException, IOException {
    // 解析application標籤的基本資訊,圖示、應用名等
    . . .


    final int innerDepth = parser.getDepth();
    int type;
    // 解析application的子標籤
    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
            && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
        if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
            continue;
        }

        String tagName = parser.getName();
		// 解析四大元件的標籤,把相應配置資訊都存放在owner的相應列表中
        if (tagName.equals("activity")) {
            Activity a = parseActivity(owner, res, parser, attrs, flags, outError, false,
                    owner.baseHardwareAccelerated);
            . . .
            owner.activities.add(a);
        } else if (tagName.equals("receiver")) {
            Activity a = parseActivity(owner, res, parser, attrs, flags, outError, true, false);
            . . .
            owner.receivers.add(a);
        } else if (tagName.equals("service")) {
            Service s = parseService(owner, res, parser, attrs, flags, outError);
            . . .
            owner.services.add(s);
        } else if (tagName.equals("provider")) {
            Provider p = parseProvider(owner, res, parser, attrs, flags, outError);
            . . .
            owner.providers.add(p);
        }
        . . .
        else {
            . . .
        }
    }
    . . .
    return true;
}


到這裡,PMS就解析完一個應用程式了,下面呼叫PMS類的scanPackageLI方法以便可以獲得前面所解析的應用程式的元件配置資訊,以及為這個應用程式分配Linux使用者ID:

// 獲得前面所解析的應用程式的元件配置資訊,以及為這個應用程式分配Linux使用者ID
private PackageParser.Package scanPackageLI(PackageParser.Package pkg, int parseFlags,
        int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
    boolean success = false;
    try {
        final PackageParser.Package res = scanPackageDirtyLI(pkg, parseFlags, scanFlags,
                currentTime, user);
        success = true;
        return res;
    } finally {
        if (!success && (scanFlags & SCAN_DELETE_DATA_ON_FAILURES) != 0) {
            removeDataDirsLI(pkg.volumeUuid, pkg.packageName);
        }
    }
}

// 獲得前面所解析的應用程式的元件配置資訊,以及為這個應用程式分配Linux使用者ID
private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, int parseFlags,
        int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
    final File scanFile = new File(pkg.codePath);
    . . .

    SharedUserSetting suid = null;
    PackageSetting pkgSetting = null;

    // 為引數pkg所描述的應用程式分配Linux使用者ID
    // writer
    synchronized (mPackages) {
        if (pkg.mSharedUserId != null) {
            // 如果該應用有sharedUserId屬性,則從mSettings中獲取要為它分配的共享uid
            suid = mSettings.getSharedUserLPw(pkg.mSharedUserId, 0, 0, true);
            // 判斷該共享uid是否合法
            . . .
        }
        . . .

        // 為應用程式分配一個Linux使用者ID
        pkgSetting = mSettings.getPackageLPw(pkg, origPackage, realName, suid, destCodeFile,
                destResourceFile, pkg.applicationInfo.nativeLibraryRootDir,
                pkg.applicationInfo.primaryCpuAbi,
                pkg.applicationInfo.secondaryCpuAbi,
                pkg.applicationInfo.flags, pkg.applicationInfo.privateFlags,
                user, false);
        . . .
    }
    . . .

    // writer
    synchronized (mPackages) {
        . . .
        // 把pkg所指向的一個Package物件儲存在mSettings中
        mPackages.put(pkg.applicationInfo.packageName, pkg);
        . . .

        // 把引數pkg所描述的應用程式的Content Provider元件配置資訊儲存在mProviders中
        int N = pkg.providers.size();
        StringBuilder r = null;
        int i;
        for (i=0; i<N; i++) {
            PackageParser.Provider p = pkg.providers.get(i);
            p.info.processName = fixProcessName(pkg.applicationInfo.processName,
                    p.info.processName, pkg.applicationInfo.uid);
            mProviders.addProvider(p);
            . . .
        }

        // 把引數pkg所描述的應用程式的Service元件配置資訊儲存在mServices中
        N = pkg.services.size();
        r = null;
        for (i=0; i<N; i++) {
            PackageParser.Service s = pkg.services.get(i);
            s.info.processName = fixProcessName(pkg.applicationInfo.processName,
                    s.info.processName, pkg.applicationInfo.uid);
            mServices.addService(s);
            . . .
        }

        // 把引數pkg所描述的應用程式的廣播接收者元件配置資訊儲存在mReceivers中
        N = pkg.receivers.size();
        r = null;
        for (i=0; i<N; i++) {
            PackageParser.Activity a = pkg.receivers.get(i);
            a.info.processName = fixProcessName(pkg.applicationInfo.processName,
                    a.info.processName, pkg.applicationInfo.uid);
            mReceivers.addActivity(a, "receiver");
            . . .
        }

        // 把引數pkg所描述的應用程式的Activity元件配置資訊儲存在mActivities中
        N = pkg.activities.size();
        r = null;
        for (i=0; i<N; i++) {
            PackageParser.Activity a = pkg.activities.get(i);
            a.info.processName = fixProcessName(pkg.applicationInfo.processName,
                    a.info.processName, pkg.applicationInfo.uid);
            mActivities.addActivity(a, "activity");
            . . .
        }

        . . .
    }

    return pkg;
}


通過呼叫getPackageLPw方法為該應用程式分配uid後,一個應用程式就成功地安裝到系統中了。到這裡PMS構造方法的掃描目標資料夾的工作就完成了。

3.掃描之後的工作

先看下時序圖:

當系統安裝完所有應用程式後,PMS的構造方法就會呼叫updatePermissionsLPw方法為前面所安裝的應用程式分配Linux使用者組ID,即授予它們所申請的資源訪問許可權,以及呼叫Settings類的writeLPr方法將這些應用程式的安裝資訊保持到本地檔案中:

public PackageManagerService(Context context, Installer installer,
        boolean factoryTest, boolean onlyCore) {

    . . .
    // 為申請了特定的資源訪問許可權的應用程式分配相應的Linux使用者組ID
    updatePermissionsLPw(null, null, StorageManager.UUID_PRIVATE_INTERNAL, updateFlags);
    . . .
    
    // 將前面獲取到的應用程式安裝資訊儲存在本地的一個配置檔案中,以便下一次再安裝這些應用程式時
    // 可以將需要保持一致的應用程式資訊恢復回來
    mSettings.writeLPr();
    . . .
    // Now after opening every single application zip, make sure they
    // are all flushed.  Not really needed, but keeps things nice and
    // tidy.
    Runtime.getRuntime().gc();

    // Expose private service for system components to use.
    LocalServices.addService(PackageManagerInternal.class, new PackageManagerInternalImpl());

}

// 為前面所安裝的應用程式分配Linux使用者組ID
private void updatePermissionsLPw(String changingPkg,
        PackageParser.Package pkgInfo, String replaceVolumeUuid, int flags) {
    . . .

    // Now update the permissions for all packages, in particular
    // replace the granted permissions of the system packages.
    if ((flags&UPDATE_PERMISSIONS_ALL) != 0) {
        for (PackageParser.Package pkg : mPackages.values()) {
            if (pkg != pkgInfo) {
                // Only replace for packages on requested volume
                final String volumeUuid = getVolumeUuidForPackage(pkg);
                final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_ALL) != 0)
                        && Objects.equals(replaceVolumeUuid, volumeUuid);
                // 為應用程式分配Linux使用者組ID,以便它們能獲得所申請的資源訪問許可權
                grantPermissionsLPw(pkg, replace, changingPkg);
            }
        }
    }
    . . .
}


這裡瞭解下AndroidManifest.xml檔案中的uses-permission標籤和Android應用程式的Linux使用者組ID的關係。假設一個應用程式需要使用攝像頭,那麼它就需要在它的配置檔案中新增許可權:

<uses-permission android:name="android.permissin.CARMERA" />


前面分析parsePackage方法時提到,PMS在解析配置檔案時會將這個uses-permission標籤中的android:name屬性的值“android.permission.CARMERA”取出來並儲存在requestedPermissions列表中。

PMS在啟動時會呼叫readPermissions方法來解析儲存