PackageManagerService启动详解(三)之开始初始化阶段流程分析

  PKMS启动详解(三)之BOOT_PROGRESS_PMS_START阶段流程分析


Android PackageManagerService系列博客目录:

PKMS启动详解系列博客概要
PKMS启动详解(一)之整体流程分析
PKMS启动详解(二)之怎么通过packages.xml对已安装应用信息进行持久化管理?
PKMS启动详解(三)之BOOT_PROGRESS_PMS_START流程分析
PKMS启动详解(四)之Android包信息体和包解析器(上)
PKMS启动详解(五)之Android包信息体和包解析器(中)
PKMS启动详解(六)之Android包信息体和包解析器(下)
PKMS启动详解(七)之BOOT_PROGRESS_PMS_SYSTEM_SCAN_START阶段流程分析
PKMS启动详解(八)之BOOT_PROGRESS_PMS_DATA_SCAN_START阶段流程分析



引言

  在前面的博客PKMS启动详解(二)之怎么通过packages.xml对已安装应用信息进行持久化管理?中我们特意花了一篇博客的篇幅从设计者的意图和功能点出发详细介绍了Settings以及关联的packages.xml等文件在PKMS中的作用,从而为本篇PKMS启动BOOT_PROGRESS_PMS_START阶段分析打下了夯实的基础。根据我们在前面的博客知道PKMS启动可以划分为好几个阶段,在今天的博客中我们将会重点从源码角度出发来分析它的BOOT_PROGRESS_PMS_START启动阶段相关逻辑和重点知识点。

通过前面博客PackageManagerService启动详解(一)之整体流程分析我们知道该阶段执行的逻辑并不是非常的复杂但是却非常繁琐,对于PKMS其中的一些成员变量的初始化我们可以一笔带过,但是其中最最需要我们注意的就是构建Settings大管家解析packages.xml文件(当然第一次开机排除在外,因为此时相关的文件还没有创建和写入成功),以及构建SystemConfig实例获取系统配置信息,譬如共享库,系统feather,权限等。

并且上述的处理过程中牵涉到了非常多的源码,我只会截取其中的重点关键流程放出来,因为如果全对源码进行解析不仅会使读者失去兴趣,同时也会使本篇失去重点。当然如果读者对源码的分析非常感兴趣的话,墙裂建议参见博客PMS 第 2 篇,前提是你有毅力能看完。

事实上,学习是一个越往后学知道地越多的一个过程,一开始接触一个新事物必然会有无数的疑问,但很多疑问都是边边角角的问题,并不会影响主流程的打通,最好的方式是先记录下来,不求甚解,以后时不时拿出来审视一番,说不定在学到某个知识点的时候豁然开朗:哦,原来是这么一回事。

如果一开始就把宝贵的精力花费在深挖细枝末节的东西上,由于知识贮备不够,问题必然会越挖越多,有的根本无从下手,有的甚至是无解的问题,进入一个死胡同,一个深渊巨坑,永远走不出来跳不出来。

注意:本篇的介绍是基于Android 7.xx平台为基础的(并且为了书写简便后续PackageManagerService统一简称为PKMS),其中涉及的代码路径如下:

--- frameworks/base/services/core/java/com/android/server/pm/
	PackageManagerService.java
	PackageSetting.java 
	Settings.java
	PackageSettingBase.java
	PackageSignatures.java
	SettingBase.java
	SharedUserSetting.java
	UserManagerService.java
	SELinuxMMAC.java

--- frameworks/base/core/java/com/android/server/SystemConfig.java
--- frameworks/base/services/java/com/android/server/SystemServer.java

--- frameworks/base/core/java/android/os/Process.java

--- frameworks/base/core/jni/android_util_Process.cpp

--- system/core/include/private/android_filesystem_config.h

并且关于PKMS中的方法有如下几点需要注意的:
1.PKMS的部分方法带有LI后缀,表示需要获取mInstalllock这个锁时才能执行
2.部分方法带有LP后缀,表示需要获取mPackages这个锁才能执行
3.部分方法带有LPr后缀,必须先持有mPackages锁,并且只用于读操作
4.部分方法带有LPw后缀,必须先持有mPackages锁,并且只用于写操作

并且最后在正式开始本篇博客的分析前,我们先来提前看下PKMS在该阶段执行的整体流程图,如下:

在这里插入图片描述




一.PKMS启动BOOT_PROGRESS_PMS_START前奏阶段

在BOOT_PROGRESS_PMS_START阶段,牵涉的逻辑并不是非常的复杂,但是其源码涉及的小细节却是非常非常的多,所以这里为了篇幅的排版和美观我会将BOOT_PROGRESS_PMS_START阶段又划分为四个小阶段(这里是我人为划分的,实际上并不存在这三个阶段之说):
1.前奏
2.发展
3.高潮(不是那个不要想歪了,这里只是暂时没有找到比较好的词来描述而已的替代词)
4.结尾
等四个小阶段来对上述流程展开分析。

至此我们正式开始第一小阶段的分析,由于BOOT_PROGRESS_PMS_START源码篇幅很长,我们先来个总体的阶段的回顾:

    public PackageManagerService(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
        /************************* PKMS启动第一阶段 *************************/
        //写入日志,标识PackageManagerService正式开始第一阶段的启动
        EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START,
                SystemClock.uptimeMillis());

		//SDK版本检测
        if (mSdkVersion <= 0) {
            Slog.w(TAG, "**** ro.build.version.sdk not set!");
        }

        mContext = context;

        mPermissionReviewRequired = context.getResources().getBoolean(
                R.bool.config_permissionReviewRequired);

		//开机模式是否为工厂模式,默认为false
        mFactoryTest = factoryTest;
		
        mOnlyCore = onlyCore;//默认为false,标记是否只加载核心服务

		// 用于存储与显示屏相关的一些属性,例如屏幕的宽 / 高尺寸,分辨率等信息
        mMetrics = new DisplayMetrics();

	/******************** BOOT_PROGRESS_PMS_START前奏阶段开始 **************************/

		/*
			 在Settings中,创建packages.xml、packages-backup.xml、packages.list 等文件对象    
			 这个Settings对象非常重要,我们在后续的分析中会多次看到并使用到它
			 它是Android系统已经安装Package在内存中的数据映射,存储了譬如已安装应用的代码位置,数据位置,签名等信息			 
		*/   
        mSettings = new Settings(mPackages);// 【 1.1 】

		/*
			向Settings实例对象添加system, phone, log, nfc, bluetooth, shell这六种shareUserId到mSettings中,
			后面扫描和安装有用!
			其中的system对应的shareUserId也就是我们经常所说的对应的Android系统权限			
		*/
        mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
                ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);// 【 1.2 】
        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);
	
		//此处具体用途不是很清晰,忽略
        File setFile = new File(AlarmManager.POWER_OFF_ALARM_SET_FILE);
        File handleFile = new File(AlarmManager.POWER_OFF_ALARM_HANDLE_FILE);
        mIsAlarmBoot = SystemProperties.getBoolean("ro.alarm_boot", false);
        if (mIsAlarmBoot) {
			...
        } else if (setFile.exists() && handleFile.exists()) {
			...
        }
		//这块具体用途不是很清晰,应该是和调试进程隔离有关
        String separateProcesses = SystemProperties.get("debug.separate_processes");
        if (separateProcesses != null && separateProcesses.length() > 0) {
            if ("*".equals(separateProcesses)) {
                mDefParseFlags = PackageParser.PARSE_IGNORE_PROCESSES;
                mSeparateProcesses = null;
                Slog.w(TAG, "Running with debug.separate_processes: * (ALL)");
            } else {
                mDefParseFlags = 0;
                mSeparateProcesses = separateProcesses.split(",");
                Slog.w(TAG, "Running with debug.separate_processes: "
                        + separateProcesses);
            }
        } else {
            mDefParseFlags = 0;
            mSeparateProcesses = null;
        }
	
		/*
			installer在SystemServer中被构造,这里通过该对象与底层installd进行socket通信
			进行具体安装与卸载的操作
		*/
        mInstaller = installer;
		//创建PackageDexOptimizer,该类用于辅助进行dex优化
        mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,
                "*dexopt*");
        mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());

		//创建OnPermissionChangeListeners对象,用于监听权限改变!
        mOnPermissionChangeListeners = new OnPermissionChangeListeners(
                FgThread.get().getLooper());

        getDefaultDisplayMetrics(context, mMetrics);

		/******************** BOOT_PROGRESS_PMS_START发展阶段开始 **************************/


		/*
			构建SystemConfig对象实例(单例模式)
			它主要用于获取系统配置信息
 			譬如共享库,系统feather,权限等
 		*/
        SystemConfig systemConfig = SystemConfig.getInstance(); // 【2.1】

        mGlobalGids = systemConfig.getGlobalGids(); // 【2.7】
        mSystemPermissions = systemConfig.getSystemPermissions();
        mAvailableFeatures = systemConfig.getAvailableFeatures();

        mProtectedPackages = new ProtectedPackages(mContext);

        synchronized (mInstallLock) {
        // writer
        synchronized (mPackages) {
			/*
				构建并启动Handler处理线程,用于处理应用的安装和卸载相关事件的处理
				这里为啥要单独开辟一个线程处理呢,这是因为第三方应用的安装和卸载是一个比较耗时的操作
			*/
            mHandlerThread = new ServiceThread(TAG,
                    Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
            mHandlerThread.start();

			//创建一个 PackageHandler 对象,绑定前面创建的HandlerThread,处理安装和卸载
            mHandler = new PackageHandler(mHandlerThread.getLooper());  // 【 2.9 】
            
            mProcessLoggingHandler = new ProcessLoggingHandler();

			 //使用看门狗检测当前消息处理线程,如果超时则触发看门狗机制
            Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);

			//创建默认权限管理对象,用于给某些预制的 apk 给予权限,也可以撤销!
            mDefaultPermissionPolicy = new DefaultPermissionGrantPolicy(this);

			/*
				创建需要扫描检测的目录文件对象,为后续扫描做准备!
				这里最最常见的就是/data/app/和/data/app-private/目录
			*/
            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");
            mRegionalizationAppInstallDir = new File(dataDir, "app-regional");

			//构造UserManagerService对象,创建用户管理服务
            sUserManager = new UserManagerService(context, this, mPackages);


            //读取权限配置文件中的信息,保存到mPermissions这个ArrayMap中
            ArrayMap<String, SystemConfig.PermissionEntry> permConfig
                    = systemConfig.getPermissions(); // 【2.7】
            for (int i=0; i<permConfig.size(); i++) {
                SystemConfig.PermissionEntry perm = permConfig.valueAt(i);
                BasePermission bp = mSettings.mPermissions.get(perm.name);
                // 加入到Settings的mPermissions中,SystemConfig解析的权限的包名都为android
                if (bp == null) {
                	// BasePermission.TYPE_BUILTIN表示的是在编译时期就确定好的,系统要提供的权限!
                    bp = new BasePermission(perm.name, "android", BasePermission.TYPE_BUILTIN);
                    mSettings.mPermissions.put(perm.name, bp);
                }
                if (perm.gids != null) {
                	// 如果系统权限有所属的gids,将其添加到BasePermission对象中
                    bp.setGids(perm.gids, perm.perUser);
                }
            }

			//获取并处理所有共享库信息
            ArrayMap<String, String> libConfig = systemConfig.getSharedLibraries(); // 【2.7】
            for (int i=0; i<libConfig.size(); i++) {
                mSharedLibraries.put(libConfig.keyAt(i),
                        new SharedLibraryEntry(libConfig.valueAt(i), null));
            }

			/******************** BOOT_PROGRESS_PMS_START高潮阶段开始 **************************/
			//尝试读取mac_permissions.xml中的selinux信息
            mFoundPolicyFile = SELinuxMMAC.readInstallPolicy();

			/*
				读取文件package.xml的内容,解析后存入mSettings的mPackages中
				并且根据解析的结果判断是否是第一次启动,如果是第一次开机,那么是没有相关数据的
				详见章节 3.1
			*/
			mFirstBoot = !mSettings.readLPw(sUserManager.getUsers(false));

            /* 
            	移除哪些codePath无效的Package(该Pacakge是系统App升级过来的被安装在/data/app分区)
            	此时需要恢复处于system目录下的同名package
            */
            final int packageSettingCount = mSettings.mPackages.size();
            for (int i = packageSettingCount - 1; i >= 0; i--) {
                PackageSetting ps = mSettings.mPackages.valueAt(i);
                if (!isExternal(ps) && (ps.codePath == null || !ps.codePath.exists())
                        && mSettings.getDisabledSystemPkgLPr(ps.name) != null) {
                    mSettings.mPackages.removeAt(i);
                    mSettings.enableSystemPackageLPw(ps.name);
                }
            }

            if (mFirstBoot) {
                /* 
                	如果是第一次开机,从另外一个系统拷贝 odex 文件到当前系统的 data 分区
         		 	Android 7.1 引进了 AB 升级,这个是 AB 升级的特性,可以先不看!
         		 */
                requestCopyPreoptedFiles();
            }
			
			//判断是否自定义的解析界面(存在多个满足添加的Activiyt,弹出的选择界面的那个)
            String customResolverActivity = Resources.getSystem().getString(
                    R.string.config_customResolverActivity);
            if (TextUtils.isEmpty(customResolverActivity)) {
                customResolverActivity = null;
            } else {
                mCustomResolverComponentName = ComponentName.unflattenFromString(
                        customResolverActivity);
            }

			//获取扫描开始的时间
            long startTime = SystemClock.uptimeMillis();
            ...

下面我们对该小阶段中涉及的重点流程逐一分析,各个击破!


1.1 Settings大管家的构建

在正式开始分析Settings的构建之前,我们先来看下它涉及的类图关系如下:

在这里插入图片描述


关于Settings牵涉的类以及关联,可以详见前面的博客PKMS启动详解(二)之怎么通过packages.xml对已安装应用信息进行持久化管理?,在这里我们要分析的是它们之间的关联在源码层是怎么被构建的。并且墙裂建议读者在开始源码分析前,将Settings涉及的几个重要成员变量要给提前熟悉!

理论知识我们武装好了,啥也不多说了,直接上源码:

//[Settings.java]
    Settings(Object lock) {
		// 这里传入的lock对象是PMKS的mPackags
        this(Environment.getDataDirectory(), lock);
    }

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

		// 构建mRuntimePermissionsPersistence实例对象,用于处理运行时权限相关的操作
        mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock);

		// 如果/data/system目录没有创建则创建,并设置该目录对应的权限
        mSystemDir = new File(dataDir, "system");
        mSystemDir.mkdirs();
        FileUtils.setPermissions(mSystemDir.toString(),
                FileUtils.S_IRWXU|FileUtils.S_IRWXG
                |FileUtils.S_IROTH|FileUtils.S_IXOTH,
                -1, -1);
		// 创建 packages.xml 和 packages-backup.xml 文件对象
        mSettingsFilename = new File(mSystemDir, "packages.xml");
        mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml");

		// 创建 packages.list 文件对象,并设置权限信息!
		mPackageListFilename = new File(mSystemDir, "packages.list");
        FileUtils.setPermissions(mPackageListFilename, 0640, SYSTEM_UID, PACKAGE_INFO_GID);
        FileUtils.setPermissions(mSettingsFilename, 0640, SYSTEM_UID, SYSTEM_UID);
        

		// 创建 sdcardfs 文件对象!
        final File kernelDir = new File("/config/sdcardfs");
        mKernelMappingFilename = kernelDir.exists() ? kernelDir : null;

        // Deprecated: Needed for migration
        // 创建 packages-stopped.xml 和 packages-stopped-backup.xml 文件对象!
        mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml");
        mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml");
    }

这里我们可以看到Setings的构建比较简单,主要就是构建了mRuntimePermissionsPersistence运行时权限管理对象,并将应用安装相关的持久化信息文件加载到具体的File文件类中。

关于/data/system/package*.xxx文件这里就不过展开了,可以到PKMS启动详解(二)之怎么通过packages.xml对已安装应用信息进行持久化管理?中进行细看。(为了节奏的连贯性,我还是简单介绍一下)

  • packages.xml:记录了系统中所有安装的应用信息,包括基本信息、签名和权限,这个是Android应用信息持久化的关键文件
  • pakcages-back.xml:packages.xml文件的备份,用于描述系统中所安装的所有 Package 信息,PMS 会先把数据写入 packages-backup.xml,信息写成功后,再改名为 packages.xml
  • pakcages-stoped.xml:记录系统中被强制停止的运行的应用信息。系统在强制停止某个应- 用的时候,会将应用的信息记录在该文件中
  • pakcages-stoped-backup.xml:pakcages-stoped.xml文件的备份
  • package-usage.list:记录了应用的使用情况,譬如使用了多久
  • packages.list:记录了应用的一些简单情况

1.2 Settings.addSharedUserLPw添加共享用户

在Settings中部分方法带有LP后缀,表示需要获取mPackages这个锁才能执行。

可以看到在PKMS中构建Settings成功以后,接着它也是毫不手软的一口气调用了五次addSharedUserLPw 方法添加了五个系统共享用户(一夜五次郎),在分析它的逻辑之前,我们先来看下它传入的参数:


  • String name:共享用户名,下面是传入的用户名:

    • android.uid.system
    • android.uid.phone
    • android.uid.log
    • android.uid.nfc
    • android.uid.bluetooth
    • android.uid.shell

    对于上面的共享用户名,做ROM开发的最最熟悉,也最最经常用的肯定是android.uid.system了。

  • int uid:共享用 id,下面的用户名和上面的id 一一对应!

    • Process.SYSTEM_UID
    • Process.RADIO_UID
    • Process.LOG_UID
    • Process.NFC_UID
    • Process.BLUETOOTH_UID
    • Process.SHELL_UID

    关于共享id的具体描述,可以参见Process.java中。

  • int pkgFlags:
    ApplicationInfo.FLAG_SYSTEM

  • int pkgPrivateFlags:
    ApplicationInfo.PRIVATE_FLAG_PRIVILEGED


好了入参我们整清楚了,直接来看源码,如下:

// [Settings.java]
    SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) {
		/*
			 从mSharedUsers列表中看能否根据用户id取出系统共享用户信息,判断当前系统共享用户是否是重复添加
		*/
		SharedUserSetting s = mSharedUsers.get(name);
        if (s != null) {
            if (s.userId == uid) {//判断uid是否发生变化
                return s;
            }
            PackageManagerService.reportSettingsProblem(Log.ERROR,
                    "Adding duplicate shared user, keeping first: " + name);
            return null;
        }
		// 创建 SharedUserSetting对象
        s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags); // 【1.2.1】
        s.userId = uid;

		// 根据uid的范围,保存到到mUserIds或mOtherUserIds中!
        if (addUserIdLPw(uid, s, name)) { // 【1.2.2】
			// 将前面创建的SharedUserSetting以name为key添加到mSharedUsers哈希列表中
            mSharedUsers.put(name, s); 
            return s;
        }
        return null;
    }

其主要逻辑分为如下:

  • 根据传入参数构建SharedUserSetting共享用户类
  • 将前面创建的SharedUserSetting实例,继续调用addUserIdLPw进行下一步处理
  • 然后根据条件判断是否将前面创建的SharedUserSetting实例添加到mSharedUsers数据结构中

下面我们简单对上述逻辑展开分析!

1.2.1 SharedUserSetting类

