Android 应用程序安装流程解析

548 阅读20分钟

Android 系统基于 Linux 系统开发,而 Android 系统基于 Linux 系统的用户机制,为应用程序分配一个用户ID,应用程序还可以拥有若干个 Linux 用户组ID,通过用户ID和用户组ID,实现了对应用程序权限访问的机制;Android 系统的用户则是单独的一套机制,与 Linux 的用户机制不同,这里需要注意;

Android 系统启动时,system_server 进程会启动 PMS 服务,PMS 服务是应用程序的管理服务,PMS 的构造方法内会扫描预置 apk 文件的目录,对保存的应用程序进行安装,应用程序的安装过程主要有两个操作:

  • 扫描 apk 的 AndroidManifest.xml 文件,获取应用程序的组件、权限等信息;
  • 应用程序分配 Linux 用户ID和用户组ID,控制应用程序的权限访问;

由于 Android 系统每次启动时,都会重新安装一遍系统中的应用程序,但是有些应用程序的信息每次安装都需要保持一致,eg:应用程序的Linux用户ID,否则每次系统重启之后,表现会不一致,因此PMS在应用安装完成之后,需要将应用的安装信息保存下来,以便系统启动时可以恢复,在 PMS 内通过 Settings 类型的成员 mSettings 变量来实现安装信息的存储和恢复。

1、PMS 安装应用程序

PMS 的构造方法的主要进行三个操作:

  • 恢复系统安装的应用程序信息;
  • 系统预置的 apk 文件的进行安装和授权;
  • 保存应用程序的安装信息;

image.png

在开始分析之前,先回答两个问题。

为什么要保存和恢复应用程序安装的信息?

  • 因为在 Android 系统中,应用程序拥有一个 Linux 用户ID,该信息在系统中是唯一的,为了应用程序表现的一致性,所以在系统重启后,需要保证应用程序的用户ID等信息稳定。

什么是共享用户?

  • 包含一系列权限的用户,当应用程序申请某个共享用户,相当于拥有了该共享用户所拥有的权限,eg:android.uid.system 共享用户,包含一系列的系统权限,应用在 AndroidMainfest.xml 文件的 manifest 标签内添加如下配置 android:sharedUserId="android.uid.system", 即可申请该共享用户的所拥有的权限信息。

下面分析下 PMS 构造函数的代码

