PKMS 1

作者: simplehych | 来源:发表于2019-11-11 13:49 被阅读0次

    本文仅供自己记录学习使用。

    /frameworks/base/core/java/android/content/pm/IPackageManager.aidl
    /frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

    摘要

    PackageManagerService,简称 PKMS,是 Android 系统中核心服务之一,管理着所有跟 package 相关的工作,常见的比如应用的安装 adb install 和卸载 adb uninstall

    PKMS 通信方式

    上图主要展示了PackageManagerService及客户端的通信方式,以及相关类的继承关系。

    1. PKMS的客户端,必须通过PackageManager才能发送请求给PKMS,可以认为PackageManager是PKMS的代理对象。
    2. PackageManager 是一个抽象类,实际的实现类是ApplicationPackageManager。当客户端利用Context的getPackagerManager函数获取PackageManager时,获取的就是ApplicationPackageManager。
    3. ApplicationPackageManager与PKMS的交互实际上是基于Binder通信的,只不过AIDL文件封装了实现细节。

    ApplicationManager构造函数如下

    ApplicationPackageManager(ContextImpl context,
            IPackageManager pm) {
        mContext = context;
        mPM = pm;
    }
    

    容易看出ApplicationPackageManager持有了IPackageManager对象。
    IPackageManager 对象是怎么得到的?
    我们以ActivityThread.java中handleBindApplication函数中的一小段代码为例:

    .......
    try {
        //getPackageManager函数将获取到IPackageManager对象
        ii = new ApplicationPackageManager(null, getPackageManager())
                .getInstrumentationInfo(data.instrumentationName, 0);
    } catch (PackageManager.NameNotFoundException e) {
        .......
    }
    
    public static IPackageManager getPackageManager() {
        if (sPackageManager != null) {
            return sPackageManager;
        }
    
        //在之前的博客介绍Java层的Binder通信时,我们知道这里最终将会返回BinderProxy对象
        IBinder b = ServiceManager.getService("package");
        //将BinderProxy对象,转换成实际的业务代理对象
        sPackageManager = IPackageManager.Stub.asInterface(b);
    }
    

    IPackageManager是PKMS的业务代理,其中服务端和客户端的业务函数由 /framework/base/core/java/android/content/pm/IPackageManager.aidl 文件定义。PKMS继承自IPackageManager.Stub。其实上也是基于Binder的服务端。

    关键字

    UID:user id
    GID:user group id

    UID和GID,均与Linux系统中进程的权限管理有关

    SharedUserSettings

    PKMS 流程

    PKMS的启动

    PKMS由SystemService在开机的时候启动,在SystemServer.java的run方法中:

    private void run() {
        .......
        try {
            .....
            startBootstrapServices();
            .....
            startOtherServices();
            .....
        } ......
    }
    
    private void startBootstrapServices() {
        // Wait for installd to finish starting up so that it has a chance to
        // create critical directories such as /data/user with the appropriate
        // permissions.  We need this to complete before we initialize other services.
        Installer installer = mSystemServiceManager.startService(Installer.class);
        .........
        //根据系统属性,决定是否为加密设备加密
        String cryptState = SystemProperties.get("vold.decrypt");
        if (ENCRYPTING_STATE.equals(cryptState)) {
            Slog.w(TAG, "Detected encryption in progress - only parsing core apps");
            mOnlyCore = true;
        } else if (ENCRYPTED_STATE.equals(cryptState)) {
            Slog.w(TAG, "Device encrypted - only parsing core apps");
            mOnlyCore = true;
        }
    
        //调用PKMS的main函数
        mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
                mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
    
        //判断是否为初次启动
        mFirstBoot = mPackageManagerService.isFirstBoot();
        mPackageManager = mSystemContext.getPackageManager();
        ..........
        // Manages A/B OTA dexopting. This is a bootstrap service as we need it to rename
        // A/B artifacts after boot, before anything else might touch/need them.
        // Note: this isn't needed during decryption (we don't have /data anyways).
        if (!mOnlyCore) {
            boolean disableOtaDexopt = SystemProperties.getBoolean("config.disable_otadexopt",
                    false);
            if (!disableOtaDexopt) {
                try {
                    //启动OtaDexoptService也需要PackageMangerService的参与
                    OtaDexoptService.main(mSystemContext, mPackageManagerService);
                }......
            }
        }
    }
    

    从上面的代码,大致可以看出SystemServer在startBootstrapServices函数中启动PKMS,在非加密机器中PKMS还将参与到OtaDexoptService中。

    在SystemServer的StartOtherService中,

    private void startOtherServices() {
        ......
        if (!mOnlyCore) {
            ........
            try {
                //将调用performDexOpt:Performs dexopt on the set of packages
                mPackageManagerService.updatePackagesIfNeeded();
            }.......
            ........
            try {
                //执行Fstrim,执行磁盘维护操作,未看到详细的资料
                //可能类似于TRIM技术,将标记为删除的文件,彻底从硬盘上移除
                //而不是等到写入时再移除,目的是提高写入时效率
                mPackageManagerService.performFstrimIfNeeded();
            }.........
            .......
            try {
                mPackageManagerService.systemReady();
            }........
            .......
        }
    }
    

    从上面的代码可以看出,PKMS启动后将参与一些系统优化工作,然后调用SystemReady函数通知系统进入到就绪状态。

    PKMS的 main 函数

    public static PackageManagerService main(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
        // Self-check for initial settings.
        //此处主要检查系统属性
        PackageManagerServiceCompilerMapping.checkProperties();
    
        //调用构造函数,其中factoryTest决定是否是测试版本,onlyCore决定是否只解析系统目录,我们先假定均为false
        PackageManagerService m = new PackageManagerService(context, installer,
                factoryTest, onlyCore);
    
        //根据条件,enable一些app,针对多用户场景
        m.enableSystemUserPackages();
    
        // Disable any carrier apps. We do this very early in boot to prevent the apps from being
        // disabled after already being started.
        //关闭一些运营商应用,直到被授权
        CarrierAppUtils.disableCarrierAppsUntilPrivileged(context.getOpPackageName(), m,
                UserHandle.USER_SYSTEM);
    
        //利用Binder通信,将自己注册到ServiceManager进程中
        ServiceManager.addService("package", m);
        return m;
    }
    
    1. checkProperties,检查系统属性,然后抛出异常
    2. enableSystemUserPackages,针对的是system user和primary user 分离的特殊场景,按条件为system user安装一些应用
    3. disableCarrierAppsUntilPrivileged 禁止运营商应用,根据名称,取出对应的ApplicationInfo

    整个system_server 进程启动过过程,涉及PKMS服务的主要几个动作如下:

    1. PKMS.main()
    2. PKMS.performBootDexOpt()
    3. PKMS.systemReady()

    PKMS 的构造函数

    主要功能:
    扫描Android系统中几个目标文件夹中的APK,从而建立合适的数据结构以管理诸如Package信息、四大组件信息、权限信息等各种信息。查看是如何解析和管理手机中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);
        ...................
    }
    
    Setting的构造函数

    刚进入到PKMS的构造函数,我们就遇到了Setting对象,及一大堆的addShareUserLPw调用。
    我们看看Setting的构造函数:

    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);
        // /data/system/packages.xml,系统安装的APK信息
        mSettingsFilename = new File(mSystemDir, "packages.xml");
        // /data/system/packages-backup.xml,备份
        mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml");
        // /data/system/packages.list,非系统自带的APK信息,即UID大于10000的APK
        mPackageListFilename = new File(mSystemDir, "packages.list");
        FileUtils.setPermissions(mPackageListFilename, 0640, SYSTEM_UID, PACKAGE_INFO_GID);
    
        // /config/sdcardfs
        final File kernelDir = new File("/config/sdcardfs");
        mKernelMappingFilename = kernelDir.exists() ? kernelDir : null;
    
        // Deprecated: Needed for migration
        // /data/system/packages-stopped.xml,强行停止运行的APK信息,backup备份文件
        mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml");
        mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml");
    }
    

    Setting构造函数主要工作是创建一些目录和文件,并配置响应的权限。其中:

    1. PKMS 扫描完目标文件夹后,会创建 /data/system/packages.xml。当系统进行程序安装、卸载和更新操作时,均会更新该文件
    2. /data/system/packages.list,用于描述系统中存在的所有非系统自带的APK信息。当这些APK有变化时,PKMS会更新该文件。
    3. /data/system/packages-stopped.xml,用于记录被用户强行停止的应用的Package信息。

    Settings主要用于管理Android系统运行过程中的一些设置信息。

    继续跟进 Setting#addShareUserLPW 函数:

    mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
            ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
    
    // 以上面代码调用为例:参数说明
    // name - "android.uid.system"
    // uid - Process.SYSTEM_UID (1000)
    // pkgFlags - ApplicationInfo.FLAG_SYSTEM(1<<0)
    // pkgPrivateFlags - ApplicationInfo.PRIVATE_FLAG_PRIVILEGED(1<<3)
    
    // 其中 name 和 uid 一一对应。
    
    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;
        }
        
        // 根据上面四个参数,构造出 ShareUserSetting 对象
        s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags);
        s.userId = uid;
    
        if (addUserIdLPw(uid, s, name)) {
            // 按照 <name, ShareUserSetting> 存入到 mSharedUsers 中
            mSharedUsers.put(name, s);
            return s;
        }
        return null;
    }
    
    
    private boolean addUserIdLPw(int uid, Object obj, Object name) {
        if (uid > Process.LAST_APPLICATION_UID) {  // 19999
            return false;
        }
    
        if (uid >= Process.FIRST_APPLICATION_UID) { // 10000 普通应用
            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 { // uid < 10000 的应用,应该都属于系统应用
            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 结构

    PKMS 根据参数构建出SharedUserSetting对象,可以通过 name 和 uid 两个维度来引用创建出的对象。
    mSharedUsers是一个 Map 对象,name 作为索引管理 ShareUserSetting 对象。
    mUserIds和mOtherUserIds,都是利用 uid 作为索引管理 ShareUserSetting 对象,不同的是 mUserIds 是 ArrayList,利用 uid 为下标 (index处理-10000,从0开始);mOtherUserIds 是 SparseArray,利用 uid 作为键值。

    SharedUserSettings 类

    持有一组PackageSetting,最终都继承 SettingBase。

    举例
    /frameworks/base/packages/SettingsProvider/AndroidManifest.xml

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
            package="com.android.providers.settings"
            coreApp="true"
            // 注意此处,在前文的PKMS为其创建了SharedUserSetting
            android:sharedUserId="android.uid.system">
    

    在xml文件汇总,声明了一个名为 android:sharedUserId的属性,其值为“android.uid.system”。

    声明了同一种sharedUserId的APK可共享彼此的数据,并且运行在同一个进程。

    声明特定的sharedUserId,该APK所在的进程将被赋予指定UID对应的权限

    Android系统中的UID标识用户ID,GID表示用户组ID,均与Linux系统中进程的权限管理有关。

    一般来说,每一个进程都会有一个对应的UID,针对不同的UID可以有不同的权限;同时,每个进程也可以分属不同的用户组,即有对应的GID,针对不同的GID也可以有不同的权限。

    通过上面的例子和UID/GID的用途,SharedUserSetting的作用就可以提现出来了:
    SharedUserSetting将 “android:sharedUserId” 属性的名称和对应的uid关联起来,同时持有所有声明相同sharedUserId的APK的PackageSetting,因此PKMS可以为同一类APK设置相同的权限。

    除了在AndroidManifest.xml 中声明sharedUserId外,APK在编译时还必须使用对应的证书签名。例如在对应的Android.mk中声明 LOCAL_CERTIFICATE := platform,这样就具有系统权限了

    /frameworks/base/packages/SettingsProvider/Android.mk

    ...
    LOCAL_PACKAGE_NAME := SettingsProvider
    LOCAL_CERTIFICATE := platform
    LOCAL_PRIVILEGED_MODULE := true
    ...
    
    SharedUserSetting相关的类图
    ShareUserSetting

    如上图所示,Settings 对象中有多个 SharedUserSetting 对象,每个 SharedUserSetting 对象持有多个PackageSettings对象。

    从继承关系来看,SharedUserSetting和PackageSetting对象,最终都将继承SettingBase对象。

    从上图可以看出,SettingBase对象持有PermissionState对象,用于表示可用的权限。
    据此推测,PackageSetting持有单个Package独有的权限;SharedUserSetting持有一组Package共有的权限。

    /frameworks/base/core/java/android/os/SystemProperties.java

    /frameworks/base/core/jni/AndroidRuntime.cpp 在其中注册jni
    /frameworks/base/core/jni/android_os_SystemProperties.cpp

    读取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();
    ..........
    

    主要看一下SystemConfig相关的函数。

    SystemConfig() {
        // Read configuration from system
        // /system/etc/sysconfig
        readPermissions(Environment.buildPath(
                Environment.getRootDirectory(), "etc", "sysconfig"), ALLOW_ALL);
        // Read configuration from the old permissions dir
        // /system/etc/permissions
        readPermissions(Environment.buildPath(
                Environment.getRootDirectory(), "etc", "permissions"), ALLOW_ALL);
        // Allow Vendor to customize system configs around libs, features, permissions and apps
        int vendorPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_PERMISSIONS |
                ALLOW_APP_CONFIGS;
        // /vender/etc/sysconfig
        readPermissions(Environment.buildPath(
                Environment.getVendorDirectory(), "etc", "sysconfig"), vendorPermissionFlag);
        // /vender/etc/permissions
        readPermissions(Environment.buildPath(
                Environment.getVendorDirectory(), "etc", "permissions"), vendorPermissionFlag);
        // Allow ODM to customize system configs around libs, features and apps
        int odmPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_APP_CONFIGS;
        // /odm/etc/sysconfig
        readPermissions(Environment.buildPath(
                Environment.getOdmDirectory(), "etc", "sysconfig"), odmPermissionFlag);
        // /odm/etc/permissions
        readPermissions(Environment.buildPath(
                Environment.getOdmDirectory(), "etc", "permissions"), odmPermissionFlag);
        // Only allow OEM to customize features
        // /oem/etc/sysconfig
        readPermissions(Environment.buildPath(
                Environment.getOemDirectory(), "etc", "sysconfig"), ALLOW_FEATURES);
        // /oem/etc/permissions
        readPermissions(Environment.buildPath(
                Environment.getOemDirectory(), "etc", "permissions"), ALLOW_FEATURES);
    }
    

    创建SystemConfig时,将从不同的文件夹的etc目录下读取权限信息,包括 /system、/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
            // 跳过最后读 platform.xml
            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中的权限信息,实际的手机上,可能没有代码中指定的所有目录,例如/oem等,但是/system/etc/permission目录一般都是有的。

    xml 文件内容

    platform.xml 优先级最高,

    <?xml version="1.0" encoding="utf-8"?>
    <!-- This file is used to define the mappings between lower-level system
         user and group IDs and the higher-level permission names managed
         by the platform.
    
         Be VERY careful when editing this file!  Mistakes made here can open
         big security holes.
    -->
    
    <permissions>
    <!-- The following tags are associating low-level group IDs with
             permission names.  By specifying such a mapping, you are saying
             that any application process granted the given permission will
             also be running with the given group ID attached to its process,
             so it can perform any filesystem (read, write, execute) operations
             allowed for that group. -->
        <!--建立权限名与gid的映射关系,一般与需要和读写底层设备的进程会声明这些权限
            这些权限涉及和Linux内核交互,所以需要在底层权限(由不同的用户的用户组界定)和Android层权限(由不同的字符串界定)之间建立映射关系 -->
        <permission name="android.permission.BLUETOOTH_ADMIN" >
            <group gid="net_bt_admin" />
        </permission>
    
        <permission name="android.permission.BLUETOOTH" >
            <group gid="net_bt" />
        </permission>
        ........
    
        <!-- The following tags are assigning high-level permissions to specific
             user IDs.  These are used to allow specific core system users to
             perform the given operations with the higher-level framework.  For
             example, we give a wide variety of permissions to the shell user
             since that is the user the adb shell runs under and developers and
             others should have a fairly open environment in which to
             interact with the system. -->
        <!--赋予对应uid相应的权限。例如下面第一行,uid为media时,那么就赋予其修改Audio设置的权限
            其实就是将uid加入到对应的用户组中-->
        <assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="media" />
        <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="media" />
        ............
    
        <!-- This is a list of all the libraries available for application
            code to link against. -->
        <!--系统提供的java库,APK运行时可以链接这些库-->
        <library name="android.test.runner"
            file="/system/framework/android.test.runner.jar" />
        ..........
    
        <!-- These are the standard packages that are white-listed to always have internet
             access while in power save mode, even if they aren't in the foreground. -->
        <!--指定进程在省电模式下(非Idle态)仍可访问网络)-->
        <allow-in-power-save-except-idle package="com.android.providers.downloads" />
    
        <!-- Whitelist of what components are permitted as backup data transports.  The
             'service' attribute here is a flattened ComponentName string. -->
        <!--指定服务可以传输备份数据-->
        <backup-transport-whitelisted-service
            service="android/com.android.internal.backup.LocalTransportService" />
        <backup-transport-whitelisted-service
            service="com.google.android.gms/.backup.BackupTransportService" />
        <backup-transport-whitelisted-service
            service="com.google.android.gms/.backup.component.D2dTransportService" />
    </permissions>
    

    主要作用是:

    1. permission和group字段用于建立 Linux层GID 和 Android层permission 字段之间的映射关系;
    2. assign-permission 用于指定的uid赋予相应的权限;
    3. library字段用于可链接的指定系统库
    4. allow-in-power-save-except-idle用于指定进程在省电模式(非idle)仍可上网
    5. backup-transport-whitelisted-service 用于指定服务具有传输备份数据的权利

    一般 .xml,这里以 android.hardware.bluetooth.xml 为例:

    <?xml version="1.0" encoding="utf-8"?>
    <!-- Adds the feature indicating support for the Bluetooth API -->
    <permissions>
        <feature name="android.hardware.bluetooth" />
    </permissions>
    

    这种类型的xml文件包含了一些feature标签,用于描述一个手持终端应该支持的硬件特性,例如上面的feature表示一个终端应该支持蓝牙功能。

    不同设备支持的硬件特性不一样。
    同一套代码可能需要适配不同的设备,此时通过定义mk文件,可以在编一阶段根据当前硬件平台的配置信息,复制相关的xml文件到 /system/etc/permission目录下。

    readPermissionFromXml

    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<String> perms = mSystemPermissions.get(uid);
                    if (perms == null) {
                       perms = new ArraySet<String>();
                        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 {
                    ......
                }
            }
            .......
        }
    }
    
    SystemConfig数据结构

    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(); 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);
                mSettings.mPermissions.put(perm.name, bp);
            }
            if (perm.gids != null) {
                //将android权限和gid关联的信息,存入到mSettings对象的mPermissions中
                //boolean型的perUser,也是从xml中解析出来的
                bp.setGids(perm.gids, perm.perUser);
            }
        }
    
        //取出systemConfig对象中的链接库信息,保存到PKMS中
        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));
        }
    
        //此处加载签名策略
        mFoundPolicyFile = SELinuxMMAC.readInstallPolicy();
        ...........
    

    readInstallPolicy()

    /**
    * 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<Policy> 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”:

    <?xml version="1.0" encoding="utf-8"?>
    <policy>
    <!--
        ..........
        * valid stanzas can take one of the following forms:
    
        // single cert protecting seinfo
        <signer signature="@PLATFORM" >
            <seinfo value="platform" />
        </signer>
    
        // multiple certs protecting seinfo (all contained certs must match)
        <signer>
            <cert signature="@PLATFORM1"/>
            <cert signature="@PLATFORM2"/>
            <seinfo value="platform" />
        </signer>
    
        // single cert protecting explicitly named app
        <signer signature="@PLATFORM" >
            <package name="com.android.foo">
                <seinfo value="bar" />
            </package>
        </signer>
    
        // multiple certs protecting explicitly named app (all certs must match)
        <signer>
            <cert signature="@PLATFORM1"/>
            <cert signature="@PLATFORM2"/>
            <package name="com.android.foo">
                <seinfo value="bar" />
            </package>
        </signer>
    -->
    
        <!-- Platform dev key in AOSP -->
        <signer signature="@PLATFORM" >
            <seinfo value="platform" />
        </signer>
    
    </policy>
    

    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将被实际的签名信息替换。

    了解mac_permissions.xml的内容后,最后再看看解析xml使用的函数:

    readSignerOrThrow()

    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 扫描目录下的Package

    private void scanDirTracedLI(File dir, final int parseFlags, int scanFlags, long currentTime) {
        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + dir.getAbsolutePath() + "]");
        try {
            scanDirLI(dir, parseFlags, scanFlags, currentTime);
        } finally {
            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
        }
    }
    
    private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {
        final File[] files = dir.listFiles();
    
        // Submit files for parsing in parallel
        int fileCount = 0;
        for (File file : files) {
            final boolean isPackage = (isApkFile(file) || file.isDirectory())
                    && !PackageInstallerService.isStageName(file.getName());
            if (!isPackage) {
                // Ignore entries which are not packages
                continue;
            }
            // 内部
            parallelPackageParser.submit(file, parseFlags);
            fileCount++;
        }
    
        // Process results one by one
        for (; fileCount > 0; fileCount--) {
            ...
            if (throwable == null) {
                ...
                try {
                    if (errorCode == PackageManager.INSTALL_SUCCEEDED) {
                        scanPackageLI(parseResult.pkg, parseResult.scanFile, parseFlags, scanFlags,
                                currentTime, null);
                    }
                } catch (PackageManagerException e) {
                    ...
                }
            } else if (throwable instanceof PackageParser.PackageParserException) {
                ...
            } else {
                ...
            }
            ...
        }
        ...
    }
    
    private PackageParser.Package scanPackageLI(PackageParser.Package pkg, File scanFile,
            final int policyFlags, int scanFlags, long currentTime, @Nullable UserHandle user)
            throws PackageManagerException {
        // If the package has children and this is the first dive in the function
        // we 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.
        if ((scanFlags & SCAN_CHECK_ONLY) == 0) {
            if (pkg.childPackages != null && pkg.childPackages.size() > 0) {
                scanFlags |= SCAN_CHECK_ONLY;
            }
        } else {
            scanFlags &= ~SCAN_CHECK_ONLY;
        }
    
        // Scan the parent
        PackageParser.Package scannedPkg = scanPackageInternalLI(pkg, scanFile, policyFlags,
                scanFlags, currentTime, user);
    
        // Scan the children
        final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
        for (int i = 0; i < childCount; i++) {
            PackageParser.Package childPackage = pkg.childPackages.get(i);
            scanPackageInternalLI(childPackage, scanFile, policyFlags, scanFlags,
                    currentTime, user);
        }
    
    
        if ((scanFlags & SCAN_CHECK_ONLY) != 0) {
            return scanPackageLI(pkg, scanFile, policyFlags, scanFlags, currentTime, user);
        }
    
        return scannedPkg;
    }
    
    public void submit(File scanFile, int parseFlags) {
        mService.submit(() -> {
            ParseResult pr = new ParseResult();
            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parallel parsePackage [" + scanFile + "]");
            try {
                PackageParser pp = new PackageParser();
                pp.setSeparateProcesses(mSeparateProcesses);
                pp.setOnlyCoreApps(mOnlyCore);
                pp.setDisplayMetrics(mMetrics);
                pp.setCacheDir(mCacheDir);
                pp.setCallback(mPackageParserCallback);
                pr.scanFile = scanFile;
                pr.pkg = parsePackage(pp, scanFile, parseFlags);
            } catch (Throwable e) {
                pr.throwable = e;
            } finally {
                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
            }
            try {
                mQueue.put(pr);
            } catch (InterruptedException e) {
                ...
            }
        });
    }
    
    @VisibleForTesting
    protected PackageParser.Package parsePackage(PackageParser packageParser, File scanFile,
            int parseFlags) throws PackageParser.PackageParserException {
        return packageParser.parsePackage(scanFile, parseFlags, true /* useCaches */);
    }
    
    public Package parsePackage(File packageFile, int flags, boolean useCaches)
            throws PackageParserException {
        Package parsed = useCaches ? getCachedResult(packageFile, flags) : null;
        if (parsed != null) {
            return parsed;
        }
    
        long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
        if (packageFile.isDirectory()) {
            parsed = parseClusterPackage(packageFile, flags);
        } else {
            parsed = parseMonolithicPackage(packageFile, flags);
        }
        ...
        return parsed;
    }
    

    PackageParser 的 parsePackage 函数
    从上面的代码可以看出,对于单一APK文件和多APK文件的package,分别调用了不同的函数进行处理。实际上,两个函数中的关键部分是一致的,我们以第一个函数为例,继续分析:

    private Package parseClusterPackage(File packageDir, int flags) throws PackageParserException {
        // 1. 解析出简化信息,例如名称、路径之类的
        final PackageLite lite = parseClusterPackageLite(packageDir, 0);
        if (mOnlyCoreApps && !lite.coreApp) {
            throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
                    "Not a coreApp: " + packageDir);
        }
    
        // Build the split dependency tree.
        //  2.
        SparseArray<int[]> splitDependencies = null;
        final SplitAssetLoader assetLoader;
        if (lite.isolatedSplits && !ArrayUtils.isEmpty(lite.splitNames)) {
            try {
                splitDependencies = SplitAssetDependencyLoader.createDependenciesFromPackage(lite);
                assetLoader = new SplitAssetDependencyLoader(lite, splitDependencies, flags);
            } catch (SplitAssetDependencyLoader.IllegalDependencyException e) {
                throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, e.getMessage());
            }
        } else {
            assetLoader = new DefaultSplitAssetLoader(lite, flags);
        }
    
        try {
            final AssetManager assets = assetLoader.getBaseAssetManager();
            final File baseApk = new File(lite.baseCodePath);
            // 3. 解析主要APK信息
            final Package pkg = parseBaseApk(baseApk, assets, flags);
            if (pkg == null) {
                throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
                        "Failed to parse base APK: " + baseApk);
            }
    
            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];
                pkg.applicationInfo.splitNames = pkg.splitNames;
                pkg.applicationInfo.splitDependencies = splitDependencies;
                pkg.applicationInfo.splitClassLoaderNames = new String[num];
    
                for (int i = 0; i < num; i++) {
                    // 4. 解析其他分离的APK信息
                    final AssetManager splitAssets = assetLoader.getSplitAssetManager(i);
                    parseSplitApk(pkg, i, splitAssets, flags);
                }
            }
    
            pkg.setCodePath(packageDir.getAbsolutePath());
            pkg.setUse32bitAbi(lite.use32bitAbi);
            return pkg;
        } finally {
            IoUtils.closeQuietly(assetLoader);
        }
    }
    

    上面的代码可以分为4个主要的步骤,我们现在来一一分析;

    1. parseClusterPackageLite
    2. loadApkIntoAssetManager
    3. parseBaseApk
    4. parseSliteApk

    回忆整个扫描过程:

    1. PackageParser 首先解析出了ApkLite,得到每个Apk文件的简化信息(对于具有多个Apk文件的Package来说,将得到多个Apklite)
    2. 利用所有的ApkLite及xml中的其他信息,解析出PackageLite;
    3. 利用PackageLite中的信息及xml中的其他信息,解析出Package信息;Package中就基本上涵盖了AndroidManifest.xml涉及的所有信息。
      注意在上述的解析过程中,PackageParser利用AssetManager存储了Package中资源文件的地址。

    scanPackageTracedLI 函数
    通过上述的扫描过程,我们得到了当前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 {
            ...........
        }
    }
    

    特殊处理 "android" 的 package

    private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg,
            final int policyFlags, final int scanFlags, long currentTime, @Nullable UserHandle user)
                    throws PackageManagerException {
        ...
        if ((scanFlags & SCAN_CHECK_ONLY) != 0) {
            ...
        } else {
            final int userId = user == null ? 0 : user.getIdentifier();
            // Modify state for the given package setting
            commitPackageSettings(pkg, pkgSetting, user, scanFlags,
                    (policyFlags & PackageParser.PARSE_CHATTY) != 0 /*chatty*/);
            if (pkgSetting.getInstantApp(userId)) {
                mInstantAppRegistry.addInstantAppLPw(userId, pkgSetting.appId);
            }
        }
        return pkg;
    }
    
    private void commitPackageSettings(PackageParser.Package pkg, PackageSetting pkgSetting,
            UserHandle user, int scanFlags, boolean chatty) throws PackageManagerException {
        final String pkgName = pkg.packageName;
        if (mCustomResolverComponentName != null &&
                mCustomResolverComponentName.getPackageName().equals(pkg.packageName)) {
            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;
                    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);
                    }
                }
            }
        }
    
    
    
    ..........
    
    //将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; i++) {
            PackageParser.Provider p = pkg.providers.get(i);
            ........
            mProviders.addProvider(p);
            ........
        }
        ..............
        //处理service信息
        N = pkg.services.size();
        .........
        for (i=0; i<N; i++) {
            ........
            mServices.addService(s);
            ........
        }
        ..............
        //处理Receiver
        N = pkg.receivers.size();
        ........
        for (i=0; i<N; i++) {
            .......
            mReceivers.addActivity(a, "receiver");
            .......
        }
        ..............
        //处理Activity
        N = pkg.activities.size();
        .........
        for (i=0; i<N; i++) {
            .......
            mActivities.addActivity(a, "activity");
            .......
        }
        ........
        //处理PermissionGroup
        N = pkg.permissionGroups.size();
        ........
        for (i=0; i<N; i++) {
            .....
            mPermissionGroups.put(pg.info.name, pg);
            ......
        }
        .........
        //处理permission和instrumentation等
        .........
        return pkg;
    }
    

    这一部分代码中,scanPackageDirtyLI 函数单独处理了名为"android"的Package。
    对应的Apk是 /system/framework/framework-res.apk,定义于framework/base/core/res中,对应的AndroidManifest.xml为:

    .........
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="android" coreApp="true" android:sharedUserId="android.uid.system"
        android:sharedUserLabel="@string/android_system_label">
    .........
    

    查看声明的framework-res.apk还包含了很多常用的Activity:

    1. ChooserActivity,当多个Activity符合某个Intent的时候,系统会弹出此Activity,由用户选择合适的应用来处理
    2. ShutdownActivity,关机前弹出的系统对话框

    现在很多做ROM的厂商,应该会修改这些Activity,以满足自己的feature。

    该Package和系统息息相关,因此得到了PKMS的特别青睐,主要体现在以下几点:

    1. PKMS中的 mPlateformPackage 成员用于保存该Package信息
    2. mAndroidApplication 用于保存此Package中的ApplicationInfo
    3. mResolveActivity 指向用于表示ChooserActivity信息的ActivityInfo
    4. 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。

    PKMS扫描Package过程从大的逻辑上来看,其实并不复杂。但其中很多地方,例如标签的含义、对某些字段的处理细节,还是需要进一步分析理解。

    PKMS构造函数的最后会扫描第三方的Package

    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);
        .............
    }
    
    // can downgrade to reader
    //将信息写到package.xml、package.lsit及pacakge-stopped.xml文件中
    mSettings.writeLPr();
    

    参考资料

    感谢一下文章作者
    Android7.0 PackageManagerService (2) PKMS构造函数的主要工作

    相关文章

      网友评论

          本文标题:PKMS 1

          本文链接:https://www.haomeiwen.com/subject/zawqvctx.html