通过前面的博客我们知道,SharedUserSetting被设计的用途主要用来描述具有相同的sharedUserId的应用信息,它的成员变量packages保存了所有具有相同sharedUserId的应用信息引用,而成员变量userId则是记录多个APK共享的UID。共享用户的应用的签名是相同的,签名保存在成员变量signatures中(这里有一点需要注意,由于签名相同,Android运行时很容易检索到某个应用拥有相同的sharedUserId的其他应用)。

在正式开始SharedUserSetting的源码前,我们先来看看它的类图(至于它的关系图,参见前面),如下:

在这里插入图片描述

好了,让我们通过源码来揭开SharedUserSetting的真实面纱

// [SharedUserSetting.java]
final class SharedUserSetting extends SettingBase {
	// 共享uid的名称
    final String name;

	// uid的值
	int userId;

    
	// 和这个sharedUserId相关的flags
	int uidFlags; // ApplicationInfo.FLAG_SYSTEM
    int uidPrivateFlags; // ApplicationInfo.PRIVATE_FLAG_PRIVILEGED

	// 使用这个共享uid的所有应用程序package信息
	final ArraySet<PackageSetting> packages = new ArraySet<PackageSetting>();

	// 使用这个共享uid的签名
	final PackageSignatures signatures = new PackageSignatures();

    SharedUserSetting(String _name, int _pkgFlags, int _pkgPrivateFlags) {
        super(_pkgFlags, _pkgPrivateFlags);
        uidFlags =  _pkgFlags;
        uidPrivateFlags = _pkgPrivateFlags;
        name = _name;
    }

    @Override
    public String toString() {
        return "SharedUserSetting{" + Integer.toHexString(System.identityHashCode(this)) + " "
                + name + "/" + userId + "}";
    }
    ...
}  

SharedUserSetting类并不是非常的复杂,在现阶段我们只需要了解它的几个核心成员的意义,以及它和我们后续要说的PackageSetting有一个共同的爸比SettingBase即可。

1.2.2 Settings.addUserIdLPw添加userid相关信息

我们继续回到章节1.2中,继续往下看Settings.addUserIdLPw方法的调用,该方法主要用于将前面创建好的SharedUserSetting实例对象根据其uid是系统uid还是非系统uid,添加到特定的集合中,其处理逻辑如下:

但是这里需要注意的是,这里只是在此种源码情况下obj参数特指SharedUserSetting对象,在后续的分析中也会调用到addUserIdLPw方法,它传入的参数obj就不一定是SharedUserSetting也可能是PackageSetting了

//[Settings.java]

	// 存储所有package.xml中shared-user标签的信息
	// 是一个以String类型的name(比如"android.uid.system")为"key",以SharedUserSetting对象为"value"的HashMap
    final ArrayMap<String, SharedUserSetting> mSharedUsers =
            new ArrayMap<String, SharedUserSetting>();
	
	// 存储是非系统应用的uid相关信息,包括共享和非共享
    private final ArrayList<Object> mUserIds = new ArrayList<Object>();
	
	// 存储是系统应用的的的uid相关信息,包括共享和非共享
    private final SparseArray<Object> mOtherUserIds =
            new SparseArray<Object>();
            
	/*
		addUserIdLPw将创建的SharedUserSetting对象根据其uid是系统uid还是非系统uid 
		添加到指定的集合中!

		但是这里需要注意的是,这里只是在这种情况下特指SharedUserSetting对象,在后续的分析中
		也会调用到addUserIdLPw方法,它传入的参数obj就不一定是SharedUserSetting也可能是PackageSetting了
	*/
    private boolean addUserIdLPw(int uid, Object obj, Object name) {
		// uid不能超过限制
		if (uid > Process.LAST_APPLICATION_UID) {
            return false;
        }

		// 如果uid属于非系统应用,将其加入到mUserIds集合中
        if (uid >= Process.FIRST_APPLICATION_UID) {
            int N = mUserIds.size();
            final int index = uid - Process.FIRST_APPLICATION_UID;
            while (index >= N) {
                mUserIds.add(null);
                N++;
            }
            if (mUserIds.get(index) != null) {
                PackageManagerService.reportSettingsProblem(Log.ERROR,
                        "Adding duplicate user id: " + uid
                        + " name=" + name);
                return false;
            }
            mUserIds.set(index, obj);
        } else {
			// 如果uid属于系统应用,将其加入到mOtherUserIds集合中!
            if (mOtherUserIds.get(uid) != null) {
                PackageManagerService.reportSettingsProblem(Log.ERROR,
                        "Adding duplicate shared id: " + uid
                                + " name=" + name);
                return false;
            }
            mOtherUserIds.put(uid, obj);
        }
        return true;
    }

这里的addUserIdLPw的处理逻辑比较简单,我们就不过多的赘述了,但是其中涉及的几个变量,我们简单说明一下:

  • Process.FIRST_APPLICATION_UID:它的值被定义成10000,用来区分当前的uid是属于系统应用还是非系统应用的。非系统应用的uid的取值在10000到19999,系统应用的uid小10000;
  • Process.LAST_APPLICATION_UID:它的值被定义为19999,表示应用程序的uid最大的合法取值为19999,如果超过这个值表明uid非法了

在本篇博客此处和后续的多处,会很频繁的提及到uid的概念,这个我觉得还是有必要重点强调一下关于Android中uid和pid两个的概念!

  • PID:为Process Identifier的简称, PID就是各进程的身份标识,程序一运行系统就会自动分配给进程一个独一无二的PID。进程中止后PID被系统回收,可能会被继续分配给新运行的程序,但是在android系统中一般不会把已经kill掉的进程ID重新分配给新的进程,新产生进程的进程号,一般比产生之前所有的进程号都要大。
  • UID:一般理解为User Identifier,UID在linux中就是用户的ID,表明时哪个用户运行了这个程序,主要用于权限的管理。而在android 中又有所不同,因为android为单用户系统,这时UID 便被赋予了新的使命,数据共享,为了实现数据共享,android为每个应用几乎都分配了不同的UID,不像传统的linux,每个用户相同就为之分配相同的UID。(当然这也就表明了一个问题,android只能时单用户系统,在设计之初就被他们的工程师给阉割了多用户),使之成了数据共享的工具。

因此在android中PID,和UID都是用来识别应用程序的身份的,但UID是为了不同的程序来使用共享的数据而使用的


1.3 PKMS启动BOOT_PROGRESS_PMS_START前奏小结

至此PKMS启动BOOT_PROGRESS_PMS_START前奏阶段核心的内容分析到这里就告一段了,其中的一些其他的成员变量类的初始化,我就没有重点表明出来了(感兴趣的读者,可以自行分析),这里我们还是对该阶段简单总结一下:

  • 首先创建Settings实例对象,该实例对象非常重要,它主要用来记录上次Android终端已安装应用相关信息的一个管理类,如果Android终端设备是第一次启动(如果没有发生异常的情况)则会创建packages.xml、packages-backup.xml、packages.list等文件,同时添加 system, phone, log, nfc, bluetooth, shell这六种 shareUserId 到mSettings中进行管理,该对象被初始化成功之后,后面的扫描安装都会被用到,如果应用的情况(即有新的应用安装,卸载,升级)发生变化,该对象也会进行相应的更新
  • 初始mInstaller对象,Installer是一个系统服务,它封装了很多PMS进行包管理需要用到的方法,譬如install()、 dexopt()、rename()等。在Installer内部,其实是通过Socket连接installd,将执行指令发送到installd完成具体的操作
  • 创建PackageDexOptimizer对象,该对象主要用于对应用进行相关的odex优化操作,它是进行Dex优化的工具类。对于一个APK而言,编译后其APK包中可执行文件的格式dex,安装到了手机上以后,需要经过文件格式转化才能运行,譬如APK需要转换成oat格式才能在ART虚拟机上运行,文件格式转换的过程就叫DexOpt。DalvikVM的时代,Android可执行文件的格式是dex,有一种进一步优化的格式叫odex;ART虚拟机的时代,Android可执行文件的格式是oat。虽然都叫做DexOpt,但在DalvikVM和ART两种不同虚拟机的时代分别有不同的内涵
  • 创建OnPermissionChangeListeners对象,该对象主要用于监听用户uid权限改变,当监听到用户uid权限发生变化之后,会将相关信息发送给注册好的监听者,是一个典型的观察者设计模式

到这里PKMS启动BOOT_PROGRESS_PMS_START前奏就宣告完结了,我们最好来看下其流程图:

在这里插入图片描述




二.PKMS启动BOOT_PROGRESS_PMS_START发展阶段

好了,前面阶段分析完毕,我们开始下一阶段的分析,在这一阶段中我们的重中之重是SystemConfig的,首先我们会从整体上开始介绍,然后再从源码入手。


2.1 SystemConfig类简介

SystemConfig是用来加载并存储系统全局的配置信息的数据结构,其中涉及的数据有系统配置信息,权限信息,gid,共享库等信息。其原始的数据来源于/system/etc/sysconfig和/system/etc/permissions目录下的XML文件,在SystemConfig对象构建时,会读取这两个目录下所有XML文件的内容,其中的XML文件主要从如下几个维度来进行描述:

  • 权限与gid的映射关系:譬如,以下内容表示属于inet这个组的用户都拥有android.permission.INTERNET权限:

    <permission name="android.permission.INTERNET" >
    	 <group git="inet">
    </permission>
    
  • 权限与uid的映射关系:譬如,以下内容表示uid为meida用户拥有android.permission.CAMERA这个权限:

    <assign-permission name="android.permission.CAMERA" uid="media" />
    
  • 共享库的定义:在Android中有很多公共库,除了BOOTCLASSPATH和SYSTEMSERVERCLASSPATH中定义的之外,框架层还支持额外的扩展,譬如,以下内容表示公共库的包名和其路径的关系:

    <library name="org.apache.http.legacy"
            file="/system/framework/org.apache.http.legacy.jar" />
    

这里我们有了一个基本的了解,下面就从SystemConfig的构造开始分析。


2.2 SystemConfig的构建

通过getInstance获取的方式来构建SystemConfig实例,我们很明显的可以知道SystemConfig是一个单例模式的,我们直接入手来看看:

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

插播一条广告,插入一条SystemConfig类图

在这里插入图片描述

好了,我们继续往下看SystemConfig的构造方法:

// [SystemConfig.java]
	/*
		 在构造器中会调用相关方法读取配置信息
	*/
    SystemConfig() {

        // 读取/etc/sysconfig/目录下的配置信息
        readPermissions(Environment.buildPath(
                Environment.getRootDirectory(), "etc", "sysconfig"), ALLOW_ALL); 

        // 读取/etc/permissions/目录下的配置信息
        readPermissions(Environment.buildPath(
                Environment.getRootDirectory(), "etc", "permissions"), ALLOW_ALL); // 【2.3】

        int odmPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_APP_CONFIGS;
        readPermissions(Environment.buildPath(
                Environment.getOdmDirectory(), "etc", "sysconfig"), odmPermissionFlag);
        readPermissions(Environment.buildPath(
                Environment.getOdmDirectory(), "etc", "permissions"), odmPermissionFlag);

        readPermissions(Environment.buildPath(
                Environment.getOemDirectory(), "etc", "sysconfig"), ALLOW_FEATURES);
        readPermissions(Environment.buildPath(
                Environment.getOemDirectory(), "etc", "permissions"), ALLOW_FEATURES);

		...
    }

SystemConfig构造方法核心逻辑是调用readPermissions方法读取相关目录下的配置文件信息,这里我们可以看到readPermissions方法有两个入参参数,在进行下面的介绍前我们简单对它的两个参数简单介绍一下:

  • File libraryDir:表示可以访问的目录,这些目录包括:

    /etc/sysconfig/
    /etc/permissions/  
    
    /odm/sysconfig/
    /odm/permissions/
    
    /oem/sysconfig/
    /oem/permissions/
    

    这里我们以/etc/permissions/为例,可以看到该目录下面有一堆的xml文件!

    在这里插入图片描述

  • int permissionFlag:表示目录被设置对应的flag时,可以有那些配置信息的类型用来被读取,关于该flag的定义如下:

    // 这个没有啥好注释的了,见名思意
    private static final int ALLOW_FEATURES = 0x01;
    private static final int ALLOW_LIBS = 0x02;
    private static final int ALLOW_PERMISSIONS = 0x04;
    private static final int ALLOW_APP_CONFIGS = 0x08;
    private static final int ALLOW_ALL = ~0;
    

这里我们将上述一番逻辑摆出来以后,经过一番转换,就得到了如下的最终公式结论:
1. /oem/etc/sysconfig和/oem/etc/permissions目录:只能设置 features!

2. /odm/etc/sysconfig和/odm/etc/permissions目录:只能设置 libs,features 和 app configs!

3. /system/etc/sysconfig和/system/etc/permissions目录:可以设置所有的配置如libs,permissions,features,app configs 等等!


2.3 SystemConfig类管理的配置文件格式简介

在正式开始介绍SystemConfig调用readPermissions方法之前,我们非常有必要拿简单介绍下它读取的配置文件的格式,这里我们以//etc/permissions/下面的platform.xml和android.hardware.bluetooth.xml为例说明:

这里为了描述方便,我会将上述两个xml中的<permissions>根标签的子标签都放在一起,这样的排版比较好(注意readPermissions读取的配置xml文件的根标签必须是<permissions>或者<config>这两种)。

并且在本篇博客中我们只会重点分析permission,assign-permission,library标签的解析,其它的标签感兴趣的读者可以自行研究。

<permissions>
	<!-- 权限与gid的映射关系,譬如,以下内容表示属于net_bt这个组的用户都拥有android.permission.BLUETOOTH权限
	并且这里有一点需要注意的是,这里group可以有多个 -->
    <permission name="android.permission.BLUETOOTH" >
        <group gid="net_bt" />
    </permission>
    ...

	<!-- 权限与uid的映射关系,譬如,以下内容表示uid为meida用户拥有android.permission.WAKE_LOCK这个权限 -->
    <assign-permission name="android.permission.WAKE_LOCK" uid="media" />
	...
	
	<!--  扩展的Android共享库(注意指的是jar包,而不是so类型的共享库),以下内容表示共享库的包名和其路径的关系-->
    <library name="android.test.runner"
            file="/system/framework/android.test.runner.jar" />
	...
	
	<!-- 该子标签是被定义在android.hardware.bluetooth.xml中的,表明当前设备终端是否支持一些feature特性,
	我们通常通过hasSystemFeature来进行判断,譬如我们最最常见的就是在SystemServer.java中根据feature特性
	来决定是否启动一些和硬件特性关联的服务,譬如BLE,NFC等 -->
	<feature name="android.hardware.bluetooth" />
	...
	
	<!-- 下面的子标签感兴趣的读者可以自行研究,这里不在当前博客重点讨论的范围之内 -->            
    <!-- 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. -->
    <allow-in-power-save package="com.android.shell" />

    <!-- These are the standard packages that are white-listed to always have internet
         access while in data mode, even if they aren't in the foreground. -->
    <allow-in-data-usage-save package="com.android.providers.downloads" />

    <!-- These are the packages that are white-listed to be able to run as system user -->
    <system-user-whitelisted-app package="com.android.settings" />

    <!-- These are the packages that shouldn't run as system user -->
    <system-user-blacklisted-app package="com.android.wallpaper.livepicker" />

    
</permissions>

同时在后续的具体标签解析中,我们也会以上面我所例举的xml文件为模板进行相关的解析,所以读者有必要提前了解一下。


2.4 SystemConfig.readPermissions处理系统配置目录

好了,要解析的文件格式和组成我们了解清楚了,让我们从心出发,错了从源码出发出发来看readPermissions是怎么解析配置文件的。

// 【SystemConfig.java】
    void readPermissions(File libraryDir, int permissionFlag) {
        
        // 检测目录可读性
        if (!libraryDir.exists() || !libraryDir.isDirectory()) {
            if (permissionFlag == ALLOW_ALL) {
                Slog.w(TAG, "No directory " + libraryDir + ", skipping");
            }
            return;
        }
        if (!libraryDir.canRead()) {
            Slog.w(TAG, "Directory " + libraryDir + " cannot be read");
            return;
        }


        // 遍历解析目录下的所有*.xml 文件!
        File platformFile = null;
        for (File f : libraryDir.listFiles()) {
            // 对于/etc/permissions/platform.xml文件,最后再处理!
            if (f.getPath().endsWith("/etc/permissions/platform.xml")) {
                platformFile = f;
                continue;
            }

			// 异常处理
            if (!f.getPath().endsWith(".xml")) {
                Slog.i(TAG, "Non-xml file " + f + " in " + libraryDir + " directory, ignoring");
                continue;
            }
            if (!f.canRead()) {
                Slog.w(TAG, "Permissions library file " + f + " cannot be read");
                continue;
            }
			// 进一步解析文件
            readPermissionsFromXml(f, permissionFlag);2.5}

        
        if (platformFile != null) {
			// 单独解析platform.xml文件
			readPermissionsFromXml(platformFile, permissionFlag);
        }
    }

readPermissions的处理逻辑比较简单,其流程如下:

  • 先做一些常规检测,譬如检测配置目录权限,可读性等
  • 调用readPermissionsFromXml方法解决配置目录文件下的xml文件,但是对/etc/permissions/platform.xml的文件单独做特殊处理,放到最后才进行解析(当然前提得是有才可以)

这里我们看到,最后无论是那个xml文件都殊途同归的调用了readPermissionsFromXml方法来进行下一步的处理,那么我们还有什么理由不跟进去一探究竟呢!


2.5 SystemConfig.readPermissionsFromXml解析系统配置文件

我们接着继续来看readPermissionsFromXml处理具体配置文件的流程,通过前面可知配置文件中的标签有很多,这里我们只会解析前面2.2章节重点表明的标签,其它的感兴趣的读者可以尝试自行处理!