// PackageManagerService.java
public PackageManagerService(Injector injector ...) {
    ...
    // 管理应用程序的安装信息
    mSettings = injector.getSettings();
    ...
    // 添加系统默认的共享用户
    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);
    mSettings.addSharedUserLPw("android.uid.se", SE_UID,
            ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
    mSettings.addSharedUserLPw("android.uid.networkstack", NETWORKSTACK_UID,
            ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
    mSettings.addSharedUserLPw("android.uid.uwb", UWB_UID,
            ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
    ...
    // 目录:/data/app
    mAppInstallDir = new File(Environment.getDataDirectory(), "app");
    ...
    synchronized (mLock) {
        // 添加超时任务,超时时间 10 分钟
        Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);
        // 1、从 packages.xml 文件中恢复上次应用程序的安装信息
        mFirstBoot = !mSettings.readLPw(mInjector.getUserManagerInternal().getUsers());

        // 目录:/system/framework
        File frameworkDir = new File(Environment.getRootDirectory(), "framework");
        ...
        // 解析apk文件的解析器
        PackageParser2 packageParser = injector.getScanningCachingPackageParser();
        // 解析apk文件的线程池
        ExecutorService executorService = ParallelPackageParser.makeExecutorService();
        // 2、安装应用程序
        // 2.1、安装系统预置目录下 overlay 文件内的apk文件
        for (int i = mDirsToScanAsSystem.size() - 1; i >= 0; i--) {
            final ScanPartition partition = mDirsToScanAsSystem.get(i);
            if (partition.getOverlayFolder() == null) {
                continue;
            }
            // 安装 /oem、/odm、/vendor、/product、/system_ext 等目录下 overlay 文件内的apk文件
            scanDirTracedLI(partition.getOverlayFolder() ...);
        }
        // 2.2、安装 /system/framework 目录下的apk文件
        scanDirTracedLI(frameworkDir ...);

        // 2.3、安装系统预置目录下 priv-app 文件内的apk文件
        for (int i = 0, size = mDirsToScanAsSystem.size(); i < size; i++) {
            final ScanPartition partition = mDirsToScanAsSystem.get(i);
            if (partition.getPrivAppFolder() != null) {
                // 安装 /system、/odm、/vendor、/product、/system_ext 等目录下 priv-app 文件内的apk文件
                scanDirTracedLI(partition.getPrivAppFolder() ... packageParser, executorService);
            }
            // 安装 /system、/oem、/odm、/vendor、/product、/system_ext 等目录下 app 文件内的apk文件
            scanDirTracedLI(partition.getAppFolder() ...);
        }
        // 2.4、安装非系统预置目录下 /data/app 文件内的apk文件(通过AS/adb等方式安装的apk会在该目录下)
        if (!mOnlyCore) {
            // 安装 /data/app 目录下的apk文件
            scanDirTracedLI(mAppInstallDir ...);
        }
        ...
        // 3、删除未被应用程序使用的共享用户
        mSettings.pruneSharedUsersLPw();
        ...
        // 4、记录应用程序安装信息
        writeSettingsLPrTEMP();
        ...
    }
}

Settings 是用于维护应用程序安装信息的类,主要用于保存和恢复应用程序的安装信息,该类将数据保存到 /data/system/packages.xml文件内,文件内容请参考附录

在 PMS 的构造方法内,主要任务分为4个部分:

  1. Settings.addSharedUserLPw() 方法,系统会先记录一些默认的共享用户,如果共享用户没有应用程序使用,则会被删除;
  2. Settings.readLPw() 方法,该方法内部对 packages.xml 文件进行解析处理,将恢复的应用程序、共享用户等信息放入相应的集合内,用于后续的应用程序更新和保存;
  3. scanDirLI 方法扫描系统预置APK文件的路径,安装/更新/删除应用程序;
  4. Settings.writeLPr 方法,将应用程序、共享用户等信息从集合写入 packages.xml 文件内,用于系统重启时,安装信息的恢复操作;

2、恢复应用程序

Settings 恢复应用程序的安装信息时序图 Android apk 安装时序图(一).png

解析已安装的信息的过程通过 Settings 类进行,主要分为3个步骤:

  1. readPackageLPw 方法,读取已安装的应用程序信息;
  2. readeSharedUserLPw 方法,读取已安装的共享用户信息;
  3. loop 循环更新 mPendingPackages 变量,记录了使用共享用户ID作为用户ID的应用程序,设置共享用户信息和 Linux 用户ID;

2.1、解析已安装的信息

// Settings.java
// 记录应用程序配置信息的Map,Key:用户包名;Value:应用程序配置信息
final WatchedArrayMap<String, PackageSetting> mPackages;
// 记录使用共享用户ID作为用户ID的应用程序,因为共享用户ID有可能发生变化,所以在解析完成之后再设置共享应用程序的用户ID
private final WatchedArrayList<PackageSetting> mPendingPackages = new WatchedArrayList<>();

// 构造方法,初始化存放应用程序配置信息的文件和记录应用配置信息的集合
Settings(File dataDir ...) {
    // 记录应用程序配置信息的Map
    mPackages = new WatchedArrayMap<>();
    // 记录应用程序用户ID
    mAppIds = new WatchedArrayList<>();
    // 记录shared-user用户ID
    mOtherAppIds = new WatchedSparseArray<>();

    // 路径:/data/system
    mSystemDir = new File(dataDir, "system");
    mSystemDir.mkdirs();

    // 应用程序配置信息文件路径:/data/system/packages.xml
    mSettingsFilename = new File(mSystemDir, "packages.xml");

    // 应用程序配置信息备份文件路径:/data/system/packages-backup.xml
    mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml");
}

// readLPw 方恢复已安装的信息
// users 参数表示 Android 系统的用户,该信息记录在 /data/system/users/userslist.xml 文件,用户信息会根据用户id保存到对应的/data/system/users/(id).xml文件,此处的User表明应用程序属于哪个Android系统用户,用于账号间的数据隔离。
boolean readLPw(List<UserInfo> users) {
    FileInputStream str = null;
    // 如果上次写入已安装的信息出错,则备份文件会存在,系统从备份文件恢复已安装的信息
    // 文件路径:/data/system/packages-backup.xml
    if (mBackupSettingsFilename.exists()) {
        try {
            str = new FileInputStream(mBackupSettingsFilename);
            // 正常存储的场景,备份文件应该不存在,所以删除已损坏的 packages.xml 文件
            if (mSettingsFilename.exists()) mSettingsFilename.delete();
        } catch (java.io.IOException e) {
            ...
        }
    }
    ...

    try {
        // 如果备份文件不存在,说明上次存储正常,从 packages.xml 文件中恢复已安装的信息
        if (str == null) {
            ...
            str = new FileInputStream(mSettingsFilename);
        }
        // 解析 xml 文件
        final TypedXmlPullParser parser = Xml.resolvePullParser(str);
        ...

        // 开始循环解析各个 attr 内容,这里主要关注 pacakge 和 shared-user 属性
        int outerDepth = parser.getDepth();
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
            // 过滤不需要检测的内容
            ...

            String tagName = parser.getName();
            if (tagName.equals("package")) {
                // 解析 package 属性,主要是应用程序信息
                readPackageLPw(parser, users);
            } else if (tagName.equals("shared-user")) {
                // 解析 shared-user 属性,主要是共享用户信息
                readSharedUserLPw(parser, users);
            }
            ...
        }
        str.close();
    } catch (IOException | XmlPullParserException e) {
        ...
    }

    // 设置使用共享用户作为用户信息的应用程序,在解析package属性时,如果应用程序是共享用户,则会存储 mPendingPackages 集合内
    final int N = mPendingPackages.size();
    for (int i = 0; i < N; i++) {
        final PackageSetting p = mPendingPackages.get(i);
        final int sharedUserId = p.getSharedUserId();
        final Object idObj = getSettingLPr(sharedUserId);
        if (idObj instanceof SharedUserSetting) {
            final SharedUserSetting sharedUser = (SharedUserSetting) idObj;
            // 设置共享用户信息
            p.sharedUser = sharedUser;
            // 设置应用程序用户ID为共享用户ID,eg:framework-res.apk 等应用程序即属于共享用户应用程序
            p.appId = sharedUser.userId;
            // 存入已安装的应用程序集合 mPackages
            addPackageSettingLPw(p, sharedUser);
        }
        ...
    }
    mPendingPackages.clear();
    ...
    return true;
}

