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

本文深入分析了Android PackageManagerService (PKMS) 启动过程中的BOOT_PROGRESS_PMS_START阶段,重点探讨了packages.xml文件的解析流程及其对应用安装信息管理的重要性。

  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
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值