// 【 SystemConfig.java】

	/*
		这个方法洋洋洒洒好几百行,但是核心思想只有一个就是解析xml中的各种标签
	*/
    private void readPermissionsFromXml(File permFile, int permissionFlag) {
        FileReader permReader = null;
        try {
            permReader = new FileReader(permFile);
        } catch (FileNotFoundException e) {
            Slog.w(TAG, "Couldn't find or open permissions file " + permFile);
            return;
        }

		/*
		 	判断当前的Android设备是否是低内存设备
		 	如果是低内存的设备,则android对一些feature特性则会进行放弃
		 */
        final boolean lowRam = ActivityManager.isLowRamDeviceStatic();

        try {
            XmlPullParser parser = Xml.newPullParser();
            parser.setInput(permReader);

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

            if (type != parser.START_TAG) {
                throw new XmlPullParserException("No start tag found");
            }

			// 判断根标签是否是permissions或者config,其它的都是非法的
            if (!parser.getName().equals("permissions") && !parser.getName().equals("config")) {
                throw new XmlPullParserException("Unexpected start tag in " + permFile
                        + ": found " + parser.getName() + ", expected 'permissions' or 'config'");
            }
			/*
				通过位与操作,判断当前的目录可以设置哪些权限
				这种方式在Android中比较常见
			*/
            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"标签
					然后将解析得到的gid存放在mGlobalGids中
				*/
                if ("group".equals(name) && allowAll) { // 【 2.5.1 】
					// 解析xml文件中定义的gid信息
                    String gidStr = parser.getAttributeValue(null, "gid");
                    if (gidStr != null) {
                        int gid = android.os.Process.getGidForName(gidStr);
                        // 将得到的gid添加到mGlobalGids列表中
                        mGlobalGids = appendInt(mGlobalGids, gid);
                    } else {
                        Slog.w(TAG, "<group> without gid in " + permFile + " at "
                                + parser.getPositionDescription());
                    }

                    XmlUtils.skipCurrentTag(parser);
                    continue;
                } 
                
				/* 
					根据标签和解析规则解析"permission"标签
				*/
				else if ("permission".equals(name) && allowPermissions) {
					//解析系统中所有权限和其所属gid的信息
                    String perm = parser.getAttributeValue(null, "name");
                    if (perm == null) {
                        Slog.w(TAG, "<permission> without name in " + permFile + " at "
                                + parser.getPositionDescription());
                        XmlUtils.skipCurrentTag(parser);
                        continue;
                    }
                    perm = perm.intern();
					// 调用readPermission继续解析权限
                    readPermission(parser, perm);  // 【 2.5.2 】

                } 
                
				/*
					 如果allowPermissions为true,解析"assign-permission"标签
					 解析系统中uid和其所持有的权限的关系!
				*/
				else if ("assign-permission".equals(name) && allowPermissions) {  // 【 2.5.3】
                    String perm = parser.getAttributeValue(null, "name");//权限名称
                    if (perm == null) {
                        Slog.w(TAG, "<assign-permission> without name in " + permFile + " at "
                                + parser.getPositionDescription());
                        XmlUtils.skipCurrentTag(parser);
                        continue;
                    }
                    String uidStr = parser.getAttributeValue(null, "uid");// 拥有该权限的uid
                    if (uidStr == null) {
                        Slog.w(TAG, "<assign-permission> without uid in " + permFile + " at "
                                + parser.getPositionDescription());
                        XmlUtils.skipCurrentTag(parser);
                        continue;
                    }
                    int uid = Process.getUidForName(uidStr);// 将uid转为init值
                    if (uid < 0) {
                        Slog.w(TAG, "<assign-permission> with unknown uid \""
                                + uidStr + "  in " + permFile + " at "
                                + parser.getPositionDescription());
                        XmlUtils.skipCurrentTag(parser);
                        continue;
                    }
                    perm = perm.intern();
                    ArraySet<String> perms = mSystemPermissions.get(uid);
                    if (perms == null) {
                        perms = new ArraySet<String>();
                        mSystemPermissions.put(uid, perms);
                    }
                    perms.add(perm);
                    XmlUtils.skipCurrentTag(parser);

                } 
                
                /*
                	如果 allowLibs为true,解析"library"标签
                	获得系统中所有共享库的信息!
                */
				else if ("library".equals(name) && allowLibs) {
                    String lname = parser.getAttributeValue(null, "name");
                    String lfile = parser.getAttributeValue(null, "file");
                    if (lname == null) {
                        Slog.w(TAG, "<library> without name in " + permFile + " at "
                                + parser.getPositionDescription());
                    } else if (lfile == null) {
                        Slog.w(TAG, "<library> without file in " + permFile + " at "
                                + parser.getPositionDescription());
                    } else {
                        //Log.i(TAG, "Got library " + lname + " in " + lfile);
                        mSharedLibraries.put(lname, lfile);
                    }
                    XmlUtils.skipCurrentTag(parser);
                    continue;

                } 
                
				/*
					 解析feather子标签
				*/
				else if ("feature".equals(name) && allowFeatures) {
                    String fname = parser.getAttributeValue(null, "name");
                    int fversion = XmlUtils.readIntAttribute(parser, "version", 0);
                    boolean allowed;
                    if (!lowRam) {
                        allowed = true;
                    } else {
                    	// 内存不足时,配置了 notLowRam 的 feature 不会在加载!
                        String notLowRam = parser.getAttributeValue(null, "notLowRam");
                        allowed = !"true".equals(notLowRam);
                    }
                    if (fname == null) {
                        Slog.w(TAG, "<feature> without name in " + permFile + " at "
                                + parser.getPositionDescription());
                    } else if (allowed) {
						// 将feature构造成featureInfo,加入到mAvailableFeatures对象中!
                        addFeature(fname, fversion);
                    }
                    XmlUtils.skipCurrentTag(parser);
                    continue;

                }
                
                /*
                	解析"unavailable-feature",如果有则将其添加到mUnavailableFeatures中
                */ 
                else if ("unavailable-feature".equals(name) && allowFeatures) {
					
                    String fname = parser.getAttributeValue(null, "name");
                    if (fname == null) {
                        Slog.w(TAG, "<unavailable-feature> without name in " + permFile + " at "
                                + parser.getPositionDescription());
                    } else {
                        mUnavailableFeatures.add(fname);
                    }
                    XmlUtils.skipCurrentTag(parser);
                    continue;

                } 

				/*
					用于获得系统中处于省电模式白名单,而没有在 idle 模式白名单中的应用信息!
                    这些应用可以在后台运行!
				*/
				else if ("allow-in-power-save-except-idle".equals(name) && allowAll) {
					
                } 
                
				/*
					 解析 "allow-in-power-save" 标签
                	 获得哪些处于 doze 模式白名单中的应用信息!
				*/
				else if ("allow-in-power-save".equals(name) && allowAll) {
					...
                } 

				/*
					解析 "allow-in-data-usage-save" 标签,
                	获得哪些处于流量节省模式白名单中的应用的信息,在白名单中的应用,当系统处于
                    流量节省模式时,可以在后台运行!
				*/
				else if ("allow-in-data-usage-save".equals(name) && allowAll) {
					...
                } 

				/*
					解析app-link标签
				*/
                else if ("app-link".equals(name) && allowAppConfigs) {
					...
                } 

				/*
					     解析system-user-whitelisted-app标签
                		获得哪些在system user下可以运行的应用信息!
				*/
                else if ("system-user-whitelisted-app".equals(name) && allowAppConfigs) {
					...
                } 

				/*
					    解析system-user-blacklisted-app标签
                		获得哪些在system user下不可以运行的应用信息!
				*/
                else if ("system-user-blacklisted-app".equals(name) && allowAppConfigs) {
					...
                } else if ("default-enabled-vr-app".equals(name) && allowAppConfigs) {
					...
                } else if ("backup-transport-whitelisted-service".equals(name) && allowFeatures) {
					...
                } else if ("disabled-until-used-preinstalled-carrier-associated-app".equals(name)
                        && allowAppConfigs) {
					...
                } else {
					...
                }
            }
        } catch (XmlPullParserException e) {
            Slog.w(TAG, "Got exception parsing permissions.", e);
        } catch (IOException e) {
            Slog.w(TAG, "Got exception parsing permissions.", e);
        } finally {
            IoUtils.closeQuietly(permReader);
        }

		// 处理加密相关的feature
        if (StorageManager.isFileEncryptedNativeOnly()) {
            addFeature(PackageManager.FEATURE_FILE_BASED_ENCRYPTION, 0);
            addFeature(PackageManager.FEATURE_SECURELY_REMOVES_USERS, 0);
        }
		// 从mAvailableFeatures移除不支持的feature
        for (String featureName : mUnavailableFeatures) {
            removeFeature(featureName);
        }
    }

我们可以看到上述解析的标签何其多,不过没有关系,我们抓大放小(并不是说其它标签的内容不重要,而是用的比较少),在接下来的博客中我们只会重点对如下的几个标签解析重点分析:

  • group标签
  • permission”标签
  • assign-permission标签
  • library标签
  • feature标签
  • unavailable-feature标签

其它标签的解析套路也差不多,通过xml解析器得到标签的数据,然后存放在一定的数据结构中。这里限于篇幅和抓大放小的原则,就先过了。

在正式开始解析相关标签之前,我们先来看下SystemConfig中存放各种标签的数据结构,如下:

// [ SystemConfig.java ]
public class SystemConfig {

    // 存放从系统配置读取的所有gid
    int[] mGlobalGids;

    // 保存了uid和其所有的权限信息的映射关系
    final SparseArray<ArraySet<String>> mSystemPermissions = new SparseArray<>();


    // 系统所有的共享库信息,key是共享库的名称,value是共享库的路径
    final ArrayMap<String, String> mSharedLibraries  = new ArrayMap<>();


    // 系统中可用的feature信息
    final ArrayMap<String, FeatureInfo> mAvailableFeatures = new ArrayMap<>();

    // 系统中不可用的feature
    final ArraySet<String> mUnavailableFeatures = new ArraySet<>();

    public static final class PermissionEntry {
        public final String name;// 权限名称
        public int[] gids;		 // 该权限所属gid
        public boolean perUser;  //表示该权限的gid是否针对不同的userId做调整

        PermissionEntry(String name, boolean perUser) {
            this.name = name;
            this.perUser = perUser;
        }
    }


    // 保存了gid和其所拥有的权限的映射关系
    final ArrayMap<String, PermissionEntry> mPermissions = new ArrayMap<>();

	/*
		其它,其它,其它
	*/
    final ArraySet<String> mAllowInPowerSaveExceptIdle = new ArraySet<>();
    final ArraySet<String> mAllowInPowerSave = new ArraySet<>();
    final ArraySet<String> mAllowInDataUsageSave = new ArraySet<>();
    final ArraySet<String> mLinkedApps = new ArraySet<>();
    final ArraySet<String> mSystemUserWhitelistedApps = new ArraySet<>();
    final ArraySet<String> mSystemUserBlacklistedApps = new ArraySet<>();
    final ArraySet<ComponentName> mDefaultVrComponents = new ArraySet<>();
    final ArraySet<ComponentName> mBackupTransportWhitelist = new ArraySet<>();
    final ArrayMap<String, List<String>> mDisabledUntilUsedPreinstalledCarrierAssociatedApps =
            new ArrayMap<>();
2.5.1 解析group子标签获得系统定义的gid用户组

通过前面章节2.3我们可知group相关的xml内容通用格式如下(这里我们只是随意摘取了一个出来),可以看到group标签并不最外层标签的子标签而存在的,而是和 permission一起配合使用的,如下:

<permissions>
	<!-- 权限与gid的映射关系,譬如,以下内容表示属于net_bt这个组的用户都拥有android.permission.BLUETOOTH权限
	并且这里有一点需要注意的是,这里group可以有多个 -->
    <permission name="android.permission.BLUETOOTH" >
        <group gid="net_bt" />
    </permission>
<permissions、>    

我们直接看下它的解析过程,如下:

				// 根据标签和解析规则解析"group"标签
                if ("group".equals(name) && allowAll) {
					// 解析xml文件中定义的gid信息
                    String gidStr = parser.getAttributeValue(null, "gid");
                    if (gidStr != null) {
                        int gid = android.os.Process.getGidForName(gidStr);
                        mGlobalGids = appendInt(mGlobalGids, gid);
                    } else {
                        Slog.w(TAG, "<group> without gid in " + permFile + " at "
                                + parser.getPositionDescription());
                    }

                    XmlUtils.skipCurrentTag(parser);
                    continue;
                } 

我们可以看到根据组字符串gid的名称,通过调用android.os.Process.getGidForName转为int类型的gid,保存到 mGlobalGids数组中,并且这里的getGidForName是一个本地方法,最后会调用到android_util_Process.cpp函数中。

这里补充一个知识点,可以看到在这里定义了如果的group,那么在我们的Android终端中要怎么具体查看呢,我们可以通过终端中执行groups命令来进行(详情读者可以参见博客linux如何查看所有的用户和组信息?),如下:

在这里插入图片描述
可以看到我们解析的net_bt赫然在列,并且当前我是root用户登录的,所以不要惊讶没有看到system和shell用户组!

至于Android支持那些用户组,详见system/core/include/private/android_filesystem_config.h头文件,可以看到其中有定义了一大推的(好像有点扯远了啊),如下:

// [ android_filesystem_config.h ]
#define AID_ROOT             0  /* traditional unix root user */

#define AID_SYSTEM        1000  /* system server */

#define AID_RADIO         1001  /* telephony subsystem, RIL */
#define AID_BLUETOOTH     1002  /* bluetooth subsystem */
#define AID_GRAPHICS      1003  /* graphics devices */
#define AID_INPUT         1004  /* input devices */
#define AID_AUDIO         1005  /* audio devices */
#define AID_CAMERA        1006  /* camera devices */
#define AID_LOG           1007  /* log devices */
#define AID_COMPASS       1008  /* compass device */
#define AID_MOUNT         1009  /* mountd socket */
#define AID_WIFI          1010  /* wifi subsystem */
#define AID_ADB           1011  /* android debug bridge (adbd) */
2.5.2 解析permission标签获得系统权限和所属的gid用户组对应关系

前面已经解决了一个标签,战斗还在持续,我们继续下一个标签的解决,这里我们还是简单奉上解析的xml标签片段,如下:

<permissions>
	...
    <permission name="android.permission.BLUETOOTH_ADMIN" >
        <group gid="net_bt_admin" />
    </permission>
    <permission name="android.permission.WRITE_EXTERNAL_STORAGE" />
    ...
<permissions/>

我们直接看下它的解析过程,如下:

// [ SystemConfig.java ]
				// 根据标签和解析规则解析"permission"标签
				else if ("permission".equals(name) && allowPermissions) {
					// 解析系统中所有权限和其所属gid的信息
                    String perm = parser.getAttributeValue(null, "name");
                    if (perm == null) {// 注意这里处理的就是类似android.permission.WRITE_EXTERNAL_STORAGE这种
                        Slog.w(TAG, "<permission> without name in " + permFile + " at "
                                + parser.getPositionDescription());
                        XmlUtils.skipCurrentTag(parser);
                        continue;
                    }
                    perm = perm.intern();
					// 调用readPermission继续解析权限
                    readPermission(parser, perm);

                } 

上述源码经过一番简单解析后,继续调用readPermission方法进行下一步的处理,我们继续跟进该方法:

    void readPermission(XmlPullParser parser, String name)
            throws IOException, XmlPullParserException {
        // 检查mPermissions列表,避免重复添加
        if (mPermissions.containsKey(name)) {
            throw new IllegalStateException("Duplicate permission definition for " + name);
        }

		/*
			perUser表示该权限的gid是否针对设备用户id进行调整,默认为false
			并且在我们举例中,也并没有定义这个属性
		*/
        final boolean perUser = XmlUtils.readBooleanAttribute(parser, "perUser", false);
		
		// 创建PermissionEntry实例,并添加到mPermissions中
        final PermissionEntry perm = new PermissionEntry(name, perUser);
        mPermissions.put(name, perm);

        int outerDepth = parser.getDepth();
        int type;
        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
               && (type != XmlPullParser.END_TAG
                       || parser.getDepth() > outerDepth)) {
            if (type == XmlPullParser.END_TAG
                    || type == XmlPullParser.TEXT) {
                continue;
            }

            String tagName = parser.getName();
			// 解析内部group标签,获得权限所属的gid,将其添加到PermissionEntry的gids数组中
            if ("group".equals(tagName)) {
                String gidStr = parser.getAttributeValue(null, "gid");
                if (gidStr != null) {
					// 为啥前面要搞个单独解析group呢,直接复用这里不OK
                    int gid = Process.getGidForName(gidStr);
                    perm.gids = appendInt(perm.gids, gid);
                } else {
                    Slog.w(TAG, "<group> without gid at "
                            + parser.getPositionDescription());
                }
            }
            XmlUtils.skipCurrentTag(parser);
        }
    }

可以看到在该方法中,主要解析permission标签以及它对应的内部标签group,得到gid和权限之间的对应关系,并将这种关系保存到SystemConfig的内部数据结构mPermissions中。并且这里我们有一点需要特别注意注意,一个权限可以对应多个gid,形如下面这个关系:

<permissions>
	...
    <permission name="android.permission.BLUETOOTH_STACK" >
        <group gid="bluetooth" />
        <group gid="wakelock" />
        <group gid="uhid" />
    </permission>
    ...
<permissions/>
2.5.3 解析assign-permission标签获得uid和其持有的权限关系

又攻破了一个重要的标签,战斗还在持续,我们继续下一个标签的解决,这里我们还是简单奉上解析的xml标签片段,如下:

<permissions>
	...
    <assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="audioserver" />
    <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="audioserver" />
    <assign-permission name="android.permission.WAKE_LOCK" uid="audioserver" />
    <assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="audioserver" />
    <assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="audioserver" />
    ...
<permissions/>

我们直接看下它的解析过程,如下:

// [ SystemConfig.java ]
				/*
					 如果allowPermissions为true,解析"assign-permission"标签
					 解析系统中uid和其所持有的权限的关系!
				*/
				else if ("assign-permission".equals(name) && allowPermissions) {  
                    String perm = parser.getAttributeValue(null, "name");//权限名称
                    if (perm == null) {
                        Slog.w(TAG, "<assign-permission> without name in " + permFile + " at "
                                + parser.getPositionDescription());
                        XmlUtils.skipCurrentTag(parser);
                        continue;
                    }
                    String uidStr = parser.getAttributeValue(null, "uid");// 拥有该权限的uid
                    if (uidStr == null) {
                        Slog.w(TAG, "<assign-permission> without uid in " + permFile + " at "
                                + parser.getPositionDescription());
                        XmlUtils.skipCurrentTag(parser);
                        continue;
                    }
                    int uid = Process.getUidForName(uidStr);// 将uid转为init值
                    if (uid < 0) {
                        Slog.w(TAG, "<assign-permission> with unknown uid \""
                                + uidStr + "  in " + permFile + " at "
                                + parser.getPositionDescription());
                        XmlUtils.skipCurrentTag(parser);
                        continue;
                    }
                    perm = perm.intern();
                    ArraySet<String> perms = mSystemPermissions.get(uid);
                    if (perms == null) {
                        perms = new ArraySet<String>();
                        mSystemPermissions.put(uid, perms);
                    }
                    perms.add(perm);
                    XmlUtils.skipCurrentTag(parser);

                } 

对于assign-permission标签的解析和前面的permission标签有所差别!这里将解析assign-permission标签,获得uid和权限对应的映射关系,并将解析结果保存到了mSystemPermissions中,这里需要特别注意的一个点就是uid 可以对应多个权限,即它们是一对多的关系。

Android中预先指定了许多默认的uid,它们被定义在Process.java中,如下:

在这里插入图片描述

2.5.4 解析library标签获得共享库名称和映射关系

又攻破了一个重要的标签,战斗还在持续,我们继续下一个标签的解决,这里我们还是简单奉上解析的xml标签片段,如下:

<permissions>
	...
	<!-- 原始排版不顺眼,我必须得给掰直了-->
    <library name="android.test.runner" file="/system/framework/android.test.runner.jar" />
    ...
<permissions/>

我们直接看下它的解析过程,如下:

// [ SystemConfig.java ]
                /*
                	如果 allowLibs为true,则解析"library"标签
                	从而获得系统中所有共享库的信息!
                */
				else if ("library".equals(name) && allowLibs) {
                    String lname = parser.getAttributeValue(null, "name");
                    String lfile = parser.getAttributeValue(null, "file");
                    if (lname == null) {
                        Slog.w(TAG, "<library> without name in " + permFile + " at "
                                + parser.getPositionDescription());
                    } else if (lfile == null) {
                        Slog.w(TAG, "<library> without file in " + permFile + " at "
                                + parser.getPositionDescription());
                    } else {
                        //Log.i(TAG, "Got library " + lname + " in " + lfile);
                        mSharedLibraries.put(lname, lfile);
                    }
                    XmlUtils.skipCurrentTag(parser);
                    continue;

                } 