恢复已安装的信息,主要分为4个步骤:

  1. PMS构造方法内,创建 Settings 对象,初始化记录数据的文件和集合,用于管理应用程序等安装信息;
  2. 解析 package 属性,表示应用程序的安装信息;
  3. 解析 shared-user 属性,表示共享用户的安装信息;
  4. 更新应用程序依赖的共享用户,因为共享用户信息有可能会发生变化,所以在共享用户信息恢复之后,再设置应用程序的共享用户信息;

2.2、解析 package 属性

  • package 属性主要记录了应用程序的安装信息,具体内容请参考附录
  • updated-pacakge 属性同样记录了应用程序的安装信息,但主要是系统预置的较低版本的应用程序;
// 解析 pacakge 属性的内容
private void readPackageLPw(TypedXmlPullParser parser, List<UserInfo> users) {
    try {
        // 获取对应属性的值,eg:name、userId、sharedUserId等
        String name = parser.getAttributeValue(null, "name");
        int userId = parser.getAttributeInt(null, "userId", 0);
        int sharedUserId = parser.getAttributeInt(null, "sharedUserId", 0);
        String codePathStr = parser.getAttributeValue(null, "codePath");
        long versionCode = parser.getAttributeLong(null, "version", 0);
        String volumeUuid = parser.getAttributeValue(null, "volumeUuid");
        ...

        // name 和 codePathStr 字段不能为null,否则记录异常信息
        if (name == null) {
            // 记录异常信息
            ...
        } else if (codePathStr == null) {
            // 记录异常信息
            ...
        } else if (userId > 0) {
            // 创建应用程序对象,存储到集合 mPackages
            packageSetting = addPackageLPw(name.intern(), ... userId, ...);
            ...
        } else if (sharedUserId != 0) {
            if (sharedUserId > 0) {
                // 创建使用共享用户的应用程序信息对象,eg: framework-res.apk 无userId,有sharedUserId,记录到集合 mPendingPackages
                packageSetting = new PackageSetting(name.intern(), ... sharedUserId ...);
                mPendingPackages.add(packageSetting);
            } else {
                // sharedUserId 小于0非法,记录异常信息
                ...
            }
        }
    } catch (NumberFormatException e) {
        // 记录异常信息,解析文件出错
            ...
    }
    if (packageSetting != null) {
        ...
        // 解析enabled属性,判断应用是否启用,默认是启用状态
        final String enabledStr = parser.getAttributeValue(null, "enabled");
        if (enabledStr != null) {
            try {
                packageSetting.setEnabled(Integer.parseInt(enabledStr), 0 /* userId */, null);
            } catch (NumberFormatException e) {
                // 通过字符内容判断启用标识,默认 "enabled"
            }
        } else {
            packageSetting.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, 0, null);
        }

        ...
    } else {
        XmlUtils.skipCurrentTag(parser);
    }
}

// 创建/缓存应用程序信息(PackageSetting),PackageSetting 用于维护应用程序的安装信息
PackageSetting addPackageLPw(String name, String realName, File codePath ... int uid ...) {
    // 先从缓存中加载,如果已经解析,则直接返回
    PackageSetting p = mPackages.get(name);
    if (p != null) {
        // 如果userId相同,则直接返回;否则返回null;
        if (p.appId == uid) return p;
        return null;
    }
    // 创建 PackageSetting 对象
    p = new PackageSetting(name, realName, codePath ... 0(sharedUserId) ...);
    // 设置用户ID
    p.appId = uid;
    // 判断userId是否合法,如果合法,则添加到集合 mPackages
    if (registerExistingAppIdLPw(uid, p, name)) {
        // 将应用程序放入缓存
        mPackages.put(name, p);
        return p;
    }
    return null;
}

// 判断 userId/sharedUserId 的合法性,并记录共享用户和应用程序的ID信息
// mAppIds:记录应用程序的ID信息
// mOtherAppIds:记录共享用户的ID信息
private boolean registerExistingAppIdLPw(int appId, SettingBase obj, Object name) {
    // 用户 ID 有效范围在 19999 内(含19999)
    if (appId > Process.LAST_APPLICATION_UID) {
        return false;
    }

    // 普通用户ID范围(10000~19999);共享用户ID范围(10000内)
    if (appId >= Process.FIRST_APPLICATION_UID) {
        int size = mAppIds.size();
        final int index = appId - Process.FIRST_APPLICATION_UID;
        // 将数组填充到该索引所在的位置
        while (index >= size) {
            mAppIds.add(null);
            size++;
        }
        if (mAppIds.get(index) != null) {
            // 应用程序已经存在,不需要重复添加
            return false;
        }
        // 将应用程序信息放入集合
        mAppIds.set(index, obj);
    } else {
        // 共享用户已经存在,不需要重复添加
        if (mOtherAppIds.get(appId) != null) {
            return false;
        }
        // 将共享用户信息放入集合
        mOtherAppIds.put(appId, obj);
    }
    return true;
}

