PackageManagerService启动详解(三)之BOOT_PROGRESS_PMS_START流程分析

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


Android PackageManagerService系列博客目录:

PackageManagerService启动详解系列博客概要
PackageManagerService启动详解(一)之整体流程分析
PackageManagerService启动详解(二)之对已安装应用怎么进行持久化存储管理?
PackageManagerService启动详解(三)之BOOT_PROGRESS_PMS_START流程分析



引言

  在前面的博客PackageManagerService启动详解(二)之对已安装应用信息怎么进行持久化存储管理?中我们特意花了一篇博客的篇幅从设计者的意图和功能点出发详细介绍了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/
	PackageSetting.java 
	Settings.java
	PackageSettingBase.java
	PackageSignatures.java
	SettingBase.java
	SharedUserSetting.java
	UserManagerService.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启动BOOT_PROGRESS_PMS_START前奏阶段

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

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

    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中
				并且根据解析的结果判断是否是第一次启动,如果是第一次开机,那么是没有相关数据的
			*/
			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的构建之前,我们先来看下它涉及的类图关系如下:


PackageManagerService启动详解(三)之BOOT_PROGRESS_PMS_START流程分析_第1张图片

关于Settings牵涉的类以及关联,可以详见前面的博客PackageManagerService启动详解(二)之对已安装应用怎么进行持久化存储管理?,在这里我们要分析的是它们之间的关联在源码层是怎么被构建的。并且墙裂建议读者在开始源码分析前,将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文件这里就不过展开了,可以到PackageManagerService启动详解(二)之对已安装应用怎么进行持久化存储管理?中进行细看。(为了节奏的连贯性,我还是简单介绍一下)

  • 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的源码前,我们先来看看它的类图(至于它的关系图,参见前面),如下:


PackageManagerService启动详解(三)之BOOT_PROGRESS_PMS_START流程分析_第2张图片


好了,让我们通过源码来揭开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发展阶段

好了,前面阶段分析完毕,我们开始下一阶段的分析,在这一阶段中我们的重中之重是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类图


PackageManagerService启动详解(三)之BOOT_PROGRESS_PMS_START流程分析_第3张图片


好了,我们继续往下看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中的根标签的子标签都放在一起,这样的排版比较好(注意readPermissions读取的配置xml文件的根标签必须是或者这两种)。

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

<permissions>
	
    <permission name="android.permission.BLUETOOTH" >
        <group gid="net_bt" />
    permission>
    ...

	
    <assign-permission name="android.permission.WAKE_LOCK" uid="media" />
	...
	
	
    <library name="android.test.runner"
            file="/system/framework/android.test.runner.jar" />
	...
	
	
	<feature name="android.hardware.bluetooth" />
	...
	
	            
    
    <allow-in-power-save package="com.android.shell" />

    
    <allow-in-data-usage-save package="com.android.providers.downloads" />

    
    <system-user-whitelisted-app package="com.android.settings" />

    
    <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, " 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, " 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, " without name in " + permFile + " at "
                                + parser.getPositionDescription());
                        XmlUtils.skipCurrentTag(parser);
                        continue;
                    }
                    String uidStr = parser.getAttributeValue(null, "uid");// 拥有该权限的uid
                    if (uidStr == null) {
     
                        Slog.w(TAG, " without uid in " + permFile + " at "
                                + parser.getPositionDescription());
                        XmlUtils.skipCurrentTag(parser);
                        continue;
                    }
                    int uid = Process.getUidForName(uidStr);// 将uid转为init值
                    if (uid < 0) {
     
                        Slog.w(TAG, " 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, " without name in " + permFile + " at "
                                + parser.getPositionDescription());
                    } else if (lfile == null) {
     
                        Slog.w(TAG, " 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, " 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, " 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>
	
    <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, " 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, " 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, " 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, " without name in " + permFile + " at "
                                + parser.getPositionDescription());
                        XmlUtils.skipCurrentTag(parser);
                        continue;
                    }
                    String uidStr = parser.getAttributeValue(null, "uid");// 拥有该权限的uid
                    if (uidStr == null) {
     
                        Slog.w(TAG, " without uid in " + permFile + " at "
                                + parser.getPositionDescription());
                        XmlUtils.skipCurrentTag(parser);
                        continue;
                    }
                    int uid = Process.getUidForName(uidStr);// 将uid转为init值
                    if (uid < 0) {
     
                        Slog.w(TAG, " 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中,如下:

PackageManagerService启动详解(三)之BOOT_PROGRESS_PMS_START流程分析_第4张图片

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, " without name in " + permFile + " at "
                                + parser.getPositionDescription());
                    } else if (lfile == null) {
     
                        Slog.w(TAG, " 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, " 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, " 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内部的数据关系图了,如下:


PackageManagerService启动详解(三)之BOOT_PROGRESS_PMS_START流程分析_第5张图片


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


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 libConfig = systemConfig.getSharedLibraries(); 
            for (int i=0; i

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

在此处有几个关键点我们需要理解:
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之间牵涉的数据关系之间的映射进行一下阶段性的总结,如下:


PackageManagerService启动详解(三)之BOOT_PROGRESS_PMS_START流程分析_第6张图片


这里最最重要的一点就是通过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中进行相关的处理,这个逻辑在后续分析第三方应用的安装和卸载中会很明显的看到。


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中。

过年了,快放假了本篇的博客肯定是写不完了。这里现将放出来,让感兴趣的读者可以先行阅读,不感兴趣的估计也不会看到这里了。

你可能感兴趣的:(SystemConfig,platform.xml,readPermission,packages.xml,PackageSetting)