上述代码片段通过解析library标签,得到共享库的名称和路径相关信息并将其保存到mSharedLibraries哈希表中,其中的key表示共享库名称,value为共享库路径。

2.5.5 解析feature和unavailable-feature标签

爆破组很累啊,攻克了一个又是一个,战斗还在持续,我们继续下一个标签的解决,这里我们还是简单奉上解析的xml标签片段,如下:

<permissions>
	...
    <feature name="android.hardware.bluetooth" />
    ...
<permissions/>

我们直接看下它的解析过程,如下:

// [SystemConfig.java]
				/*
					 解析feather子标签
				*/
				else if ("feature".equals(name) && allowFeatures) {
                    String fname = parser.getAttributeValue(null, "name");
                    int fversion = XmlUtils.readIntAttribute(parser, "version", 0);
                    boolean allowed;
                    if (!lowRam) {
                        allowed = true;
                    } else {
                    	// 内存不足时,配置了nototLowRam的feature不会在加载!
                        String notLowRam = parser.getAttributeValue(null, "notLowRam");
                        allowed = !"true".equals(notLowRam);
                    }
                    if (fname == null) {
                        Slog.w(TAG, "<feature> without name in " + permFile + " at "
                                + parser.getPositionDescription());
                    } else if (allowed) {
						// 将feature构造成featureInfo,加入到mAvailableFeatures对象中!
                        addFeature(fname, fversion);
                    }
                    XmlUtils.skipCurrentTag(parser);
                    continue;

                }
                
                /*
                	解析"unavailable-feature",如果有则将其添加到mUnavailableFeatures中
                */ 
                else if ("unavailable-feature".equals(name) && allowFeatures) {
					
                    String fname = parser.getAttributeValue(null, "name");
                    if (fname == null) {
                        Slog.w(TAG, "<unavailable-feature> without name in " + permFile + " at "
                                + parser.getPositionDescription());
                    } else {
                        mUnavailableFeatures.add(fname);
                    }
                    XmlUtils.skipCurrentTag(parser);
                    continue;

                } 

在上面的源码中会把配置文件中的feature和unavailable-feature标签解析出来,可用feature会被添加到 mAvailableFeatures中,不可用feature会被添加到mUnavailableFeatures中。

这里的feature通常和硬件特性有关,譬如是否支持蓝牙,nfc啊等等!

2.5.6 其它标签的解析

其它,其它就不解析了,解析得都要吐血了!得来个肾宝,补一补!


2.6 SystemConfig解析系统配置文件小结

至此SystemConfig类以及它解析相关配置文件的流程就到这里告一段落了,此时我们可以清晰的得到SystemConfig内部的数据关系图了,如下:

在这里插入图片描述

如果说Settings类是应用安装信息相关文件的管理类大管家,那么这里的SystemConfig应该说就是系统相关配置文件的大管家,二者在PMKS其中扮演着非常重要的角色,实力不容小觑!

在最后我们通过一张它的流程图,来完成SystemConfig解析系统配置文件小结!

在这里插入图片描述


2.7 PKMS继续处理解析完成的SystemConfig数据管家类

不知不觉,我们进入SystemConfig的分支处理中已经很久很久,久到离谱了!我们继续回到PKMS的构造方法的BOOT_PROGRESS_PMS_START发展阶段,看看对于完成解析满血状态的SystemConfig数据管家类进行怎么处理的:

这里我们为了排版和博客的连贯性,会跳过源码的顺序,将后续PKMS对于SystemConfig的处理逻辑放在一起来分析!

// [ PackageManagerService.java ]
        mGlobalGids = systemConfig.getGlobalGids();
        mSystemPermissions = systemConfig.getSystemPermissions();
        mAvailableFeatures = systemConfig.getAvailableFeatures();