解析 package 属性,主要分为4个步骤:

  1. 解析 package 属性内各 attr 的值;
  2. 如果有 userId,则使用普通的用户ID,创建 PackageSetting 对象,记录到应用程序集合 mPackages;
  3. 如果有 sharedUserId,则使用共享用户ID,创建 PackageSetting 对象,记录到待确认应用程序集合 mPendingPackages;
  4. 存入集合 mPackages 时,判断用户ID的合法性;

2.3、解析 shread-user 属性

  • shared-user 属性主要记录共享用户的安装信息,具体内容请参考附录
// 解析 shared-user 属性的内容
private void readSharedUserLPw(TypedXmlPullParser parser, List<UserInfo> users) {
    ...
    SharedUserSetting su = null;
    {
        // 获取对应属性的值,eg:name、userId等
        String name = parser.getAttributeValue(null, "name");
        int userId = parser.getAttributeInt(null, "userId", 0);
        ...
        // name 为 null 或 userId 为0,属于无效信息,需要记录异常信息
        if (name == null) {
            ...
        } else if (userId == 0) {
            ...
        } else {
            // 创建共享用户
            if ((su = addSharedUserLPw(name.intern(), userId, ...)) == null) {
                // 创建失败,记录异常信息
            }
        }
    }

    if (su != null) {
        int outerDepth = parser.getDepth();
        int type;
        // 解析共享用户内的属性
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
            ...
            String tagName = parser.getName();
            if (tagName.equals("sigs")) {
                su.signatures.readXml(parser, mPastSignatures.untrackedStorage());
            } else if (tagName.equals("perms")) {
                // 解析共享用户拥有的权限
                readInstallPermissionsLPr(parser, su.getLegacyPermissionState(), users);
            } else {
                ...
            }
        }
    } else {
        XmlUtils.skipCurrentTag(parser);
    }
}

// 创建/缓存共享用户信息(SharedUserSetting),SharedUserSetting 用于维护共享用户的安装信息
SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) {
    SharedUserSetting s = mSharedUsers.get(name);
    if (s != null) {
        // 共享用户信息已存在,直接返回
        if (s.userId == uid) {
            return s;
        }
        return null;
    }
    // 创建 SharedUserSetting 对象
    s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags);
    // 设置用户ID
    s.userId = uid;
    if (registerExistingAppIdLPw(uid, s, name)) {
        // 将共享用户记录到集合 mSharedUsers
        mSharedUsers.put(name, s);
        return s;
    }
    return null;
}

解析 shared-user 属性,主要分为4个步骤:

  1. 解析 shared-user 属性内各 attr 的值;
  2. 如果有 name 且 userId 合法,创建 SharedUserSetting 对象,记录到共享用户集合 mSharedUsers;
  3. 解析共享用户拥有的权限、签名等信息,并存到 SharedUserSetting 对象内,拥有该共享用的应用程序同时拥有了相关权限和签名等信息;
  4. 存入集合 mSharedUsers 时,判断用户ID的合法性;

2.4、更新应用程序的共享用户

在遍历集合 mPendingPackags 时,通过 addPackageSettingLPw 方法,更新应用程序的共享用户信息;

// 更新使用共享用户的应用程序信息
private void addPackageSettingLPw(PackageSetting p, SharedUserSetting sharedUser) {
    // 将应用程序信息记录到集合 mPackages
    mPackages.put(p.name, p);
    if (sharedUser != null) {
        // 清理旧的共享用户记录的应用程序信息
        if (p.sharedUser != null && p.sharedUser != sharedUser) {
            p.sharedUser.removePackage(p);
        } else if (p.appId != sharedUser.userId) {
            // 记录异常信息
            ...
        }
        // 共享用户记录使用该用户的应用程序
        sharedUser.addPackage(p);
        // 设置应用程序新的的共享用户
        p.sharedUser = sharedUser;
        // 设置应用程序的用户ID为共享用户ID
        p.appId = sharedUser.userId;
    }
    ...
}

更新应用程序的共享用户信息过程,主要分为4个步骤:

  1. 将使用共享用户的应用程放入集合 mPackages;
  2. 清理旧的共享用户信息;
  3. 共享用户记录使用该用户的应用程序;
  4. 更新应用程序的用户信息;

3、安装应用程序

Android apk 安装时序图(二).png

通过时序图分析,安装应用程序,主要是解析apk文件,设置用户ID,并将信息存入 Settings,主要分为3个步骤:

  1. scanDirLI 方法,扫描预置apk文件的目录,逐个解析内部的apk文件或目录;
  2. 将解析文件的任务提交到线程池内,解析apk文件;
  3. 将解析的apk数据,创建 PackageSetting 对象(复用或生成用户ID)存入 Settings 内;