这里将SystemConfig中的mGlobalGids,mSystemPermissions和mAvailableFeatures拷贝一份保存到PKMS中,我们接着继续往下看相关处理逻辑:

            //读取权限配置文件中的信息,保存到mPermissions这个ArrayMap中
            ArrayMap<String, SystemConfig.PermissionEntry> permConfig
                    = systemConfig.getPermissions(); // 【2.7】
            for (int i=0; i<permConfig.size(); i++) {
                SystemConfig.PermissionEntry perm = permConfig.valueAt(i);
                // 此时mSettings并没有开始解析packages.xml等相关文件,所以它里面的数据结构此时是没有任何内容的
                BasePermission bp = mSettings.mPermissions.get(perm.name);
                // 加入到Settings的mPermissions中,SystemConfig解析的权限的包名都为android
                if (bp == null) {
                	// BasePermission.TYPE_BUILTIN表示的是在编译时期就确定好的,系统要提供的权限!
                    bp = new BasePermission(perm.name, "android", BasePermission.TYPE_BUILTIN); //详见 【2.7.2】
                    mSettings.mPermissions.put(perm.name, bp);
                }
                if (perm.gids != null) {
                	/*           
                		如果系统权限有所属的gids,将其添加到BasePermission对象中
                		这个逻辑比较简单就不展开了
                	*、
                    bp.setGids(perm.gids, perm.perUser);
                }
            }

			//获取并处理所有共享库信息
            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));
            }

这里可以看到PKMS继续处理权限信息和共享库信息的逻辑,将从SystemConfig中解析出来的mPermissions信息保存到Setting的mPermissions中,并且将权限所属的包名指定为android,这也是packages.xml中<permissions>标签数据的由来。

在此处有几个关键点我们需要理解:
1. 此时mSettings并没有开始解析packages.xml等相关文件,所以它里面的数据结构mPermissions此时是没有任何内容的
2. 此时SystemConfig.mPermissions中保存的是系统中定义的权限和对应的gid之间的映射关系

2.7.1 PKMS授权机制简介

在开始BasePermission的介绍前,非常必须且有必要先来简单介绍下PKMS的授权机制的设计,它设计的最最主要用途是用来判定是否授权以及授权类型。

Android中,有权限持有者和权限申请者两个角色,一个Android包可以扮演其中一种角色,或两种角色兼备。持有者通过设计权限来保护接口和数据,申请者如果要访问受保护的接口和数据时,需要事先声明,然后交由包管理者来判断是否要授权。

对于权限持有者而言,可以通过protectionLevel属性定义了权限的受保护级别,其取值主要有以下四种:

  • normal(0): 最普通的一类权限,只要申请使用这一类权限就授予。
  • dangerous(1): 较为危险的一类权限,譬如访问联系人、获取位置服务等权限,需要经过用户允许才授予。
  • signature(2): 如果申请者与该权限的持有者签名相同,则授予这类权限。
  • signatureOrPrivileged(18): 对singature的一个补充,权限申请者与持有者签名相同,或者申请者是位于/system/priv-app目录下的应用,则授予这类权限。在早期的Android版本,所有系统应用都位于/system/app目录下,其定义为signatureOrSystem(3),但该定义已经过时了;当Android引入了/system/priv-app目录以后,就将这一类保护级别重新定义为signatureOrPrivileged(18)。

并且对于权限持有者而言,会在它对外提供的API接口中,通过调用类似checkPermission方法检测调用者是否拥有权限,如果没有则会提示对应信息或者抛出异常!


有了上面的权限级别限制,就可以理解,部分权限安装时就被授予,而部分权限需要申请者满足一定的条件才能被授予,从Android M(6.0)开始,对最终授予的权限进行了分类:

  • install:安装时授予的权限。normal、signature、signatureOrPrivilege的授予都属于这一类。
  • runtime:运行时由用户决定是否授予的权限。在Android M(6.0)以前,dangerous的权限属于install类型,但从Android M(6.0)以后,dangerous的权限改为属于runtime一类了。在使用这类权限时,会弹出一个对话框,让用户选择是否授权。

注意,授权类型是Android M(6.0)才引进的,之前只有是否授权的区分。之所以做授权类型的区分,是为了适应多用户使用的场景,install类型的授权,对所有用户都是一样的;runtime类型的授权,不同用户使用的选择不一样,譬如一个用户在使用的会授予某个应用访问联系人数据的权限,但另一个用户使用时会选择拒绝授权。

2.7.2 BasePermission权限持有者封装类

为了对权限持有者申明的权限进行管理特意构建了BasePermission数据结构对其进行管理。好了,有了以上的初步了解我们直接从源码层面来分析一下BasePermission,上源码!

// [ BasePermission.java ]

	private int[] gids;// 拥有该权限的gid组
    BasePermission(String _name, String _sourcePackage, int _type) {
        name = _name;// 权限名
        sourcePackage = _sourcePackage;// 定义权限的包名,即权限的持有者
        type = _type;// 权限类型
        
        protectionLevel = PermissionInfo.PROTECTION_SIGNATURE;//权限的级别,这个前面有提到过了
    }

并且Android系统将权限分为如下有三种类型进行管理:

    final static int TYPE_NORMAL = 0;//通用类型

    final static int TYPE_BUILTIN = 1;//构建类型(这种类型的权限,通常必须只能系统层才能被定义)

    final static int TYPE_DYNAMIC = 2;//动态类型

最终我们得到了它的类图,如下

在这里插入图片描述


2.8 PKMS处理SystemConfig数据管家小结

至此PKMS对于SystemConfig大管家的读取操作到这里就基本结束了,通过前面的分析我们可以得出如下的结论就是SystemConfig保存的均是和系统相关的属性信息,比如系统权限,系统特性,gid等等。而通过后续的分析我们会知道Settings应用安装信息管家不仅会保存系统的也会保存安装应用所有的信息,所以这里需要将SystemConfig中的权限信息拷贝一份到Settings中。

在对本篇博客余下逻辑分析前,我们必须且必要对于SystemConfig、Settings以及PKMS之间牵涉的数据关系之间的映射进行一下阶段性的总结,如下:

在这里插入图片描述

这里最最重要的一点就是通过SystemConfig的解析,我们可以得到一些系统定义的一些配置信息:

  • 系统定义的一些gid;
  • 系统定义的权限和uid的映射关系;
  • 系统定义的权限和gid的映射关系;
  • 系统共享库和feature;

这里给读者抛出一个问题?就是为什么PKMS中要重复设计一套和SystemConfig中相同的数据结构来将它备份一遍呢,而不是直接使用SystemConfig中的呢,这样不是浪费内存空间吗(虽然这点数据空间微不足道)!读者朋友可以带这个这个疑问继续接下来的相关分析,我想你会找到满意的答案的。


2.9 构建PackageHandler实例以及绑定消息处理线程

在该逻辑中我们会创建PackageHandler对象,将其绑定到一个ServiceThread后台线程的消息队列中,并加入到Watchdog的监控列表中。

这里为啥PackageHandler绑定的不是PKMS的主线程呢?这是为什么呢?
如果读者对应用的安装整个流程有一定了解的话,应该知道应用的安装和卸载是一个比较耗时的操作,所以这些厚重的耗时操作必须开辟专门的线程来处理,就交由这个后台线程完成了。并且由于PKMS是一个重要的系统服务,这个后台线程的消息队列如果过于忙碌,则会导致系统一直卡住,所以需要将这个消息队列加入Watchdog的监控列表,以便在这种情况下,Watchdog可以做出一些应急操作,譬如重启PKMS所属的system_serverin进程等相关操作。

这里我们对该逻辑简单分析一下:

// [ PackageManagerService.java ]
	/*
		 建立并启动一个名为 “PackageManager” 的 ServiceThread,用于处理一些耗时的操作!
	*/
	mHandlerThread = new ServiceThread(TAG,
	            Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
	mHandlerThread.start();
	/*
		 创建一个 PackageHandler对象,绑定前面创建的 HandlerThread!
	*/	 
	mHandler = new PackageHandler(mHandlerThread.getLooper());// 【 2.9.1 ]

这里的ServiceThread继承HandlerThread,专门为系统服务定义的(从它的名称就可以看出该Thread是用来和Handler进行绑定的)!

2.9.1 PackageHandler的消息处理

PackageHandler归根究底是一个Handler子类,所以一定逃不出真香定律,通常我们对于Hhandler重点要关注的是它的handleMessage方法,我们简单来看看它处理了那些消息。

// [ PackageManagerService.java ]
    class PackageHandler extends Handler {
    	...
        public void handleMessage(Message msg) {
            try {
                doHandleMessage(msg);
            } finally {
				...
            }
        }
        void doHandleMessage(Message msg) {
            switch (msg.what) {
                case INIT_COPY: {// 安装拷贝阶段
                	...
                }
                case MCS_BOUND: { //绑定阶段
                	...
                }
                case MCS_RECONNECT: {
                	...
                }
                case MCS_UNBIND: {
                	...
                }
                case SEND_PENDING_BROADCAST: {
                	...
                }
                case START_CLEANING_PACKAGE: {
                	...
                }
				...
             } 
        }  

    }

这里我们可以看到关于应用安装,卸载处理的一些耗时的操作,PKMS都会放在Handler中进行相关的处理,这个逻辑在后续分析第三方应用的安装和卸载中会很明显的看到。

分析完成之后,我们可以得到如下的PackageHandler消息循环构建的时序图:

PackageHandler


2.10 构建UserManagerService用户管理服务

尼玛,我要被整吐了PKMS启动BOOT_PROGRESS_PMS_START发展阶段的内容好多啊,尼玛太累了。分析源码吗,感觉囫囵吞枣也不行,匆匆带过也不行,难啊!此阶段还剩最好一个逻辑就是构建UserManagerService用户管理服务,我们简单来看下逻辑:

// [ UserManagerService.java ]
    private UserManagerService(Context context, PackageManagerService pm,
            Object packagesLock, File dataDir) {
        mContext = context;
        mPm = pm;
        mPackagesLock = packagesLock;
        mHandler = new MainHandler();
        synchronized (mPackagesLock) {
        	// mUsersDir=/data/system/users
            mUsersDir = new File(dataDir, USER_INFO_DIR);
            mUsersDir.mkdirs();
            // userZeroDir=/data/system/users/0
            File userZeroDir = new File(mUsersDir, String.valueOf(UserHandle.USER_SYSTEM));
            userZeroDir.mkdirs();
            FileUtils.setPermissions(mUsersDir.toString(),
                    FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH,
                    -1, -1);
            // mUserListFile=/data/system/users/userlist.xml
            mUserListFile = new File(mUsersDir, USER_LIST_FILENAME);
            initDefaultGuestRestrictions();
            readUserListLP();
            sInstance = this;
        }
        mLocalService = new LocalService();
        LocalServices.addService(UserManagerInternal.class, mLocalService);
        mLockPatternUtils = new LockPatternUtils(mContext);
        mUserStates.put(UserHandle.USER_SYSTEM, UserState.STATE_BOOTING);
    }

它的构造构造过程比较简单,就是创建几个目录和几个文件:

  • /data/system/users

  • /data/system/users/0

  • /data/system/users/userlist.xml

接着通过调用readUserList方法读取用户列表(这里就不对该方法展开了),该方法就是从userlist.xml文件中读取用户信息,保存到UserManager的成员变量mUsers中。




三.PKMS启动BOOT_PROGRESS_PMS_START高潮阶段

好了,前面阶段分析完毕,我们开始下一阶段的分析,在这一阶段中我们的重中之重是解析packages.xml文件将其读取到对应Settings数据结构中进行相应的管理,这里我们还是按照源码逻辑顺序进行分析!


3.1 SELinuxMMAC.readInstallPolicy解析selinux配置文件

该方法会调用SELinuxMMAC.readInstallPolicy读取/etc/security/mac_permissions.xml文件中的 SELinux配置,设置应用程序的安全上下文!

对于SELinux还不是很熟悉的读者,可以参见系列博客Android SELinux开发指南,并且SELinux也不是我们这里重点内容,属于选学的,这个部分大家了解一下就OK了。

// [SELinuxMMAC.java]
    private static final File MAC_PERMISSIONS = new File(Environment.getRootDirectory(),
            "/etc/security/mac_permissions.xml");
    public static boolean readInstallPolicy() {
        List<Policy> policies = new ArrayList<>();

		// 解析/etc/security/mac_permissions.xml文件
        FileReader policyFile = null;
        XmlPullParser parser = Xml.newPullParser();
        try {
            policyFile = new FileReader(MAC_PERMISSIONS);
            Slog.d(TAG, "Using policy file " + MAC_PERMISSIONS);

            parser.setInput(policyFile);
            parser.nextTag();
            parser.require(XmlPullParser.START_TAG, null, "policy");

            while (parser.next() != XmlPullParser.END_TAG) {
                if (parser.getEventType() != XmlPullParser.START_TAG) {
                    continue;
                }

                switch (parser.getName()) {
                    case "signer": // 解析 "signer" 标签的内容,返回一个 Policy 对象!
                        policies.add(readSignerOrThrow(parser));
                        break;
                    default:
                        skip(parser);
                }
            }
        } catch (IllegalStateException | IllegalArgumentException |
                XmlPullParserException ex) {
			...
            return false;
        } catch (IOException ioe) {
            Slog.w(TAG, "Exception parsing " + MAC_PERMISSIONS, ioe);
            return false;
        } finally {
            IoUtils.closeQuietly(policyFile);
        }

        // 使用比较器对其进行排序!
        PolicyComparator policySort = new PolicyComparator();
        Collections.sort(policies, policySort);
        if (policySort.foundDuplicate()) {
            Slog.w(TAG, "ERROR! Duplicate entries found parsing " + MAC_PERMISSIONS);
            return false;
        }

        synchronized (sPolicies) {
            sPolicies = policies;
        }

        return true;
    }

在分析源码之前,我们先来看下其中牵涉的一个文件/etc/security/mac_permissions.xml,在我进行测试的终端中它的内容如下:

<?xml version="1.0" encoding="iso-8859-1"?><!-- AUTOGENERATED FILE DO NOT MODIFY -->
<policy>
	<signer signature="308204a830820390a003020102020900b3998086d056cffa300d06092a864886f70d0101040500308194310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d301e170d3038303431353232343035305a170d3335303930313232343035305a308194310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d30820120300d06092a864886f70d01010105000382010d003082010802820101009c780592ac0d5d381cdeaa65ecc8a6006e36480c6d7207b12011be50863aabe2b55d009adf7146d6f2202280c7cd4d7bdb26243b8a806c26b34b137523a49268224904dc01493e7c0acf1a05c874f69b037b60309d9074d24280e16bad2a8734361951eaf72a482d09b204b1875e12ac98c1aa773d6800b9eafde56d58bed8e8da16f9a360099c37a834a6dfedb7b6b44a049e07a269fccf2c5496f2cf36d64df90a3b8d8f34a3baab4cf53371ab27719b3ba58754ad0c53fc14e1db45d51e234fbbe93c9ba4edf9ce54261350ec535607bf69a2ff4aa07db5f7ea200d09a6c1b49e21402f89ed1190893aab5a9180f152e82f85a45753cf5fc19071c5eec827020103a381fc3081f9301d0603551d0e041604144fe4a0b3dd9cba29f71d7287c4e7c38f2086c2993081c90603551d230481c13081be80144fe4a0b3dd9cba29f71d7287c4e7c38f2086c299a1819aa48197308194310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d820900b3998086d056cffa300c0603551d13040530030101ff300d06092a864886f70d01010405000382010100572551b8d93a1f73de0f6d469f86dad6701400293c88a0cd7cd778b73dafcc197fab76e6212e56c1c761cfc42fd733de52c50ae08814cefc0a3b5a1a4346054d829f1d82b42b2048bf88b5d14929ef85f60edd12d72d55657e22e3e85d04c831d613d19938bb8982247fa321256ba12d1d6a8f92ea1db1c373317ba0c037f0d1aff645aef224979fba6e7a14bc025c71b98138cef3ddfc059617cf24845cf7b40d6382f7275ed738495ab6e5931b9421765c491b72fb68e080dbdb58c2029d347c8b328ce43ef6a8b15533edfbe989bd6a48dd4b202eda94c6ab8dd5b8399203daae2ed446232e4fe9bd961394c6300e5138e3cfd285e6e4e483538cb8b1b357">
	<seinfo value="platform"/>
	</signer>
</policy>

我们知道在Android引入SELinux规则以后,Android会为每个文件打上标签(SELabel),对于APK而言,打标签(SELabel)的准则就是签名,即根据签名信息打上不同的标签(SELabel)。我们知道Android将签名分类成为platform,testkey,media等,所以Android特意设计了一个mac_permission.xml类型的文件来存储这种映射的关系。所以,我们这里需要读取该文件的内容来解析这种映射关系。

关于此处我也还有一个疑点,此时mac_permission.xml文件并没有定义media等对应的映射关系,media签名的应用是怎么被打上标签的呢?如果对于此处有比较熟悉读者可以告知一下,这个地方我就没有特意花费太多的时间进行分析了!关于seinfo感兴趣的读者可以参见博客Android-N app seinfo设置流程深入理解SELinux/SEAndroid中对于其有比较详细的分析。

并且根据mac_permissions.xml的定义可知,如果App是在Android源码编译环境下,其Android.mk中指定了LOCAL_CERTIFICATE : = platform的话,它的seinfo就是platform。如果Android.mk中不进行对应的设置,setinfo为默认值default。通常对于对于第三方的APK,其seinfo值通常为default。关于各个应用的seinfo的取值,我们可以通过ps -z命令进行相关的查看,如下:

在这里插入图片描述

并且这里还需要补充一点,mac_permission.xml是怎么生成的呢?通过全局搜索,最终发现它被定义在Android源码的system/sepolicy/mac_permissions.xml下,如下:

在这里插入图片描述
读者是不是发现了什么蹊跷呢,为啥signature的取值不一样呢?这是因为当mac_permissions.xml编译进/etc/security/目录时,@PLATFORM 将被实际的签名信息替换,这个地方读者注意一下就好了!


3.2 Settings.readLPw解析上次应用安装相关信息

前方高能预警,虽然此处只有简简单单的一行代码,但是它发散开来的信息量就比较大了。所以强烈建议读者该上厕所的上厕所,该休息的休息!然后再挨着饱满的热情来攻克这个硬骨头!在没有开啃之前,先奉上成果,以资奖励!
在这里插入图片描述

闲话少说,我们直接看源码:

// [ PackageManagerService.java ]
			//读取文件package.xml的内容,解析后存入mSettings的mPackages中
			//并且根据解析的结果判断是否是第一次启动,如果是第一次开机,那么是没有相关数据的
			mFirstBoot = !mSettings.readLPw(sUserManager.getUsers(false));

这个方法主要用来读取上次应用安装的信息,我们展开来看:

// [ Settings.java ]
	/*
		如果不是第一次开机则获取上次安装的信息
		如果是第一次开机,则创建对应的packages.xml等文件
	*/
	// 这里传入的参数和多用户有关,我们在学习的时候可以忽略Android终端多用户的情况
    boolean readLPw(@NonNull List<UserInfo> users) {
        FileInputStream str = null;
		// 如果packages-backup.xml存在,就从packages-backup.xml中读取
        if (mBackupSettingsFilename.exists()) {
            try {
                str = new FileInputStream(mBackupSettingsFilename);
                mReadMessages.append("Reading from backup settings file\n");
                PackageManagerService.reportSettingsProblem(Log.INFO,
                        "Need to read from backup settings file");

				// 如果packages.xml存在,删除
                if (mSettingsFilename.exists()) {
                    Slog.w(PackageManagerService.TAG, "Cleaning up settings file "
                            + mSettingsFilename);
                    mSettingsFilename.delete();
                }
            } catch (java.io.IOException e) {
                
            }
        }

		// 清空一些集合,防止异常问题
        mPendingPackages.clear();
        mPastSignatures.clear();
        mKeySetRefs.clear();
        mInstallerPackages.clear();

        try {
			// 如果packages-backup.xml不存在,就从packages.xml中读取! 
            if (str == null) {
				// 如果packages.xml文件不存在,则直接返回
                if (!mSettingsFilename.exists()) {
                    mReadMessages.append("No settings file found\n");
                    PackageManagerService.reportSettingsProblem(Log.INFO,
                            "No settings file; creating initial state");
                    findOrCreateVersion(StorageManager.UUID_PRIVATE_INTERNAL);
                    findOrCreateVersion(StorageManager.UUID_PRIMARY_PHYSICAL);
                    return false;
                }
                str = new FileInputStream(mSettingsFilename);
            }
            XmlPullParser parser = Xml.newPullParser();
            parser.setInput(str, StandardCharsets.UTF_8.name());

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

			...
			
			// 开始解析上次所有应用安装信息
            int outerDepth = parser.getDepth();
            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                    continue;
                }

                String tagName = parser.getName();
				/*
					调用不同的解析方法,解析packages.xml中相关的标签内容,关于packages.xml文件知识储备详见章节【 3.3 】
				*/
				// 解析<package>标签内容,注意它实际执行的顺序在解析<permission>之后,详见章节 【 3.4 】
                if (tagName.equals("package")) {
                    readPackageLPw(parser);
                } else if (tagName.equals("permissions")) {// 解析<permission>标签,详见章节 【 3.5 】
                    readPermissionsLPw(mPermissions, parser);
                } else if (tagName.equals("permission-trees")) { // 解析<permission-trees>标签
                    readPermissionsLPw(mPermissionTrees, parser);
                } else if (tagName.equals("shared-user")) {// 解析<shared-user>标签
                    readSharedUserLPw(parser);
                } else if (tagName.equals("preferred-packages")) {
                } else if (tagName.equals("preferred-activities")) {//解析 <preferred-activities>标签
                    readPreferredActivitiesLPw(parser, 0);
                } else if (tagName.equals(TAG_PERSISTENT_PREFERRED_ACTIVITIES)) {
                    ...
                } else if (tagName.equals(TAG_CROSS_PROFILE_INTENT_FILTERS)) {
                    ...
                } else if (tagName.equals(TAG_DEFAULT_BROWSER)) { // 解析<default-browser>标签
                    readDefaultAppsLPw(parser, 0);
                } else if (tagName.equals("updated-package")) { // 解析<updated-package>标签,详见章节 【 3.7 】
                    readDisabledSysPackageLPw(parser);
                } else if (tagName.equals("cleaning-package")) { // 解析<cleaning-package>标签
                    String name = parser.getAttributeValue(null, ATTR_NAME);
                    String userStr = parser.getAttributeValue(null, ATTR_USER);
                    String codeStr = parser.getAttributeValue(null, ATTR_CODE);
                    if (name != null) {
                        int userId = UserHandle.USER_SYSTEM;
                        boolean andCode = true;
                        try {
                            if (userStr != null) {
                                userId = Integer.parseInt(userStr);
                            }
                        } catch (NumberFormatException e) {
                        }
                        if (codeStr != null) {
                            andCode = Boolean.parseBoolean(codeStr);
                        }
                        addPackageToCleanLPw(new PackageCleanItem(userId, name, andCode));
                    }
                } else if (tagName.equals("renamed-package")) { // 解析 <renamed-package>标签
                    String nname = parser.getAttributeValue(null, "new");
                    String oname = parser.getAttributeValue(null, "old");
                    if (nname != null && oname != null) {
                        mRenamedPackages.put(nname, oname);
                    }
                } else if (tagName.equals("restored-ivi")) {
                   	...
                } else if (tagName.equals("last-platform-version")) {
					...
                } else if (tagName.equals("database-version")) { // 解析<database-version>标签
					...
                } else if (tagName.equals("verifier")) {
					...
                } else if (TAG_READ_EXTERNAL_STORAGE.equals(tagName)) {
					...
                } else if (tagName.equals("keyset-settings")) {// 解析<keyset-settings>标签
                    ...
                } else if (TAG_VERSION.equals(tagName)) {// 解析<version>标签
					...
                } else {
					...
                    XmlUtils.skipCurrentTag(parser);
                }
            }

            str.close();

        } catch (XmlPullParserException e) {
			...
        } catch (java.io.IOException e) {
			...
        }

		...

		/*************** 至此标签解析结束,进行相关的收尾工作 ******************/

		/*  接着,对mPendingPackages集合中存放着的需要
			验证共享用户id有效性的package,进行共享用户id有效性的验证
			关于此处逻分析详见章节 【 3.9 】
		*/
        final int N = mPendingPackages.size();

        for (int i = 0; i < N; i++) {
            final PendingPackage pp = mPendingPackages.get(i);

			// 看sharedId是否能对应找到一个ShardUserSetting对象!
            Object idObj = getUserIdLPr(pp.sharedId);
            if (idObj != null && idObj instanceof SharedUserSetting) { // 能找到,说明共享用户ID是有效的!

				// 创建PackageSetting
				PackageSetting p = getPackageLPw(pp.name, null, pp.realName,
                        (SharedUserSetting) idObj, pp.codePath, pp.resourcePath,
                        pp.legacyNativeLibraryPathString, pp.primaryCpuAbiString,
                        pp.secondaryCpuAbiString, pp.versionCode, pp.pkgFlags, pp.pkgPrivateFlags,
                        null, true /* add */, false /* allowInstall */, pp.parentPackageName,
                        pp.childPackageNames);
                if (p == null) {
                    PackageManagerService.reportSettingsProblem(Log.WARN,
                            "Unable to create application package for " + pp.name);
                    continue;
                }
				// 将 PendingPackage的其他数据拷贝到PackageSetting中!
                p.copyFrom(pp);// 保存到Settings.mPackages对象中,表明ID有效,已经分配ID了
            } else if (idObj != null) {
                String msg = "Bad package setting: package " + pp.name + " has shared uid "
                        + pp.sharedId + " that is not a shared uid\n";
                mReadMessages.append(msg);
                PackageManagerService.reportSettingsProblem(Log.ERROR, msg);
            } else {
                String msg = "Bad package setting: package " + pp.name + " has shared uid "
                        + pp.sharedId + " that is not defined\n";
                mReadMessages.append(msg);
                PackageManagerService.reportSettingsProblem(Log.ERROR, msg);
            }
        }
		//清空
        mPendingPackages.clear();

		/*
			读取packages-stopped-backup.xml和packages_stopped.xml文件!
			这个不是我们重点关注的内容,读者感兴趣可以自行分析
		*/
        if (mBackupStoppedPackagesFilename.exists()
                || mStoppedPackagesFilename.exists()) {
            // 如果packages_stopped.xml存在,我们先会读取文件数据,然后删除packages_stopped.xml相关文件!
            readStoppedLPw();
            mBackupStoppedPackagesFilename.delete();
            mStoppedPackagesFilename.delete();
            // 用最新的数据更新偏好设置!
            writePackageRestrictionsLPr(UserHandle.USER_SYSTEM);
        } else {
            for (UserInfo user : users) {
				// 否则,我们直接读取已有的偏好设置,更新上次安装的信息!
                readPackageRestrictionsLPr(user.id);
            }
        }

        for (UserInfo user : users) {
			// 读取每个user下的运行时权限信息,详见章节 【 3.10 】
            mRuntimePermissionsPersistence.readStateForUserSyncLPr(user.id);
        }


         /* 
         	mDisabledSysPackages里面保存着所有被替换的Package信息,
         	如果这些package之前配置的是共享用户uid
    	    那这里要建立package和共享用户id的关联!
    	 */
        final Iterator<PackageSetting> disabledIt = mDisabledSysPackages.values().iterator();
        while (disabledIt.hasNext()) {
            final PackageSetting disabledPs = disabledIt.next();
            final Object id = getUserIdLPr(disabledPs.appId);
            if (id != null && id instanceof SharedUserSetting) {
                disabledPs.sharedUser = (SharedUserSetting) id;
            }
        }

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

        writeKernelMappingLPr();

        return true;
    }

这个方法代码量比较大,我们不要被它唬住!我们先抓主干内容,各个分支的可以慢慢来。先解决吃饱在解决吃好!

该方法的核心逻辑流程主要有如下几点,如下:

  • 读取packages-backup.xml或者packages.xml文件中的数据并解析,获得上一次应用的安装和使用信息

    此处是重点将要分析的!

  • 校验共享用户id的应用程序包的uid的有效性

  • 读取packages-stopped-backup.xml和packages_stopped.xml文件中的数据并解析!

    此处读者自行分析

  • 处理被替换的系统应用的共享用户id逻辑关系

好了目标方向明确了,我们对上述相关内容各个击破一一分析!


3.3 解析并读取packages.xml文件前期知识热身

还记得前面博客PackageManagerService启动详解(二)之对已安装应用怎么进行持久化存储管理?大费周章的重点介绍了PKMS启动要牵涉的几个重要包管理持久化存储的文件吗,那都是有原因的,都是为了这里打的基础。解析并读取packages.xml文件,其本质就是解析如下的相关标签内容:

标签标签意义
package系统中所有已安装应用包的相关信息(其包括系统应用和第三方安装应用)
permissions系统中定义的所有权限(包括Android预先定义的,也包括第三方应用定义的)
permission-trees系统中定义的权限树
shared-user系统中所有的共享用户信息(这个包括Android系统预置的,也可以是用于自定义的)
preferred-packages未知,等待探索…
preferred-activities未知,等待探索…
persistent-preferred-activities未知,等待探索…
crossProfile-intent-filters未知,等待探索…
default-browser默认浏览器
updated-package被覆盖升级的系统应用的相关信息,这个标签很重要在后面PKMS扫描应用中会用到
cleaning-package来记录那些已经删除,但是数据目录还暂时保留的应用的信息
renamed-package系统中被重命名的应用程序包
restored-ivi未知,等待探索…
last-platform-version系统升级之前的版本
database-version数据库版本
verifier未知,等待探索…
read-external-storage未知,等待探索…
keyset-settings所有安装应用签名的公钥信息
version当前系统的版本

是不是感觉,这里要解析的标签很多啊!当然了,我们不会全部进行分析的,我将会重点分析package、permission,shared-user等相关内容的解析和存储,其他的内容可以在使用过程中再来具体查看。

在接下来的分析中,我们会以下面的精简版本packages.xml为蓝本,然后一起盘它!

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<packages>
    <version sdkVersion="xxx" databaseVersion="xxx" fingerprint="xxx" />
    <version volumeUuid="xxx" sdkVersion="xxx" databaseVersion="xxx" fingerprint="xxx" />

    <permissions>
        <item name="android.permission.REAL_GET_TASKS" package="android" protection="18" />
        ...
    </permissions>   
    
    <package name="com.android.providers.telephony" codePath="/system/priv-app/TelephonyProvider" nativeLibraryPath="/system/priv-app/TelephonyProvider/lib" publicFlags="1007402501" privateFlags="8" ft="11e8dc5d800" it="11e8dc5d800" ut="11e8dc5d800" version="25" versionName="7.1.2" applicationName="电话和短信存储" sharedUserId="1001" isOrphaned="true">
        <sigs count="1">
            <cert index="1" key="xxx" />
        </sigs>
        <perms>
            <item name="android.permission.WRITE_SETTINGS" granted="true" flags="0" />
			...
        </perms>
        <proper-signing-keyset identifier="1" />
    </package>  
    ...       
      
    <updated-package name="xxx.xxx.xxx" codePath="/system/app/xxx" ft="11e8dc5d800" it="11e8dc5d800" ut="11e8dc5d800" version="11" nativeLibraryPath="/system/app/xxx/lib" primaryCpuAbi="armeabi-v7a" sharedUserId="1000" />


    <shared-user name="android.media" userId="10005">
        <sigs count="1">
            <cert index="2" />
        </sigs>
        <perms>
            <item name="android.permission.ACCESS_CACHE_FILESYSTEM" granted="true" flags="0" />
            ...
        </perms>
    </shared-user>
    ...

    <keyset-settings version="1">
        <keys>
            <public-key identifier="1" value="xxx" />
            <public-key identifier="2" value="xxx" />
            <public-key identifier="3" value="xxx" />
			...
        </keys>
        <keysets>
            <keyset identifier="1">
                <key-id identifier="1" />
            </keyset>
            <keyset identifier="2">
                <key-id identifier="2" />
            </keyset>
            <keyset identifier="3">
                <key-id identifier="3" />
            </keyset>
			...
        </keysets>
        <lastIssuedKeyId value="6" />
        <lastIssuedKeySetId value="6" />
    </keyset-settings>
</packages>   

后续解析将会重点使用到Setings中的相关方法,所以这里有必要再次把Settings的类图关系图给祭出来,对于后续解析中有不清楚的可以回过头来看看!

在这里插入图片描述


3.4 Settings.readPackageLPw解析<package>标签

在解析之前先放上将要解析的内容模板内容如下:

    <package name="com.android.providers.telephony" codePath="/system/priv-app/TelephonyProvider" nativeLibraryPath="/system/priv-app/TelephonyProvider/lib" publicFlags="1007402501" privateFlags="8" ft="11e8dc5d800" it="11e8dc5d800" ut="11e8dc5d800" version="25" versionName="7.1.2" applicationName="电话和短信存储" sharedUserId="1001" isOrphaned="true">
        <sigs count="1">
            <cert index="1" key="xxx" />
        </sigs>
        <perms>
            <item name="android.permission.WRITE_SETTINGS" granted="true" flags="0" />
			...
        </perms>
        <proper-signing-keyset identifier="1" />
    </package>  

要解析的内容放出来了,我们看readPackageLPw对其是怎么解析,并将解析的内容通过相关数据结构存储的!

// 【 Settings.java 】
	/*
		读取并解析packages.xml中的pacakge标签以及子标签相关的内容
	*/
	private void readPackageLPw(XmlPullParser parser) throws XmlPullParserException, IOException {
		// 一大堆需要填充的成员变量值
        String name = null;
        String realName = null;
        String idStr = null;
        String sharedIdStr = null;
        String codePathStr = null;
        String resourcePathStr = null;
        String legacyCpuAbiString = null;
        String legacyNativeLibraryPathStr = null;
        String primaryCpuAbiString = null;
        String secondaryCpuAbiString = null;
        String cpuAbiOverrideString = null;
        String systemStr = null;
        String installerPackageName = null;
        String isOrphaned = null;
        String volumeUuid = null;
        String uidError = null;
        int pkgFlags = 0;
        int pkgPrivateFlags = 0;
        long timeStamp = 0;
        long firstInstallTime = 0;
        long lastUpdateTime = 0;
        PackageSettingBase packageSetting = null;
        String version = null;
        int versionCode = 0;
        String parentPackageName;
        try {
			// 获得应用的包名
            name = parser.getAttributeValue(null, ATTR_NAME);
            realName = parser.getAttributeValue(null, "realName");

			// 获得userId和sharedId的名称, userId和sharedIdStr不能同时存在
            idStr = parser.getAttributeValue(null, "userId");
            uidError = parser.getAttributeValue(null, "uidError");
            sharedIdStr = parser.getAttributeValue(null, "sharedUserId");

			// 获获取应用程序安装包路劲,譬如/system/app/XXX或者/data/app/XXX
            codePathStr = parser.getAttributeValue(null, "codePath");
            resourcePathStr = parser.getAttributeValue(null, "resourcePath");

            legacyCpuAbiString = parser.getAttributeValue(null, "requiredCpuAbi");

            parentPackageName = parser.getAttributeValue(null, "parentPackageName");

			// 获取安装应用so共享库路径
            legacyNativeLibraryPathStr = parser.getAttributeValue(null, "nativeLibraryPath");

			// 获取当前安装应用abi的相关配置
            primaryCpuAbiString = parser.getAttributeValue(null, "primaryCpuAbi");
            secondaryCpuAbiString = parser.getAttributeValue(null, "secondaryCpuAbi");
            cpuAbiOverrideString = parser.getAttributeValue(null, "cpuAbiOverride");

            if (primaryCpuAbiString == null && legacyCpuAbiString != null) {
                primaryCpuAbiString = legacyCpuAbiString;
            }

			// 获得版本号!
            version = parser.getAttributeValue(null, "version");
            if (version != null) {
                try {
                    versionCode = Integer.parseInt(version);
                } catch (NumberFormatException e) {
                }
            }
			// 获得安装器的名称!
            installerPackageName = parser.getAttributeValue(null, "installer");
			
			...

			// 获得flags相关信息,读取顺序为:publicFlags和privateFlags、flags、system;
			systemStr = parser.getAttributeValue(null, "publicFlags");
            if (systemStr != null) {
				...
            } else {
                // 在Android M之前,是没有publicFlags和privateFlags之分的,所有的标志位都存放在 flags 中!
                systemStr = parser.getAttributeValue(null, "flags");
                if (systemStr != null) {
					...
                } else {
					...
                }
            }

			// 获得被安装应用相关的时间戳信息
            String timeStampStr = parser.getAttributeValue(null, "ft");
			...

			/* 
			   获得userId的int值,如果AndroidManifest.xml有设置android:sharedUserId属性,
			   那么应用的userId就为0
			*/
			int userId = idStr != null ? Integer.parseInt(idStr) : 0;
            if (resourcePathStr == null) {
                resourcePathStr = codePathStr;
            }
            if (realName != null) {
                realName = realName.intern();
            }
            if (name == null) {
				...
            } else if (codePathStr == null) {
				...
            } else if (userId > 0) {
				/*
					如果userId大于0,说明package是独立用户id
            	    调用addPackageLPw方法保存这个有独立userId的Package!
            	    详见 【 3.4.1 】
            	*/
                packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr),
                        new File(resourcePathStr), legacyNativeLibraryPathStr, primaryCpuAbiString,
                        secondaryCpuAbiString, cpuAbiOverrideString, userId, versionCode, pkgFlags,
                        pkgPrivateFlags, parentPackageName, null);
                if (PackageManagerService.DEBUG_SETTINGS)
                    Log.i(PackageManagerService.TAG, "Reading package " + name + ": userId="
                            + userId + " pkg=" + packageSetting);
                if (packageSetting == null) {
                    PackageManagerService.reportSettingsProblem(Log.ERROR, "Failure adding uid "
                            + userId + " while parsing settings at "
                            + parser.getPositionDescription());
                } else {
					// 设置时间戳,第一次安装时间,最近更新时间
                    packageSetting.setTimeStamp(timeStamp);
                    packageSetting.firstInstallTime = firstInstallTime;
                    packageSetting.lastUpdateTime = lastUpdateTime;
                }
            } else if (sharedIdStr != null) {
            	// sharedIdStr不为null,说明package设置了共享用户id!
                userId = sharedIdStr != null ? Integer.parseInt(sharedIdStr) : 0;
                if (userId > 0) {

					/*
						对于共享用户ID这种情况,还需要验证其有效性!
                	    创建一个PendingPackage对象,来封装这个有共享用户ID的package的信息!
                	*/
                    packageSetting = new PendingPackage(name.intern(), realName, new File(
                            codePathStr), new File(resourcePathStr), legacyNativeLibraryPathStr,
                            primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString,
                            userId, versionCode, pkgFlags, pkgPrivateFlags, parentPackageName,
                            null);
					// 设置时间戳,第一次安装时间,最近更新时间!  
                    packageSetting.setTimeStamp(timeStamp);
                    packageSetting.firstInstallTime = firstInstallTime;
                    packageSetting.lastUpdateTime = lastUpdateTime;

					// 添加到mPendingPackages中,因为后续需要确定shareUserId的有效性
                    mPendingPackages.add((PendingPackage) packageSetting);
                    if (PackageManagerService.DEBUG_SETTINGS)
                        Log.i(PackageManagerService.TAG, "Reading package " + name
                                + ": sharedUserId=" + userId + " pkg=" + packageSetting);
                } else {
                    PackageManagerService.reportSettingsProblem(Log.WARN,
                            "Error in package manager settings: package " + name
                                    + " has bad sharedId " + sharedIdStr + " at "
                                    + parser.getPositionDescription());
                }
            } else {
                PackageManagerService.reportSettingsProblem(Log.WARN,
                        "Error in package manager settings: package " + name + " has bad userId "
                                + idStr + " at " + parser.getPositionDescription());
            }
        } catch (NumberFormatException e) {
			...
        }

		// 接下来继续解析其它的属性值
        if (packageSetting != null) {
            packageSetting.uidError = "true".equals(uidError);
            packageSetting.installerPackageName = installerPackageName;
            packageSetting.isOrphaned = "true".equals(isOrphaned);
            packageSetting.volumeUuid = volumeUuid;
            packageSetting.legacyNativeLibraryPathString = legacyNativeLibraryPathStr;
            packageSetting.primaryCpuAbiString = primaryCpuAbiString;
            packageSetting.secondaryCpuAbiString = secondaryCpuAbiString;
            
            final String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED);
            if (enabledStr != null) {
				...
            } else {
                packageSetting.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, 0, null);
            }

            if (installerPackageName != null) {
                mInstallerPackages.add(installerPackageName);
            }

			 // package安装状态!
            final String installStatusStr = parser.getAttributeValue(null, "installStatus");
            if (installStatusStr != null) {
                if (installStatusStr.equalsIgnoreCase("false")) {
                    packageSetting.installStatus = PackageSettingBase.PKG_INSTALL_INCOMPLETE;
                } else {
                    packageSetting.installStatus = PackageSettingBase.PKG_INSTALL_COMPLETE;
                }
            }

            int outerDepth = parser.getDepth();
            int type;
			/*
				继续解析<package>标签的子标签
			*/
            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                    continue;
                }

                String tagName = parser.getName();
               
                if (tagName.equals(TAG_DISABLED_COMPONENTS)) { // 解析disabled-components标签
                   ...
                } else if (tagName.equals(TAG_ENABLED_COMPONENTS)) { // 解析enabled-components标签
                    ...
                } else if (tagName.equals("sigs")) { // 解析sigs标签
                    packageSetting.signatures.readXml(parser, mPastSignatures);
                } else if (tagName.equals(TAG_PERMISSIONS)) {// 解析<perms>标签,得到这个应用包的相关权限信息,详见 【 3.4.3 】
                    readInstallPermissionsLPr(parser,
                            packageSetting.getPermissionsState());
                    packageSetting.installPermissionsFixed = true;
                } else if (tagName.equals("proper-signing-keyset")) {
					...
                } else if (tagName.equals("signing-keyset")) {
                    ...
                } else if (tagName.equals("upgrade-keyset")) {
					...
                } else if (tagName.equals("defined-keyset")) {
					...
                } else if (tagName.equals(TAG_DOMAIN_VERIFICATION)) {
                    ...
                } else if (tagName.equals(TAG_CHILD_PACKAGE)) {
					...
                } else {
                    PackageManagerService.reportSettingsProblem(Log.WARN,
                            "Unknown element under <package>: " + parser.getName());
                    XmlUtils.skipCurrentTag(parser);
                }
            }
        } else {
            XmlUtils.skipCurrentTag(parser);
        }
    }

这里的源码又是好几百行,深入源码分析就是这么枯燥且乏味的事情!饭还是要吃,日子还是要过,我们这里先对上述流程简单总结一下,其核心的逻辑如下:

  • 首先对<package>标签内容进行解析,获取其相关的属性值并存储起来,且对于该标签的解析中有如下两点需要重点注意:

    • 对于分配了独立的userId的已安装package应用,则直接创建PackageSetting对象,添加到mPackages中,并且将自身和uid的引用关系保存到mUsersId或者mOthersId中
    • 对于分配了共享的sharedUserId的已安装package应用,则先创建PendingPackage对象,添加到mPendingPackages中,后续会对其共享uid的有效性进行校验,然后做进一步处理
  • 继续解析<package>的子标签内容

3.4.1 Settings.addPackageLPw构建PackageSetting数据结构

在正式开始分析之前,这里又要老生常谈了!如果对于PackageSetting数据类还不清楚的读者一定要阅读下PackageManagerService启动详解(二)之对已安装应用怎么进行持久化存储管理?,真不是我自卖自夸!

这里还是简单的对PackageSetting说明下,这个数据结构类是packages.xml里面记录安装包信息标签<package>相对应的类!

啥也不多说了,直接上源码硬啃!

    PackageSetting addPackageLPw(String name, String realName, File codePath, File resourcePath,
            String legacyNativeLibraryPathString, String primaryCpuAbiString,
            String secondaryCpuAbiString, String cpuAbiOverrideString, int uid, int vc, int
            pkgFlags, int pkgPrivateFlags, String parentPackageName,
            List<String> childPackageNames) {
        // 根据name值从mPackages集合中查询name对应的PackageSettings对象
        PackageSetting p = mPackages.get(name);
        if (p != null) {
			// 如果能得到,且两个uid相等,说明已经创建过了,那就直接返回
            if (p.appId == uid) {
                return p;
            }
            PackageManagerService.reportSettingsProblem(Log.ERROR,
                    "Adding duplicate package, keeping first: " + name);
			// 如果发现uid发生了变化,那就不会读取上一次的安装信息
			return null;
        }

		// 否则,就创建一个PackageSettings对象来封装这个应用程序的信息
        p = new PackageSetting(name, realName, codePath, resourcePath,
                legacyNativeLibraryPathString, primaryCpuAbiString, secondaryCpuAbiString,
                cpuAbiOverrideString, vc, pkgFlags, pkgPrivateFlags, parentPackageName,
                childPackageNames);
        p.appId = uid;
		// 根据uid情况判断加到mUserIds或者mOtherUserIds中
        if (addUserIdLPw(uid, p, name)) {
			// 将这个PackageSettings对象保存在Settings.mPackages中
            mPackages.put(name, p);
            return p;
        }
        return null;
    }

是不是这里的代码有种似曾相识的感觉,是的这里和章节1.2 Settings.addSharedUserLPw的类似,它的逻辑并不是非常的复杂,主要步骤如下:

  • 将传入的参数构建PackageSetting对象实例,然后根据实际情况决定是否将该实例添加到mPackages哈希表中
  • 接着调用addUserIdLPw 方法,将package的uid进行封装,根据其范围,将其添加到mUserIds或者mOtherUserIds中
3.4.2 Android应用权限机制涉及的的数据结构

在深入分析解析<packages>子标签<perms>子标签之前,我们有必要先介绍一下Android授权机制涉及到的数据结构:

在这里插入图片描述

  • BasePermission:权限持有者所定义的每一项权限都会生成一个BasePermission,所有BasePermission都聚合在PMS的Settings中。BasePermission有三种类型: NORMAL、BUILTIN、DYNAMIC。

  • PackageSetting: 每一个解析成功的包都会生成一个PackageSetting,所有的PackageSetting都聚合在PMS的Settings中。PackageSetting继承了其祖宗SettingBase的属性mPermissionsState,这个属性表示一个包的授权状态。

  • PermissionsState(注意,这是复数):聚合了包中所有权限的授予状态,在多用户的场景下,不同用户的授权情况不同,因此要区分出一个权限针对每个用户的授予情况,为此设计了一个数据封装类PermissionData(注意,这是单数),记录了一个BasePermission与多个用户mUserState之间的关系。每一个用户的权限授予状态用 PermissionState(注意,这是单数) 来记录。

理解应用授权机制的数据结构设计,可以结合日常生活中的门禁系统。一个大厦里面有很多房间,进入不同的房间需要获得通过门禁的权限,每一个房间的门禁就可以理解为BasePermission,所有的门禁权限都记录在大厦管理者的数据中心,这个数据中心就可以理解为PMS的Settings。每一个用户来到大厦,都会被登记,登记的记录就是PackageSetting。

用户会申请要进入哪些房间,待管理中心分配,最后,用户会拿到的门禁卡,表示可以进入哪些房间。不同用户需要进入和有权进入的房间通常是不同的,譬如涉及大厦安全的监控房间,就不让访客进入,这就决定了对不同用户而言,门禁卡开通的权限不一样。门禁卡所对应的就是PermissionsState。

假设大厦临时换了一套管理规定,之前某个用户有权进入某个房间,在临时的管理规定下变成了无权进入。同一个房间,在不同管理规定下,对一个用户开放的权限是不同的,这个信息被记录下来,就是PermissionData。这样一来,同一张门禁卡,面对同一张门,在不同的管理规定下,打开门的情况是不一样的。

不同的管理规定,其实就是Android中的多用户使用情况的体现,不同用户的使用规则是不同的,对于同一个应用而言,不同用户使用下的授权情况会有差异。

3.4.3 Settings.readInstallPermissionsLPr获取package安装权限信息
3.4.3.1 PermissionsState数据结构类

通过前面的铺垫我们知道对于PackageSetting和SharedUserSetting都有自己的权限管理对象 PermissionsState,用于保存和管理package和SharedUser所有的权限,其权限通过解析子标签<perms>获得,具体的解析方法是就是我们这里要分析的readInstallPermissionsLPr。在对该方法之前我们还是简单来看下PermissionsState数据结构类,看看它的庐山真面目,它是如何做到权限管理的。

// 【  PermissionsState.java  】
public class PermissionsState {

    public static final int PERMISSION_OPERATION_FAILURE = -1; // 返回值:权限操作失败
    public static final int PERMISSION_OPERATION_SUCCESS = 0; // 返回值:权限操作成功,gid 没有改变!
    public static final int PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED = 1; // 返回值:权限操作成功,gid 改变!
    
    private ArrayMap<String, PermissionData> mPermissions; // 用于封装这个应用程序的所有权限!
    private int[] mGlobalGids = NO_GIDS;  
    
    ... ... ...
}
3.4.3.2 正式开始解析

在解析之前先放上将要解析的内容模板内容如下:

        <perms>
            <item name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" granted="true" flags="0" />
            <item name="com.android.launcher3.permission.READ_SETTINGS" granted="true" flags="0" />
            <item name="android.permission.USE_CREDENTIALS" granted="true" flags="0" />
            <item name="android.permission.MODIFY_AUDIO_SETTINGS" granted="true" flags="0" />
            <item name="android.permission.MANAGE_ACCOUNTS" granted="true" flags="0" />
            ...
        </perms>

要解析的内容放出来了,我们直接看解析流程(即怎么填充PackageSetting对应的PermissionsState)

// 【 Settings.java 】
	/*
		来解析package所使用的权限和其授予情况
		这其中包括PackageSetting和SharedUserSetting两种情况

		在当前情况下
		parser:xml解析类对象,指向子标签 “perms”
		permissionsState: 指向前面PackageSetting对应的PermissionsState
	*/
    void readInstallPermissionsLPr(XmlPullParser parser,
            PermissionsState permissionsState) throws IOException, XmlPullParserException {
        int outerDepth = parser.getDepth();
        int type;
        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG
                || parser.getDepth() > outerDepth)) {
            if (type == XmlPullParser.END_TAG
                    || type == XmlPullParser.TEXT) {
                continue;
            }
            String tagName = parser.getName();
            if (tagName.equals(TAG_ITEM)) { //解析标签<item>
                String name = parser.getAttributeValue(null, ATTR_NAME);

				/* 
					从之前解析的Settings.mPermissions获得对应的权限;
					Settings.mPermissions中保存的权限来自两部分(这个地方需要回过头再看下),
					一个是SystemConfig.mPermissions中解析到了系统定义的权限,还有一部分,来自从packages.xml中解析<permissions>获取得到的
				*/
                BasePermission bp = mPermissions.get(name);
                if (bp == null) {
                    Slog.w(PackageManagerService.TAG, "Unknown permission: " + name);
                    XmlUtils.skipCurrentTag(parser);
                    continue;
                }

				// 解析"granted"属性,获得授予情况,如果没有该属性或者设置为 true,那么表示授予!
                String grantedStr = parser.getAttributeValue(null, ATTR_GRANTED);
                final boolean granted = grantedStr == null
                        || Boolean.parseBoolean(grantedStr);

				// 解析flag属性
				String flagsStr = parser.getAttributeValue(null, ATTR_FLAGS);
                final int flags = (flagsStr != null)
                        ? Integer.parseInt(flagsStr, 16) : 0;

				// 处理权限授予情况
				if (granted) {// 默认授予情况
                    if (permissionsState.grantInstallPermission(bp) ==
                            PermissionsState.PERMISSION_OPERATION_FAILURE) {
                        Slog.w(PackageManagerService.TAG, "Permission already added: " + name);
                        XmlUtils.skipCurrentTag(parser);
                    } else {
                        permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,
                                PackageManager.MASK_PERMISSION_FLAGS, flags);
                    }
                } else {// 默认不授予的情况!
                    if (permissionsState.revokeInstallPermission(bp) ==
                            PermissionsState.PERMISSION_OPERATION_FAILURE) {
                        Slog.w(PackageManagerService.TAG, "Permission already added: " + name);
                        XmlUtils.skipCurrentTag(parser);
                    } else {
						// 更新权限标志位!
                        permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,
                                PackageManager.MASK_PERMISSION_FLAGS, flags);
                    }
                }
            } else {
                Slog.w(PackageManagerService.TAG, "Unknown element under <permissions>: "
                        + parser.getName());
                XmlUtils.skipCurrentTag(parser);
            }
        }
    }

上述源码逻辑主要通过解析<package>子标签的<perms>信息,来填充安装包对应的数据结构的PackageSetting对象实例的PermissionsState权限状态信息。对于其中的解析和填充这里就不过细说了!

Settings.mPermissions中保存的权限来自两部分(这个地方需要回过头再看下),一个是SystemConfig.mPermissions中解析到了系统定义的权限,还有一部分,来自从packages.xml中解析获取得到的。

这里估计读者会由疑问了,这里不是还没有开始解析标签吗,怎么就得到它里面的数据内容了啊?没有被解析只是排版的顺序,其实按照xml的解析逻辑它是在前面的,这点读者需要注意下!


3.5 Settings.readPermissionsLPw解析<permissions>标签和<permission-trees>标签获取权限信息

关于此处有几点我们可以留一下:
1.对于<permission-trees>和<permissions>解析的方法都是readPermissionsLPw,但是 <permissions>的解析结果,会保存到mPermissions 中,而<permission-trees>的解析结果,会保存到mPermissionTrees中。

2.所以对于上述两种标签的解析放在一起分析!并且通常permission-trees标签的内容基本为空,