3.1、安装预置目录下的应用程序

在 PMS 的构造方法内,通过 scanDirTracedLI 方法开始扫描预置 apk 文件的目录;

// PackageManagerService.java
// 扫描预置apk文件的目录
private void scanDirTracedLI(File scanDir, final int parseFlags, int scanFlags,
        long currentTime, PackageParser2 packageParser, ExecutorService executorService) {
    ...
    // 真正的实现扫描的方法 scanDirLI
    scanDirLI(scanDir, parseFlags, scanFlags, currentTime, packageParser, executorService);
    ...
}

// 开始扫描预置apk文件的目录,将解析apk文件或目录的任务提交到线程池,解析完成之后安装应用程序
private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime,
        PackageParser2 packageParser, ExecutorService executorService) {
    final File[] files = scanDir.listFiles();
    ...

    // 将解析器和线程池封装
    ParallelPackageParser parallelPackageParser =
            new ParallelPackageParser(packageParser, executorService);

    int fileCount = 0;
    for (File file : files) {
        // 将apk文件或目录提交到线程池解析
        parallelPackageParser.submit(file, parseFlags);
        // 记录解析任务的数量,用于解析任务的获取
        fileCount++;
    }
    // 逐个处理解析任务的结果
    for (; fileCount > 0; fileCount--) {
        // 解析完成后,处理apk文件解析结果,当任务解析完成会放入阻塞队列,通知主线程处理,这里主线程需要等待因为PMS构造方法执行完成后,需要确保所有的应用程序已经被正确安装;
        ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();
        Throwable throwable = parseResult.throwable;

        if (throwable == null) {
            ...
            // 解析完成,开始安装应用程序
            addForInitLI(parseResult.parsedPackage, parseFlags, scanFlags,
                        currentTime, null);
        }
        ...
    }
}

安装应用程序的过程,主要分为3个步骤:

  1. 预置应用程序目录内的 apk 文件或目录,封装到解析任务内,然后提交到解析线程;
  2. 解析 apk 文件,并将解析结果放入完成队列 mQueue;
  3. 处理 apk 文件解析结果,完成应用程序的安装;

3.2、解析apk文件

apk 文件实际是一个 zip 压缩文件,压缩包内包含多个 class.dex、Androidmanifest.xml、resources.arsc 等类型的文件;

解析 apk 文件过程,主要是针对 apk 文件内的 AndroidManifest.xml 文件进行解析。

// ParallelPackageParser.java
// 将解析文件的任务提交到线程池
public void submit(File scanFile, int parseFlags) {
    mExecutorService.submit(() -> {
        ParseResult pr = new ParseResult();
        pr.scanFile = scanFile;
        // 解析处理,最终是调用 PackageParser2.parsePackage 方法
        pr.parsedPackage = parsePackage(scanFile, parseFlags);
        // 将解析结果放入队列内
        mQueue.put(pr);
    });
}
// 提取解析结果,mQueue 是阻塞队列(BlockingQueue),当队列内有元素是take返回,如果队列内无元素时,阻塞获取线程;
public ParseResult take() {
    ...
    return mQueue.take();
    ...
}

// PackageParser2.java
// 解析文件
public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches) {
    // 从缓存中加载解析数据
    if (useCaches && mCacher != null) {
        ParsedPackage parsed = mCacher.getCachedResult(packageFile, flags);
        if (parsed != null) {
            return parsed;
        }
    }

    ParseInput input = mSharedResult.get().reset();
    // 通过 ParsingPackageUtils.parsePackage 方法进行解析
    ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags);
    // 获取解析数据,解析数据保存到 ParsedPackage 对象
    ParsedPackage parsed = (ParsedPackage) result.getResult().hideAsParsed();

    // 将解析数据放入缓存
    if (mCacher != null) {
        mCacher.cacheResult(packageFile, flags, parsed);
    }
    return parsed;
}

// ParsingPackageUtils.java
public ParseResult<ParsingPackage> parsePackage(ParseInput input, File packageFile,
        int flags)
        throws PackageParserException {
    // 区分文件夹和文件,最终内部都是通过 parseBaseApk 方法对 apk 文件进行解析
    if (packageFile.isDirectory()) {
        // 解析文件夹内的apk文件
        return parseClusterPackage(input, packageFile, flags);
    } else {
        // 解析apk文件
        return parseMonolithicPackage(input, packageFile, flags);
    }
}

// 解析操作主要是针对 apk 内的 AndroidManifest.xml 文件进行解析
private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, File apkFile,
        String codePath, SplitAssetLoader assetLoader, int flags)
        throws PackageParserException {
    ...
    // 打开 AndroidManifest.xml 文件
    try (XmlResourceParser parser = assets.openXmlResourceParser(cookie, "AndroidManifest.xml")) {
        final Resources res = new Resources(assets, mDisplayMetrics, null);
        // pareseBaseApk 重载方法,内部通过 parseBaseApkTags 方法解析 APK 文件
        ParseResult<ParsingPackage> result = parseBaseApk(input, apkPath, codePath, res, parser, flags);
        ...
        return input.success(pkg);
    } catch (Exception e) {
    }
}