还是老规矩,在开始解析之前先放上将要解析的内容模板内容如下:

    <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" />
        <item name="android.permission.REMOTE_AUDIO_PLAYBACK" package="android" protection="2" />
        <item name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" package="com.android.providers.downloads" />
        <item name="android.permission.REGISTER_WINDOW_MANAGER_LISTENERS" package="android" protection="2" />
        <item name="android.permission.INTENT_FILTER_VERIFICATION_AGENT" package="android" protection="18" />
        <item name="android.permission.BIND_INCALL_SERVICE" package="android" protection="18" />
        <item name="android.permission.PROVIDE_RESOLVER_RANKER_SERVICE" package="android" protection="18" />
        ...
    <permissions/>        

要解析的内容放出来了,我们看readPermissionsLPw方法是怎么对其进行解析的!

	/*
		读取packags.xml中的<permission-trees>或者<permissions>标签,
		这里的入参:
		out 表示的是Settings.mPermissions或者Settings.mPermissionTrees哈希表
		parser 表示将要解析的标签内容
	*/
    private void readPermissionsLPw(ArrayMap<String, BasePermission> out, XmlPullParser parser)
            throws IOException, XmlPullParserException {
        int outerDepth = parser.getDepth();
        int type;
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                continue;
            }

            final String tagName = parser.getName();
            if (tagName.equals(TAG_ITEM)) {// 解析<item>标签
                final String name = parser.getAttributeValue(null, ATTR_NAME);// 获得permission的名称
                final String sourcePackage = parser.getAttributeValue(null, "package");
                final String ptype = parser.getAttributeValue(null, "type");
                if (name != null && sourcePackage != null) {
                    final boolean dynamic = "dynamic".equals(ptype);
					// 尝试从Settings对应集合中获得权限 bp
                    BasePermission bp = out.get(name);
                    // If the permission is builtin, do not clobber it.
                    /* 
                      如果bp为null或者bp不为null且其类型不是BasePermission.TYPE_BUILTIN,就创建一个新的!
                	  BasePermission.TYPE_BUILTIN类型的权限是系统权限,前面已经解析过!
                	  BasePermission.TYPE_DYNAMIC类型是针对于权限树的类型的!
                	*/
                    if (bp == null || bp.type != BasePermission.TYPE_BUILTIN) {
                        bp = new BasePermission(name.intern(), sourcePackage,
                                dynamic ? BasePermission.TYPE_DYNAMIC : BasePermission.TYPE_NORMAL);
                    }
					// 获得定义permission的级别,默认为PROTECTION_NORMAL然后对保护级别修正! 
                    bp.protectionLevel = readInt(parser, null, "protection",
                            PermissionInfo.PROTECTION_NORMAL);
                    bp.protectionLevel = PermissionInfo.fixProtectionLevel(bp.protectionLevel);
                    if (dynamic) {
						// 如果是动态权限,进一步处理,动态权限会封装为PermissionInfo!
                    	// 动态权限是可以动态添加和移除的权限!通过权限树指定!
                        PermissionInfo pi = new PermissionInfo();
                        pi.packageName = sourcePackage.intern();
                        pi.name = name.intern();
                        pi.icon = readInt(parser, null, "icon", 0);
                        pi.nonLocalizedLabel = parser.getAttributeValue(null, "label");
                        pi.protectionLevel = bp.protectionLevel;
                        bp.pendingInfo = pi;
                    }
					// 封装成BasePermission保存到mPermissions集合中!
                    out.put(bp.name, bp);
                } else {
                    PackageManagerService.reportSettingsProblem(Log.WARN,
                            "Error in package manager settings: permissions has" + " no name at "
                                    + parser.getPositionDescription());
                }
            } else {
                PackageManagerService.reportSettingsProblem(Log.WARN,
                        "Unknown element reading permissions: " + parser.getName() + " at "
                                + parser.getPositionDescription());
            }
            XmlUtils.skipCurrentTag(parser);
        }
    }

这个的逻辑比较简单,就是将所有的非动态权限信息解析保存到Setting.mPermissions中。至于<permissions>中子标签的属性的相关含义可以参见前面博客PKMS启动详解(二)之怎么通过packages.xml对已安装应用信息进行持久化管理?的1.2.1章节的介绍!


3.6 Settings.readSharedUserLPw解析<shared-user>获取共享用户信息

关于这里有几点我们可以留意一下:
1.解析<shared-user>标签,该标签内容和<package>可以类比!

2.并且这里的共享用户可能是系统预置的,也可以是第三方应用自定义的,这个地方需要注意一下!

还是老规矩,在开始解析之前先放上将要解析的内容模板内容如下:

    <shared-user name="android.uid.system" userId="1000">
        <sigs count="1">
            <cert index="1" />
        </sigs>
        <perms>
            <item name="android.permission.BIND_INCALL_SERVICE" granted="true" flags="0" />
            <item name="android.permission.WRITE_SETTINGS" granted="true" flags="0" />
            <item name="android.permission.CONFIGURE_WIFI_DISPLAY" granted="true" flags="0" />
            <item name="android.permission.CONFIGURE_DISPLAY_COLOR_MODE" granted="true" flags="0" />
            <item name="android.permission.ACCESS_WIMAX_STATE" granted="true" flags="0" />
            <item name="android.permission.RESTART_PACKAGES" granted="true" flags="0" />
            <item name="android.permission.USE_CREDENTIALS" granted="true" flags="0" />
			...
        </perms>
    </shared-user>

	<!-- 第三方用户定义的共享用户信息 -->
    <shared-user name="com.android.mytest" userId="10055">
        <sigs count="1">
            <cert index="10" />
        </sigs>
        <perms>
            <item name="android.permission.READ_EXTERNAL_STORAGE" granted="true" flags="0" />
            <item name="android.permission.READ_PHONE_STATE" granted="true" flags="0" />
            <item name="android.permission.CHANGE_WIFI_STATE" granted="true" flags="0" />
            <item name="android.permission.WRITE_EXTERNAL_STORAGE" granted="true" flags="0" />
            <item name="android.permission.ACCESS_WIFI_STATE" granted="true" flags="0" />
        </perms>
    </shared-user>

好了,要解析的数据摆出来了,是时候展现真正的实力了让我们肢解它!

// 【 Settings.java 】
	/*
		解析<shared-user>标签,该标签内容和<package>可以类比
		这里的共享用户可能是系统预置的,也可以是第三方应用自定义的
	*/
    private void readSharedUserLPw(XmlPullParser parser) throws XmlPullParserException,IOException {
        String name = null;
        String idStr = null;
        int pkgFlags = 0;
        int pkgPrivateFlags = 0;
        SharedUserSetting su = null;
        try {
            name = parser.getAttributeValue(null, ATTR_NAME);// 获得共享用户的名称
            idStr = parser.getAttributeValue(null, "userId");// 获得共享用户的id
            int userId = idStr != null ? Integer.parseInt(idStr) : 0;
			// 是否是系统的用户id
            if ("true".equals(parser.getAttributeValue(null, "system"))) {
                pkgFlags |= ApplicationInfo.FLAG_SYSTEM;
            }
            if (name == null) {
				...
            } else if (userId == 0) {
				...
            } else {
				/*
					 调用addSharedUserLPw方法,将这个共享用户和对应的uid保存下来
					 回见章节 【 1.2 】
				*/
                if ((su = addSharedUserLPw(name.intern(), userId, pkgFlags, pkgPrivateFlags))
                        == null) {
                    PackageManagerService
                            .reportSettingsProblem(Log.ERROR, "Occurred while parsing settings at "
                                    + parser.getPositionDescription());
                }
            }
        } catch (NumberFormatException e) {
			...
        }

        if (su != null) {// 解析<shared-user>子标签,可以参见解析<package>子标签
            int outerDepth = parser.getDepth();
            int type;
            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                    continue;
                }

                String tagName = parser.getName();
                if (tagName.equals("sigs")) {
                    su.signatures.readXml(parser, mPastSignatures);
                } else if (tagName.equals("perms")) {// 解析<perms>标签,关于此处逻辑请回见章节 【 3.4.3 】
                    readInstallPermissionsLPr(parser, su.getPermissionsState());
                } else {
                    PackageManagerService.reportSettingsProblem(Log.WARN,
                            "Unknown element under <shared-user>: " + parser.getName());
                    XmlUtils.skipCurrentTag(parser);
                }
            }
        } else {
            XmlUtils.skipCurrentTag(parser);
        }
    }

对于这里的逻辑处理,有了前面解析的经验值加持,这里就so easy了。此处的主要逻辑如下:

  • 先解析<shared-user>标签,将其封装成SharedUserSetting实例对象,保存到mSharedUsers,并根据uid的取值将其保存到mUserIds 或者mOtherIds中

    这里我们需要注意对于system,phone等几个特殊的共享用户已经在前面有创建OK了,这里只需要将其从mSharedUsers哈希表中取出来,然后对其填充PermisssionState相关信息即可!

  • 接着继续解析<shared-user>标签的子标签<perm>等等,其中核心的解析共享用户的权限,针对每个权限构建一个PermissionData对象,然后将其统一放SharedUserSetting对应的PermisssionState实例中中,从而达到管理共享用户的权限的功能


3.7 Settings.readDisabledSysPackageLPw解析<updated-package>获取被覆盖安装的系统应用信息

这个标签的内容很重要,因为后续PKMS在扫描安装应用处理升级相关逻辑时会用到这个标签中解析得到的信息。

并且读者对于系统App的两种升级方式覆盖升级和OTA升级一定要了解,因为后续会用到它。如果对这个概念还不是很清楚的可以参见前面博客PackageManagerService启动详解系列博客概要的章节2.3.2!

还是原来的配方还是原来的味道,在开始解析之前先放上将要解析的内容模板内容如下:

	<!-- 覆盖安装到data目录下的应用信息 -->
    <package name="com.xxx.xxx" 
    	codePath="/data/app/com.xxx.xxx-1" 
    	nativeLibraryPath="/data/app/com.xxx.xxx-1/lib" 
    	primaryCpuAbi="armeabi-v7a" 
    	publicFlags="940097221" 
    	privateFlags="0" 
    	ft="177d2d079b0" it="177c7dd5c08" ut="177d2d0815f" 
    	version="12" 
    	versionName="1.0.10_20210127" 
    	applicationName="com.xxx.xxx.XXX" 
    	sharedUserId="1000" 
    	isOrphaned="true">
        <sigs count="1">
            <cert index="1" />
        </sigs>
        <perms>
            <item name="android.permission.BIND_INCALL_SERVICE" granted="true" flags="0" />
            <item name="android.permission.WRITE_SETTINGS" granted="true" flags="0" />
			...
        </perms>
        <proper-signing-keyset identifier="1" />
    </package>

	<!-- 被覆盖安装的系统应用 -->
    <updated-package name="com.xxx.xxx" 
    	codePath="/system/app/XXX" ft="177c7dd5c08" it="177c7dd5c08" ut="177c7dd5c08" 
    	version="11" 
    	nativeLibraryPath="/system/app/XXX/lib" 
    	primaryCpuAbi="armeabi-v7a" 
    	sharedUserId="1000" />

好了,要解析的内容也已经放出来了,啥也不多说了,我们直接来看看其解析流程!

// 【 Settings.java 】
	/*
		解析<updated-package>标签,后续在PKMS扫描应用安装目录下会
		用到此处的解析得到的数据
	*/
    private void readDisabledSysPackageLPw(XmlPullParser parser) throws XmlPullParserException,
            IOException {

		// 开始解析<updated-package>内容
        String name = parser.getAttributeValue(null, ATTR_NAME);
        String realName = parser.getAttributeValue(null, "realName");
        String codePathStr = parser.getAttributeValue(null, "codePath");
        String resourcePathStr = parser.getAttributeValue(null, "resourcePath");

        String legacyCpuAbiStr = parser.getAttributeValue(null, "requiredCpuAbi");
        String legacyNativeLibraryPathStr = parser.getAttributeValue(null, "nativeLibraryPath");

        String parentPackageName = parser.getAttributeValue(null, "parentPackageName");

        String primaryCpuAbiStr = parser.getAttributeValue(null, "primaryCpuAbi");
        String secondaryCpuAbiStr = parser.getAttributeValue(null, "secondaryCpuAbi");
        String cpuAbiOverrideStr = parser.getAttributeValue(null, "cpuAbiOverride");

        if (primaryCpuAbiStr == null && legacyCpuAbiStr != null) {
            primaryCpuAbiStr = legacyCpuAbiStr;
        }

        if (resourcePathStr == null) {
            resourcePathStr = codePathStr;
        }
        String version = parser.getAttributeValue(null, "version");
        int versionCode = 0;
        if (version != null) {
            try {
                versionCode = Integer.parseInt(version);
            } catch (NumberFormatException e) {
            }
        }

        int pkgFlags = 0;
        int pkgPrivateFlags = 0;
        pkgFlags |= ApplicationInfo.FLAG_SYSTEM;
        final File codePathFile = new File(codePathStr);
        if (PackageManagerService.locationIsPrivileged(codePathFile)) {
            pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
        }
		// 将解析得到的数据,封装成PackageSetting实例对象
        PackageSetting ps = new PackageSetting(name, realName, codePathFile,
                new File(resourcePathStr), legacyNativeLibraryPathStr, primaryCpuAbiStr,
                secondaryCpuAbiStr, cpuAbiOverrideStr, versionCode, pkgFlags, pkgPrivateFlags,
                parentPackageName, null);

		// 获取相关时间戳属性值
		...

		// 处理其独立uid或者共享uid信息
        String idStr = parser.getAttributeValue(null, "userId");
        ps.appId = idStr != null ? Integer.parseInt(idStr) : 0;
        if (ps.appId <= 0) {
            String sharedIdStr = parser.getAttributeValue(null, "sharedUserId");
            ps.appId = sharedIdStr != null ? Integer.parseInt(sharedIdStr) : 0;
        }

        int outerDepth = parser.getDepth();
        int type;
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                continue;
            }

            if (parser.getName().equals(TAG_PERMISSIONS)) {// 解析<perms>标签,关于此处详见章节 【 3.4.3 】
                readInstallPermissionsLPr(parser, ps.getPermissionsState()); 
            } else if (parser.getName().equals(TAG_CHILD_PACKAGE)) {
                String childPackageName = parser.getAttributeValue(null, ATTR_NAME);
                if (ps.childPackageNames == null) {
                    ps.childPackageNames = new ArrayList<>();
                }
                ps.childPackageNames.add(childPackageName);
            } else {
                PackageManagerService.reportSettingsProblem(Log.WARN,
                        "Unknown element under <updated-package>: " + parser.getName());
                XmlUtils.skipCurrentTag(parser);
            }
        }

		// 此处是重点,将解析并封装好的PackageSetting数据放入mDisabledSysPackages哈希列表中
        mDisabledSysPackages.put(name, ps);
    }

这里的逻辑没有什么特别复杂的,其核心步骤如下:

  • 首先将解析<updated-package>标签得到的相关数据构建填充PackageSetting对象实例
  • 接着将前面构建的PackageSetting实例对象添加到mDisabledSysPackages哈希表中进行管理

3.8 解析<renamed-package>被重命名应用

关于此标签要几点主要说明:
1.这个标签的解析比较简单,在解析此标签以前读者一定要对AndroidManifest.xml文中的<original-package>要有所l理解!
2.这个表示被重命名的安装应用
3.如果对<renamed-package>标签表示的内容有不清楚的读者可以详见博客Android “original-package” 机制解析Android “original-package” 机制

还是原来的配方还是原来的味道,在开始解析之前先放上将要解析的内容模板内容如下:

	<renamed-package new="com.android.newpackage" old="com.android.original" />
    <package name="com.android.original" realName="com.android.newpackage" codePath="/system/app/NewPackage.apk" nativeLibraryPath="/system/lib/NewPackage" primaryCpuAbi="armeabi-v7a" publicFlags="940097095" privateFlags="0" ft="1774cbd5ed8" it="1774cbd0500" ut="1774cbd0500" version="1" versionName="1.0" applicationName="NewPackage" sharedUserId="1000" isOrphaned="true">
        <sigs count="1">
            <cert index="1" />
        </sigs>
        <perms>
            <item name="android.permission.BIND_INCALL_SERVICE" granted="true" flags="0" />
			...
        </perms>
        <proper-signing-keyset identifier="1" />
    <package\>

好了要解析的内容放出来了,看下是怎么进行解析的,如下:

// 【 Settings.java 】
else if (tagName.equals("renamed-package")) {
    String nname = parser.getAttributeValue(null, "new");
    String oname = parser.getAttributeValue(null, "old");
    if (nname != null && oname != null) {
        mRenamedPackages.put(nname, oname);
    }
}

代码很简单,将重新命名的应用的数据新名字为key。旧名字为value,添加到mRenamedPackages中!


3.9 确定package应用安装包共享用户id有效性

我是谁,我在那里!是不是看到我们的标题,突然有这么个感觉了!此处我们要跳转到章节3.4处,解析<package>标签包含有共享用户sharedUserId信息时候会将相关安装包信息添加到mPendingPackages进行管理,,然后我们再跳回章节3.1来确定我们mPendingPackages中信息的合法性。

关于此处,我觉得有必要再强调,重点补充两点:
1.如果pacakge是共享用户id,那么所有uid为共享用户id的package,其权限是一样的,都是共享用户的权限;
2.如果pacakge是独立用户id,那么这个package有自己独立的权限!

好了,我们直接分析开始分析源码逻辑:

// 【 Settings.java 】
		/*************** 至此标签解析结束,进行相关的收尾工作 ******************/

		/*  接着,对mPendingPackages集合中存放着的需要
			验证共享用户id有效性的package,进行共享用户id有效性的验证
		*/
        final int N = mPendingPackages.size();

		
        for (int i = 0; i < N; i++) {
            final PendingPackage pp = mPendingPackages.get(i);

			// 看sharedId是否能对应找到一个ShardUserSetting对象!
            Object idObj = getUserIdLPr(pp.sharedId); // 详见章节 【 3.9.1 】
            if (idObj != null && idObj instanceof SharedUserSetting) { // 能找到,说明共享用户ID是有效的!

				// 创建PackageSetting
				PackageSetting p = getPackageLPw(pp.name, null, pp.realName,
                        (SharedUserSetting) idObj, pp.codePath, pp.resourcePath,
                        pp.legacyNativeLibraryPathString, pp.primaryCpuAbiString,
                        pp.secondaryCpuAbiString, pp.versionCode, pp.pkgFlags, pp.pkgPrivateFlags,
                        null, true /* add */, false /* allowInstall */, pp.parentPackageName,
                        pp.childPackageNames);
                if (p == null) {
                    PackageManagerService.reportSettingsProblem(Log.WARN,
                            "Unable to create application package for " + pp.name);
                    continue;
                }
				// 将PendingPackage的其他数据拷贝到PackageSetting中!
                p.copyFrom(pp);// 保存到Settings.mPackages对象中,表明ID有效,已经分配ID了
            } else if (idObj != null) {
                String msg = "Bad package setting: package " + pp.name + " has shared uid "
                        + pp.sharedId + " that is not a shared uid\n";
                mReadMessages.append(msg);
                PackageManagerService.reportSettingsProblem(Log.ERROR, msg);
            } else {
                String msg = "Bad package setting: package " + pp.name + " has shared uid "
                        + pp.sharedId + " that is not defined\n";
                mReadMessages.append(msg);
                PackageManagerService.reportSettingsProblem(Log.ERROR, msg);
            }
        }
		//清空
        mPendingPackages.clear();

此处源码逻辑不是很复杂,其主要步骤流程如下:

  • 首先检测解析得到的应用package的sharedId是否能从前面解析填充的mUserIds或mOtherUserIds中获得一个共享用户的ShardUserSetting对象
  • 如果找到了合适的ShardUserSetting实例对象,说明此package是合法的继续调用getPackageLPw方法将该package对应的PendingPackage信息封装成PackageSetting添加到mPackages进行管理
3.9.1 Settings.getUserIdLPr通过uid查询相关信息

这里通过传入uid查找对应的Object,得到的可能是一个PackageSetting或者是SharedUserSetting实例对象,逻辑如下:

// 【 Settings.java 】

    public Object getUserIdLPr(int uid) {
        if (uid >= Process.FIRST_APPLICATION_UID) {
            final int N = mUserIds.size();
            final int index = uid - Process.FIRST_APPLICATION_UID;
            return index < N ? mUserIds.get(index) : null;
        } else {
            return mOtherUserIds.get(uid);
        }
    }

关于此处我们要结合前面解析解析<package>和<shared-user>标签向mOtherUserIds或者mUserIds填充的数据的填充情况,此处就比较容易理解了。

3.9.2 Settings.getPackageLPw获取PackageSetting实例对象

啥也不多说,直接开撸源码!但是此处要注意下如下传入参数的取值情况:

  • PackageSetting origPackage:表示源包,传入 null;
  • SharedUserSetting sharedUser:共享 uid 对象,这里是不为 null!
  • UserHandle installUser:为 null;
  • boolean add:true;
  • boolean allowInstall: false
// 【 Settings.java 】
	/*
		创建PendingPackage对应的PackageSetting对象实例
	*/
    private PackageSetting getPackageLPw(String name, PackageSetting origPackage,
            String realName, SharedUserSetting sharedUser, File codePath, File resourcePath,
            String legacyNativeLibraryPathString, String primaryCpuAbiString,
            String secondaryCpuAbiString, int vc, int pkgFlags, int pkgPrivateFlags,
            UserHandle installUser, boolean add, boolean allowInstall, String parentPackage,
            List<String> childPackageNames) {
        PackageSetting p = mPackages.get(name);// 查看之前是否有添加过这个包
        UserManagerService userManager = UserManagerService.getInstance();
		
        if (p != null) {// 很显然不会进入此逻辑之中
			...
        }
		
        if (p == null) {// 之前没有添加过,那就需要创建新的PackageSetting对象
            if (origPackage != null) {// 如果有原始包的话,这个我们暂且不考虑
				...
            } else {
				/*
					如果这个package没有原包的话,就以参数name为包名
					创建packageSetting对象
				*/
                p = new PackageSetting(name, realName, codePath, resourcePath,
                        legacyNativeLibraryPathString, primaryCpuAbiString, secondaryCpuAbiString,
                        null /* cpuAbiOverrideString */, vc, pkgFlags, pkgPrivateFlags,
                        parentPackage, childPackageNames);
                p.setTimeStamp(codePath.lastModified());

				// 设置其sharedUser属性,因为它是共享uid的
                p.sharedUser = sharedUser;
				
                // 如果是非系统的应用,设置它在每个设备用户下的安装状态
                if ((pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) {
                    if (DEBUG_STOPPED) {
                        RuntimeException e = new RuntimeException("here");
                        e.fillInStackTrace();
                        Slog.i(PackageManagerService.TAG, "Stopping package " + name, e);
                    }
                    List<UserInfo> users = getAllUsers();
                    final int installUserId = installUser != null ? installUser.getIdentifier() : 0;
                    if (users != null && allowInstall) {
                        for (UserInfo user : users) {
                            final boolean installed = installUser == null
                                    || (installUserId == UserHandle.USER_ALL
                                        && !isAdbInstallDisallowed(userManager, user.id))
                                    || installUserId == user.id;
                            p.setUserState(user.id, 0, COMPONENT_ENABLED_STATE_DEFAULT,
                                    installed,
                                    true, // stopped,
                                    true, // notLaunched
                                    false, // hidden
                                    false, // suspended
                                    null, null, null,
                                    false, // blockUninstall
                                    INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED, 0);
                            writePackageRestrictionsLPr(user.id);
                        }
                    }
                }

				// 设置它的appId,在默认设备用户下,appId等于uid
                if (sharedUser != null) {
                    p.appId = sharedUser.userId;
                } else {// 根据情况,显然不会进入这个分支
					...
                }
            }
            if (p.appId < 0) {
                PackageManagerService.reportSettingsProblem(Log.WARN,
                        "Package " + name + " could not be assigned a valid uid");
                return null;
            }
            if (add) {
                /*
                	入参add为true,会进入这个分支
                	将确定了共享用户id的PackageSetting对象,添加到mPackages中
                */
                addPackageSettingLPw(p, name, sharedUser); // 详见章节 【 3.9.3 】
            }
        } else {// 也不会进入此分支
			...
        }
        return p;
    }

在该源码中执行的逻辑如下(结合实际入参情况决定):

  • 首先创建PendingPackage对应的PackageSetting对象实例
  • 接着调用addPackageSettingLPw方法将构建的PackageSetting添加到mPackages中
3.9.3 Settings.addPackageSettingLPw

我们接着继续来看下addPackageSettingLPw方法是如何将确定了共享用户id的PackageSetting对象,添加到mPackages中的!

// 【 Settings.java]
    /*
    	将确定了共享用户id的PackageSetting对象,添加到mPackages中
    	并且我们此时传入的参数sharedUser不为null
    */
    private void addPackageSettingLPw(PackageSetting p, String name,
            SharedUserSetting sharedUser) {
        // 添加到mPackages中
        mPackages.put(name, p);
        if (sharedUser != null) {// 进入此分支
            if (p.sharedUser != null && p.sharedUser != sharedUser) {
                PackageManagerService.reportSettingsProblem(Log.ERROR,
                        "Package " + p.name + " was user "
                        + p.sharedUser + " but is now " + sharedUser
                        + "; I am not changing its files so it will probably fail!");
                p.sharedUser.removePackage(p);
            } else if (p.appId != sharedUser.userId) {
                PackageManagerService.reportSettingsProblem(Log.ERROR,
                    "Package " + p.name + " was user id " + p.appId
                    + " but is now user " + sharedUser
                    + " with id " + sharedUser.userId
                    + "; I am not changing its files so it will probably fail!");
            }
			// 确保SharedUserSetting和PackageSetting相互引用正确
            sharedUser.addPackage(p);
            p.sharedUser = sharedUser;
            p.appId = sharedUser.userId;
        }


        // 获得这个package对应的SharedUserSetting对象!
        Object userIdPs = getUserIdLPr(p.appId);
        if (sharedUser == null) {
			...
        } else {
            if (userIdPs != null && userIdPs != sharedUser) {// 实际不会进入此分支
				// 更新uid和SharedUserSetting的关系!
                replaceUserIdLPw(p.appId, sharedUser);
            }
        }

    }

逻辑比较简单,也么有啥需要重点关注的就一笔带过了。


3.10 读取运行时权限信息,并处理授予情况

不容易啊,分析到这里,此处应该要有掌声!虽然前面一路过来,源码并不是很复杂,但是细节比较多,能坚持下来的我觉得都值得享有掌声!

// 【 Settings.java]
// 读取每个 user 下的运行是权限信息!
for (UserInfo user : users) {
	// 该方法会读取指定的user下的运行时权限信息
    mRuntimePermissionsPersistence.readStateForUserSyncLPr(user.id);
}

RuntimePermissionPersistence是Settings的一个内部类,专门用来 运行时权限,我们接着往下看!

// 【 Settings.java 】
        public void readStateForUserSyncLPr(int userId) {
        	// 获得运行时权限文件对象
            File permissionsFile = getUserRuntimePermissionsFile(userId);
            if (!permissionsFile.exists()) {
                return;
            }

            FileInputStream in;
            try {
                in = new AtomicFile(permissionsFile).openRead();
            } catch (FileNotFoundException fnfe) {
                Slog.i(PackageManagerService.TAG, "No permissions state");
                return;
            }

            try {
                XmlPullParser parser = Xml.newPullParser();
                parser.setInput(in, null);
				// 解析运行时权限数据,详见章节 【 3.10.1 】
                parseRuntimePermissionsLPr(parser, userId);

            } catch (XmlPullParserException | IOException e) {
                throw new IllegalStateException("Failed parsing permissions file: "
                        + permissionsFile , e);
            } finally {
                IoUtils.closeQuietly(in);
            }
        }

这里首先通过调用方法getUserRuntimePermissionsFile会获得保存了运行时权限的文件对象,如下:

// 【 Setings.java 】
    private File getUserRuntimePermissionsFile(int userId) {
        File userDir = new File(new File(mSystemDir, "users"), Integer.toString(userId));
        return new File(userDir, RUNTIME_PERMISSIONS_FILE_NAME);
    }

由于我们当前的Android终端是单用户模式,所以只会有一个该文件,它位于 /data/system/users/0/runtime-permissions.xml,如下截图所示:

在这里插入图片描述

我们来简单的看下该文件的具体内容:

<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<runtime-permissions fingerprint="XXX/XXX/XXX:8.1.0/OPM2.171019.012/6:user/release-keys">

  <pkg name="com.android.camera2">
    <item name="android.permission.ACCESS_FINE_LOCATION" granted="true" flags="20" />
    <item name="android.permission.READ_EXTERNAL_STORAGE" granted="true" flags="20" />
    <item name="android.permission.ACCESS_COARSE_LOCATION" granted="true" flags="20" />
    <item name="android.permission.CAMERA" granted="true" flags="20" />
    <item name="android.permission.WRITE_EXTERNAL_STORAGE" granted="true" flags="20" />
    <item name="android.permission.RECORD_AUDIO" granted="true" flags="20" />
  </pkg>

  <pkg name="com.ss.android.article.news">
    <item name="android.permission.READ_EXTERNAL_STORAGE" granted="false" flags="1" />
    <item name="android.permission.ACCESS_COARSE_LOCATION" granted="true" flags="0" />
    <item name="android.permission.READ_PHONE_STATE" granted="false" flags="1" />
  </pkg>
  
</runtime-permissions>
3.10.1 RuntimePermissionPersistence.parseRuntimePermissionsLPr解析运行时权限文件

有了前面经验的加持,对于这里调用parseRuntimePermissionsLPr方法解析运行时权限文件,获得运行时权限相关的信息应该是手到擒来了,看招!

// 【 Settings.java 】
		/*
			用于解析运行时权限文件,获得运行时权限相关的信息!
		*/
        private void parseRuntimePermissionsLPr(XmlPullParser parser, int userId)
                throws IOException, XmlPullParserException {
            final int outerDepth = parser.getDepth();
            int type;
            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                    continue;
                }

                switch (parser.getName()) {
                    case TAG_RUNTIME_PERMISSIONS: {// runtime-permissions标签
                        String fingerprint = parser.getAttributeValue(null, ATTR_FINGERPRINT);
                        mFingerprints.put(userId, fingerprint);
                        final boolean defaultsGranted = Build.FINGERPRINT.equals(fingerprint);
                        mDefaultPermissionsGranted.put(userId, defaultsGranted);
                    } break;

                    case TAG_PACKAGE: {// 解析pkg标签
                        String name = parser.getAttributeValue(null, ATTR_NAME);
                        PackageSetting ps = mPackages.get(name);
                        if (ps == null) {
                            Slog.w(PackageManagerService.TAG, "Unknown package:" + name);
                            XmlUtils.skipCurrentTag(parser);
                            continue;
                        }
						// 解析并处理package的运行时权限授予情况! 详见章节 【 3.10.2 】
                        parsePermissionsLPr(parser, ps.getPermissionsState(), userId);
                    } break;

                    case TAG_SHARED_USER: {
                        String name = parser.getAttributeValue(null, ATTR_NAME);
                        SharedUserSetting sus = mSharedUsers.get(name);
                        if (sus == null) {
                            Slog.w(PackageManagerService.TAG, "Unknown shared user:" + name);
                            XmlUtils.skipCurrentTag(parser);
                            continue;
                        }
						// 解析并处理package的运行时权限授予情况!
                        parsePermissionsLPr(parser, sus.getPermissionsState(), userId);
                    } break;

                    case TAG_RESTORED_RUNTIME_PERMISSIONS: { // restored-perms 标签
                        final String pkgName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME); // 解析要被恢复的权限所属的应用包名
                        parseRestoredRuntimePermissionsLPr(parser, pkgName, userId); // 解析 restored-perms
                    } break;
                }
            }
        }

这个过程主要工作如下:

  • 解析并处理package的运行时权限授予情况;
  • 解析并处理shared-user的运行时权限授予情况;

上述两种类型的解析最后都会用到parsePermissionsLPr方法来进行下一步的处理,我们接着分析。

3.10.2 RuntimePermissionPersistence.parsePermissionsLPr解析和处理权限授予情况

我们来看下parsePermissionsLPr是如何解析和处理运行时权限的

// 【 Settings.java 】
		/*
			解析和处理运行时权限的
		*/
        private void parsePermissionsLPr(XmlPullParser parser, PermissionsState permissionsState,
                int userId) throws IOException, XmlPullParserException {
            final int outerDepth = parser.getDepth();
            int type;
            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                    continue;
                }

                switch (parser.getName()) {
                    case TAG_ITEM: {
                        String name = parser.getAttributeValue(null, ATTR_NAME);
                        BasePermission bp = mPermissions.get(name);
                        if (bp == null) {
                            Slog.w(PackageManagerService.TAG, "Unknown permission:" + name);
                            XmlUtils.skipCurrentTag(parser);
                            continue;
                        }

                        String grantedStr = parser.getAttributeValue(null, ATTR_GRANTED);
                        final boolean granted = grantedStr == null
                                || Boolean.parseBoolean(grantedStr);

                        String flagsStr = parser.getAttributeValue(null, ATTR_FLAGS);
                        final int flags = (flagsStr != null)
                                ? Integer.parseInt(flagsStr, 16) : 0;
						
						/*
							处理运行时权限的授予情况,这里和处理安装时权限很类似
						*/
                        if (granted) {
							// 如果上次安装时,该运行时权限处于授予状态,接着更新flags!
                            permissionsState.grantRuntimePermission(bp, userId);
                            permissionsState.updatePermissionFlags(bp, userId,
                                        PackageManager.MASK_PERMISSION_FLAGS, flags);
                        } else {
							// 如果上次安装时,该运行时权限处于未授予状态,只更新 flags!
                            permissionsState.updatePermissionFlags(bp, userId,
                                    PackageManager.MASK_PERMISSION_FLAGS, flags);
                        }

                    } break;
                }
            }
        }

这里我们看到,对于运行时权限已授予的情况,我们会先进行一次运行时权限授予,然后更新权限的flags;对于运行时权限未授予的情况,只是更新flags即可!此处的逻辑和章节3.4.3一致,这里就不展开分析了。


3.11 Settings.readLPw小结

至此Settings.readLPw的整个分析逻辑就到此结束了,逻辑不复杂,但是代码量超多。有点伤害不大,侮辱性极强的意味!虽然源码逻辑已经分析完了,但是我还是墙裂建议读者一定要将下面Settings的类图关系图给搞熟悉,因为它是我们前面readLPw的成果检验,并且在后续分析PKMS扫描应用安装目录的时候也会用到Settings中的数据结构。

在这里插入图片描述

最后我们得到如下的执行流程图:

在这里插入图片描述




四.PKMS启动BOOT_PROGRESS_PMS_START结尾阶段

好了,PKMS启动BOOT_PROGRESS_PMS_START到这里基本就结束了,剩余的就是一些扫尾的结束工作了,这里就简单过下,没有什么多说的呢。

// 【 Settings.java 】
            /* 
            	移除哪些codePath无效的Package(该Pacakge是系统App升级过来的被安装在/data/app分区)
            	此时需要恢复处于system目录下的同名package
            */
            final int packageSettingCount = mSettings.mPackages.size();
            for (int i = packageSettingCount - 1; i >= 0; i--) {
                PackageSetting ps = mSettings.mPackages.valueAt(i);
                if (!isExternal(ps) && (ps.codePath == null || !ps.codePath.exists())
                        && mSettings.getDisabledSystemPkgLPr(ps.name) != null) {
                    mSettings.mPackages.removeAt(i);
                    mSettings.enableSystemPackageLPw(ps.name);
                }
            }

            if (mFirstBoot) {
                /* 
                	如果是第一次开机,从另外一个系统拷贝 odex 文件到当前系统的 data 分区
         		 	Android 7.1 引进了 AB 升级,这个是 AB 升级的特性,可以先不看!
         		 */
                requestCopyPreoptedFiles();
            }
			
			// 判断是否自定义的解析界面(存在多个满足添加的Activiyt,弹出的选择界面的那个)
			// 及是否自定义的Intent resolver
            String customResolverActivity = Resources.getSystem().getString(
                    R.string.config_customResolverActivity);
            if (TextUtils.isEmpty(customResolverActivity)) {
                customResolverActivity = null;
            } else {
                mCustomResolverComponentName = ComponentName.unflattenFromString(
                        customResolverActivity);
            }

			//获取扫描开始的时间
            long startTime = SystemClock.uptimeMillis();

这里我只补充一点就是customResolverActivity是我们通过隐式启动一个应用时候,弹出的是否自定义的选择框,如果没有就是用Android系统预制的,如下:

customResolverActivity



五.PKMS启动BOOT_PROGRESS_PMS_START阶段总结

至此PKMS启动BOOT_PROGRESS_PMS_START阶段就分析完成了,读者是感到意犹未尽呢,还是感觉到分析得想吐了呢。木有办法,我们还是得总结一下该流程的主要过程:

  • 通过SystemConfig类解析系统相关配置信息,其中核心的如下几个系统属性信息:

    • group:获得系统中定义的gid,保存到了SystemConfig.mGlobalGids中!

    • permission:系统权限,每一个权限都创建一个PermissionEntry,解析完成之后保存到 SystemConfig.mPermissions中

    • assign-permission:系统uid和其具有的权限,解析完成之后将先关的数据保存到了SystemConfig.mSystemPermissions中!每一个uid都对应一个或多个权限;

    • library:共享库信息,解析完成之后保存到了SystemConfig.mSharedLibraries 中!

    • feature:系统特性信息,解析完成之后保存到了SystemConfig.mAvailableFeaturesSystemConfig.mUnavailableFeatures 中!

    最终得到如下的,数据结构图:

在这里插入图片描述

  • 通过Settings类读取上一次的安装信息,其中核心的就是解析packags.xml文件,其中最最核心的就是解析如下几个标签

  • package: 获取应用安装的相关信息

  • permissions:获取Android系统权限相关信息,包括Android系统预制的也包括第三方的

  • shared-user: 获取共享用户相关信息,包括Android系统预制的也包括第三方的

最终得到如下的,Settings类图关系图:

在这里插入图片描述

好了,到这里PackageManagerService启动详解(三)之BOOT_PROGRESS_PMS_START流程分析分析就告一段落了,各位青山不改绿水长流,各位江湖见!当然各位读者的点赞和关注是我写作路上前进的最大动力了,如果有啥不对或者不爽的也可以踩一踩也无妨!你们的鼓励和批评是博主前进路上最大的动力。感兴趣的读者可以移步>PKMS启动详解(四)之Android包信息体和解析器(上)继续下一个博客的打怪升级之路!

  • 8
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 17
    评论
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值