// 开始解析xml文件,解析不同的tag内容
private ParseResult<ParsingPackage> parseBaseApkTags(ParseInput input, ParsingPackage pkg ...) {
    // 解析应用程序申请的共享用户,即属性 android:sharedUserId 的内容
    ParseResult<ParsingPackage> sharedUserResult = parseSharedUser(input, pkg, sa);
    if (sharedUserResult.isError()) {
        return sharedUserResult;
    }
    ...
    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT ...) {
        ...
        if (TAG_APPLICATION.equals(tagName)) {
           // 解析 applitaion item 属性
           result = parseBaseApplication(input, pkg, res, parser, flags);
        } else {
           // 解析其他 item 属性
           result = parseBaseApkTag(tagName, input, pkg, res, parser, flags);
        }
        ...
    }
    return input.success(pkg);
}

// 解析 application 属性内的元素,主要是解析4大组件信息
private ParseResult<ParsingPackage> parseBaseApplication(ParseInput input ...) {
    ...
    // 循环解析 application 属性内的各组件内容
    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
            && (type != XmlPullParser.END_TAG
            || parser.getDepth() > depth)) {
        ...
        final ParseResult result;
        String tagName = parser.getName();
        boolean isActivity = false;
        switch (tagName) {
            // 解析 Activity/Receiver 组件
            case "activity":
                isActivity = true;
                // fall-through
            case "receiver":
                ParseResult<ParsedActivity> activityResult =
                        ParsedActivityUtils.parseActivityOrReceiver(... parser ...);

                if (activityResult.isSuccess()) {
                    ParsedActivity activity = activityResult.getResult();
                    if (isActivity) {
                        pkg.addActivity(activity);
                    } else {
                        pkg.addReceiver(activity);
                    }
                }

                result = activityResult;
                break;
            // 解析 Service 组件
            case "service":
                ParseResult<ParsedService> serviceResult =
                        ParsedServiceUtils.parseService(... parser ...);
                if (serviceResult.isSuccess()) {
                    ParsedService service = serviceResult.getResult();
                    pkg.addService(service);
                }

                result = serviceResult;
                break;
            // 解析 Provider 组件
            case "provider":
                ParseResult<ParsedProvider> providerResult =
                        ParsedProviderUtils.parseProvider(... parser ...);
                if (providerResult.isSuccess()) {
                    pkg.addProvider(providerResult.getResult());
                }

                result = providerResult;
                break;
            default:
                result = parseBaseAppChildTag(input, tagName, pkg, res, parser, flags);
                break;
        }

        if (result.isError()) {
            return input.error(result);
        }
    }
    ...
    return input.success(pkg);
}

解析apk文件的过程,主要分为4个步骤:

  1. 解析apk文件,主要是针对 AndroidManifest.xml 文件,由 ParsingPackageUtils 类管理解析操作;
  2. 解析 application 和其他类型属性的内容;
  3. 解析 application 内的组件元素内容,将解析的组件内容放入 ParsingPackage 类型的对象内,内部通过组件集合管理不同的组件信息;
  4. 解析其他属性,eg:permission等,并将其放入相应集合内;

3.3、添加或更新已安装的应用程序信息

parallelPackageParser.take() 返回解析结果,PMS开始进行应用程序的安装过程;

// PackageManagerServices.java
private AndroidPackage addForInitLI(ParsedPackage parsedPackage ...) {
    ...
    synchronized (mLock) {
        ...
        // 获取已安装的应用程序安装信息
        final PackageSetting installedPkgSetting = mSettings.getPackageLPr(
                parsedPackage.getPackageName());
        pkgSetting = originalPkgSetting == null ? installedPkgSetting : originalPkgSetting;
    ...
    // 对解析的apk文件内容进行转换,更新或创建应用程序安装信息
    final ScanResult scanResult = scanPackageNewLI(parsedPackage ...);
    if (scanResult.success) {
        synchronized (mLock) {
            ...
            // 创建应用程序的用户ID,并记录到 Settings 内
            appIdCreated = optimisticallyRegisterAppId(scanResult);
            // 如果有更新,则记录新的应用程序安装信息
            commitReconciledScanResultLocked(reconcileResult.get(pkgName), mUserManager.getUserIds());
            ...
        }
    }
    ...
    return scanResult.pkgSetting.pkg;
}

// 更新/创建应用程序的安装信息
private ScanResult scanPackageNewLI(ParsedPackage parsedPackage ...) {
    // 获取更新的应用程序信息
    final PackageSetting originalPkgSetting = getOriginalPackageLocked(parsedPackage,
            renamedPkgName);
    // 获取已安装的应用程序信息
    final PackageSetting pkgSetting = mSettings.getPackageLPr(parsedPackage.getPackageName());

    scanFlags = adjustScanFlags(scanFlags, pkgSetting, disabledPkgSetting, user, parsedPackage);
    synchronized (mLock) {
        final ScanRequest request = new ScanRequest(parsedPackage, sharedUserSetting ... );
        // 返回一个最新的应用程序安装信息结果
        return scanPackageOnlyLI(request, mInjector, mFactoryTest, currentTime);
    }
}

// 根据解析的应用程序数据或已安装的应用程序信息,创建或更新应用程序安装信息
static ScanResult scanPackageOnlyLI(ScanRequest request ...) {
    ...
    final boolean createNewPackage = (pkgSetting == null);
    // 应用程序首次安装,创建应用程序信息
    if (createNewPackage) {
        pkgSetting = Settings.createNewSetting(parsedPackage.getPackageName() ...);
    } else {
        // 已安装过应用程序信息,则通过已安装的应用程序信息更新解析的应用程序信息
        Settings.updatePackageSetting(pkgSetting ...);
    }
    ...
    return new ScanResult(request, true, pkgSetting ...);
}

// 更新应用程序安装信息
private AndroidPackage commitReconciledScanResultLocked(ReconciledPackage reconciledPkg, int[] allUsers) {
    ...
    if (result.existingSettingCopied) {
        // 如果存在已安装的应用程序,则使用已安装的应用程序更新解析的应用程序信息
        pkgSetting = request.pkgSetting;
        pkgSetting.updateFrom(result.pkgSetting);
    } else {
        // 如果不存在已安装的应用程序,则使用解析的应用程序信息
        pkgSetting = result.pkgSetting;
    }
    ...
    parsedPackage.setUid(pkgSetting.appId);
    final AndroidPackage pkg = parsedPackage.hideAsFinal();
    ...
    commitPackageSettings(pkg, oldPkg, pkgSetting, oldPkgSetting ...);
    ...
    return pkg;
}

// 应用程序安装信息更新覆盖旧的数据
private void commitPackageSettings(AndroidPackage pkg, AndroidPackage oldPkg ...) {
    ...
    synchronized (mLock) {
        // 将新的应用程序数据存入Settings配置管理器
        mSettings.insertPackageSettingLPw(pkgSetting, pkg);
        // 记录安装的应用程序
        mPackages.put(pkg.getPackageName(), pkg);
        // 由权限管理模块,更新应用程序的权限信息
        mPermissionManager.onPackageAdded(pkg, (scanFlags & SCAN_AS_INSTANT_APP) != 0, oldPkg);
    }
}

安装应用程序过程,主要分为4个步骤:

  1. 获取到apk文件的解析结果,将其转换为应用程序的安装信息;
  2. 根据已安装信息的状态决定,更新或创建应用程序的安装信息;
  3. 更新应用程序,将已安装的应用程序版本存入 Settings 的集合 mDisabledSysPackages,存储到 packages.xml 文件内对应的属性是 "updated-package";
  4. 将应用程序信息更新到 Settings 的集合 mPackages,存储到 packages.xml 文件内对应的属性是 "package";

4、记录应用程序的安装信息

应用程序安装结束之后,需要将安装/覆盖/权限/共享用户等信息写入packages.xml 文件内;

// Settings.java
void writeLPr() {
    // 备份 pacakges.xml 文件到 packages-backup.xml
    if (mSettingsFilename.exists()) {
        if (!mBackupSettingsFilename.exists()) {
            // 直接重命名文件的方式进行备份
            if (!mSettingsFilename.renameTo(mBackupSettingsFilename)) {
                return;
            }
        } else {
            mSettingsFilename.delete();
        }
    }
    ...
    final FileOutputStream fstr = new FileOutputStream(mSettingsFilename);
    final TypedXmlSerializer serializer = Xml.resolveSerializer(fstr);
    ...
    serializer.startTag(null, "packages");
    // 将 Settings 内记录的信息写入 xml 文件
    ...
    serializer.endTag(null, "packages");
    ...
    mBackupSettingsFilename.delete();
    ...
    return;
}

写入数据的过程,主要分为3个步骤:

  1. 将 packages.xml 文件重命名为 packages-backup.xml 文件,作为备份文件;
  2. 创建 xml 文件生成器,遍历 Settings 内的各个集合数据,写入到对应的标签内;
  3. 成功记录安装的信息之后,将备份文件删除;

至此,应用程序的安装流程结束,后续 AMS 启动应用程序,或 Launcher 显示应用的图标,即可从 PMS 查询应用程序的相关数据。

总结

PMS 在安装应用程序时,对应用程序的 AndroidManifes.xml 配置文件进行解析,配置文件内有很多信息,其中最重要的是 Android 四大组件信息,组件在配置文件内被正确的使用,才可以被 AMS 服务正常启动。

应用申请的权限或申请共享用户信息,实际是分配用户组ID或用户ID的过程;PMS 根据权限为应用程序分配 Linux 用户组ID,eg:读取联系人、使用摄像头等权限;而分配共享用户,则系统直接将共享用户设置给应用程序,所以应用程序拥有了共享用户的一系列权限;

Android 中的权限会有对应的用户组,在 packages.xml 对应的 permission 属性内有记录,当应用程序申请权限,相当于将应用程序授予了某个用户组的权限。这里其实借助的是 Linux 的用户管理机制,为A用户分配一个用户组,则该组所拥有的访问权限,A用户即可享有。

最后,分析 Android 应用程序的安装流程,当理解了应用程序安装的内容,再分析应用程序安装的具体过程就会更清晰,希望本篇文章可以帮助你更好的掌握应用程序的安装流程。

附录

Settings 保存的应用程序配置信息文件(存放路径:/data/system/packages.xml)

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<packages>
    <version sdkVersion="30" databaseVersion="3" fingerprint="qti/admin_jaymz/admin_jaymz:11/REWQ.20109.001/eng.demohh.20240711.090822:userdebug/test-keys" />
    <version volumeUuid="primary_physical" sdkVersion="30" databaseVersion="3" fingerprint="qti/admin_jaymz/admin_jaymz:11/REWQ.20109.001/eng.demohh.20240711.090822:userdebug/test-keys" />

    <!-- 权限集合 -->
    <permission-trees />

    <!-- 权限 -->
    <permissions>
        <item name="android.permission.REAL_GET_TASKS" package="android" protection="18" />
        <item name="android.permission.ACCESS_CACHE_FILESYSTEM" package="android" protection="18" />
        ...
    </permissions>

    <!-- 应用程序 -->
    <package name="com.android.providers.media" codePath="/system/priv-app/MediaProviderLegacy" nativeLibraryPath="/system/priv-app/MediaProviderLegacy/lib" publicFlags="944258629" privateFlags="-1946152952" ft="11e8f7d4c00" it="11e8f7d4c00" ut="11e8f7d4c00" version="1024" sharedUserId="10010" isOrphaned="true">
        <sigs count="1" schemeVersion="3">
            <cert index="4" key="390kajkgnjkgdjahthkjgh" />
        </sigs>
        <perms>
            <item name="android.permission.ACCESS_CACHE_FILESYSTEM" granted="true" flags="0" />
            <item name="android.permission.MANAGE_EXTERNAL_STORAGE" granted="true" flags="0" />
            <item name="android.permission.WRITE_MEDIA_STORAGE" granted="true" flags="0" />
            ...
        </perms>
        <proper-signing-keyset identifier="3" />
    </package>
    <package name="android" codePath="/system/framework/framework-res.apk" nativeLibraryPath="/system/lib64/framework-res" primaryCpuAbi="arm64-v8a" publicFlags="810073609" privateFlags="-1945104280" ft="11e8f7d4c00" it="11e8f7d4c00" ut="11e8f7d4c00" version="30" sharedUserId="1000" isOrphaned="true">
        <sigs count="1" schemeVersion="3">
            <cert index="2" />
        </sigs>
        <perms>
            <item name="android.permission.REAL_GET_TASKS" granted="true" flags="0" />
            ...
        </perms>
        <proper-signing-keyset identifier="2" />
    </package>
    <package name="com.example.test.demo" codePath="/data/app/~~9wVilhPbAuqqaPB8DdXDKw==/com.example.test.demo-o19apShZNSh7CKSkJ5gZig==" nativeLibraryPath="/data/app/~~9wVilhPbAuqqaPB8DdXDKw==/com.example.test.demo-o19apShZNSh7CKSkJ5gZig==/lib" primaryCpuAbi="arm64-v8a" publicFlags="541605831" privateFlags="-1936715520" ft="190b9a8f868" it="11e8f7d4c00" ut="190b9a8fc14" version="601" userId="10199">
        <sigs count="1" schemeVersion="2">
            <cert index="2" />
        </sigs>
        <perms>
            <item name="android.permission.SYSTEM_ALERT_WINDOW" granted="true" flags="0" />
            ...
        </perms>
        <proper-signing-keyset identifier="2" />
    </package>
    
    <!-- 过期的应用程序 -->
    <updated-package name="com.example.test.demo" codePath="/system/app/TestDemo" ft="11e8f7d4c00" it="11e8f7d4c00" ut="11e8f7d4c00" version="596" nativeLibraryPath="/system/app/TestDemo/lib" primaryCpuAbi="arm64-v8a" userId="10199">
        <perms>
            <item name="android.permission.SYSTEM_ALERT_WINDOW" granted="true" flags="0" />
            ...
        </perms>
    </updated-package>

    <!-- shared-user 信息 -->
    <shared-user name="android.uid.systemui" userId="10058">
        <sigs count="1" schemeVersion="3">
            <cert index="2" />
        </sigs>
        <perms>
            <item name="android.permission.REAL_GET_TASKS" granted="true" flags="0" />
            <item name="android.permission.REMOTE_AUDIO_PLAYBACK" granted="true" flags="0" />
            ...
        </perms>
    </shared-user>
    ...
    <shared-user name="android.uid.system" userId="1000">
        <sigs count="1" schemeVersion="3">
            <cert index="2" />
        </sigs>
        <perms>
            <item name="android.permission.REAL_GET_TASKS" granted="true" flags="0" />
            ...
        </perms>
    </shared-user>
</packages>