Android7.0 PackageManagerService (2) PKMS构造函数的主要工作

这篇博客深入剖析了Android7.0中PackageManagerService (PKMS)的构造函数,重点讲解了Settings对象的创建、SharedUserSettings的管理和XML配置信息的读取。PKMS通过Settings保存系统设置信息,如packages.xml和packages-list,管理Android系统中APK的状态。SharedUserSettings用于管理共享用户ID的APK,确保权限正确设置。此外,博客还探讨了XML文件如platform.xml和mac_permissions.xml的解析,用于定义权限和签名策略。最后,文章介绍了如何扫描和解析APK文件,形成Package数据结构,以便PKMS管理。
摘要由CSDN通过智能技术生成

从本篇博客开始,我们开始分析PKMS的构造函数,看看PKMS到底是如何解析和管理手机中APK的信息的。
由于PKMS的构造函数较长,我们会分段进行研究。

public PackageManagerService(Context context, Installer installer,
        boolean factoryTest, boolean onlyCore) {
    ........
    mContext = context;
    mFactoryTest = factoryTest; //假定为false,运行在非工厂模式下
    mOnlyCore = onlyCore; //假定为false,即扫描所有的APK
    mMetrics = new DisplayMetrics(); //分辨率相关

    mSettings = new Settings(mPackages);
    mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
    mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID,
        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
    mSettings.addSharedUserLPw("android.uid.log", LOG_UID,
        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
    mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID,
        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
    mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID,
        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
    mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID,
        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
    ...................
}

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

Settings(Object lock) {
    this(Environment.getDataDirectory(), lock);
}

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

    mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock);

    //目录指向"data/system"
    mSystemDir = new File(dataDir, "system");
    //创建目录
    mSystemDir.mkdirs();
    FileUtils.setPermissions(mSystemDir.toString(),
            FileUtils.S_IRWXU|FileUtils.S_IRWXG
            |FileUtils.S_IROTH|FileUtils.S_IXOTH,
            -1, -1);
    //packages.xml和packages-backup.xml为一组,用于描述系统所安装的Package信息,其中packages-backup.xml是packages.xml的备份
    //PKMS写把数据写到backup文件中,信息全部写成功后在改名为非backup文件,以防止在写文件的过程中出错,导致信息丢失
    mSettingsFilename = new File(mSystemDir, "packages.xml");
    mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml");

    //packages.list保存系统中存在的所有非系统自带的APK信息,即UID大于10000的apk
    mPackageListFilename = new File(mSystemDir, "packages.list");
    FileUtils.setPermissions(mPackageListFilename, 0640, SYSTEM_UID, PACKAGE_INFO_GID);

    //感觉是sdcardfs相关的文件
    final File kernelDir = new File("/config/sdcardfs");
    mKernelMappingFilename = kernelDir.exists() ? kernelDir : null;

    // Deprecated: Needed for migration
    //packages-stopped.xml用于描述系统中强行停止运行的package信息,backup也是备份文件
    mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml");
    mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml");
}

从代码可以看出,Settings的构造函数主要用于创建一些目录和文件,并配置相应的权限。其中:
* PKMS扫描完目标文件夹后,会创建packages.xml。当系统进行程序安装、卸载和更新等操作时,均会更新该文件;
* packages-list用于描述系统中存在的所有非系统自带的APK信息。当这些APK有变化时,PKMS就会更新该文件;
* packages-stopped.xml记录被用户强行停止的应用的Package信息(例如,从设置进入某个应用,然后点击强行停止,那么应用的Package信息就会被记录)。

因此,我们可以推测出Settings主要用于保存一些信息,实际上它确实是用于管理Android系统运行过程中的一些设置信息。

我们继续跟进Settings的addSharedUserLPw函数:

//name和uid一一对应,例如:"android.uid.system":Process.SYSTEM_UID(1000)
//                      "android.uid.phone" :RADIO_UID(Process.PHONE_UID, 1001)
//pkgFlags均为:ApplicationInfo.FLAG_SYSTEM
//pkgPrivateFlags均为:ApplicationInfo.PRIVATE_FLAG_PRIVILEGED
SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) {
    SharedUserSetting s = mSharedUsers.get(name);
    if (s != null) {
        if (s.userId == uid) {
            return s;
        }
        PackageManagerService.reportSettingsProblem(Log.ERROR,
                "Adding duplicate shared user, keeping first: " + name);
        return null;
    }
    //目的就是利用参数构造出SharedUserSetting
    s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags);
    s.userId = uid;

    if (addUserIdLPw(uid, s, name)) {
        //按 <名称---SharedUserSettings> 存入map中
        mSharedUsers.put(name, s);
        return s;
    }
    return null;
}

private boolean addUserIdLPw(int uid, Object obj, Object name) {
    //LAST_APPLICATION_UID = 19999
    if (uid > Process.LAST_APPLICATION_UID) {
        return false;
    }

    //普通APK的uid
    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 {
        if (mOtherUserIds.get(uid) != null) {
            PackageManagerService.reportSettingsProblem(Log.ERROR,
                    "Adding duplicate shared id: " + uid
                        + " name=" + name);
            return false;
        }
        mOtherUserIds.put(uid, obj);
    }
    return true;
}

PKMS创建Settings后,调用一系列的addSharedUserLPw函数,将形成如上图所示的数据结构。

如图所示,PKMS将根据参数构建出SharedUserSettings对象,可以通过两个维度来引用创建出的对象,即名称和uid。
在Settings中mSharedUsers是一个map对象,利用名称作为索引管理SharedUserSettings对象。
Settings中的mOtherUserIds和mUserIds,均是利用userId作为索引管理SharedUserSettings对象。不同的是mOtherUserIds是SparseArray,以系统uid作为键值;mUserIds是ArrayList,普通APK的uid为ArrayList的下标。

说了这么多,SharedUserSettings到底是什么?PKMS为什么要花这么大的力气,创建和管理SharedUserSettings?接下来,我们就来逐步揭晓答案。

1.1 SharedUserSettings
我们看看SharedUserSettings类:

final class SharedUserSetting extends SettingBase {
   
    final String name;

    int userId;

    // flags that are associated with this uid, regardless of any package flags
    int uidFlags;
    int uidPrivateFlags;

    //关键点
    final ArraySet<PackageSetting> packages = new ArraySet<PackageSetting>();

    final PackageSignatures signatures = new PackageSignatures();

    SharedUserSetting(String _name, int _pkgFlags, int _pkgPrivateFlags) {
        super(_pkgFlags, _pkgPrivateFlags);
        uidFlags =  _pkgFlags;
        uidPrivateFlags = _pkgPrivateFlags;
        name = _name;
    }
    ............
    void removePackage(PackageSetting packageSetting) {
        if (packages.remove(packageSetting)) {
            .......
        }
    }

    void addPackage(PackageSetting packageSetting) {
        if (packages.add(packageSetting)) {
            ........
        }
    }
}

从上面的代码来看,SharedUserSettings将持有一组PackageSetting。
从SharedUserSettings的命名来看,这一组PackageSetting应该有相似的共性。

为了进一步分析,我们举个例子来看看。
在packages/apps/Settings的AndroidManifest.xml中,有以下内容:

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

如上所示,在xml文件中,声明了一个名为android:sharedUserId的属性,其值为”android.uid.system”。
实际上多个声明了同一种sharedUserId的APK可共享彼此的数据,并且可运行在同一进程中。更重要的是,通过声明特点的sharedUserId,该APK所在的进程将被赋予指定UID对应的权限。

我们知道Android系统中的UID表示用户ID,GID表示用户组ID,均与Linux系统中进程的权限管理有关。一般来说,每一个进程都会有一个对应的UID,针对不同的UID可以有不同的权限;同时,每个进程也可以分属于不同的用户组,即有对应的GID,针对不同的GID也可以有不同的权限。

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

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

1.2 SharedUserSettings相关的类图

在这一部分的最后,我们来简单回顾一下SharedUserSettings相关的类图。
如上图所示,Settings对象中持有多个SharedUserSettings对象,每个SharedUserSettings对象由会持有多个PackageSettings对象。
从继承关系来看,SharedUserSettings和PackageSettings对象,最终都将继承SettingsBase对象。

从图上可以看出,SettingsBase对象持有PermissionsState对象,用于表示可用的权限。
因此,SharedUserSettings对象和PackageSettings对象中都将包含有PermissionsState。
可以据此推测出,SharedUserSettings中持有的是一组Package共有的权限;PackageSettings中持有的是单个Package独有的权限。

PKMS中Settings除去SharedUserSettings之外,还管理了其它重要的数据结构,我们暂时略过,等流程涉及到时,再作分析。

二、读取XML文件中系统配置信息
我们回到PKMS的构造函数,看下一段代码:

//debug相关
.......
//构造函数传入的InstallerService,与底层Installd通信
mInstaller = installer;
mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,
        "*dexopt*");

//定义一些回调函数
mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());

mOnPermissionChangeListeners = new OnPermissionChangeListeners(
        FgThread.get().getLooper());

//存储显示信息
getDefaultDisplayMetrics(context, mMetrics);

//获取系统配置信息
SystemConfig systemConfig = SystemConfig.getInstance();
//将系统配置信息,存储到PKMS中
mGlobalGids = systemConfig.getGlobalGids();
mSystemPermissions = systemConfig.getSystemPermissions();
mAvailableFeatures = systemConfig.getAvailableFeatures();
..........

在这一段代码中,PKMS创建了许多对象,暂时可以先不管它们,重点看看SystemConfig相关的函数。

//单例模式
public static SystemConfig getInstance() {
    synchronized (SystemConfig.class) {
        if (sInstance == null) {
            sInstance = new SystemConfig();
        }
        return sInstance;
    }
}

SystemConfig() {
    // Read configuration from system
    //从“system”目录下读取
    readPermissions(Environment.buildPath(
            Environment.getRootDirectory(), "etc", "sysconfig"), ALLOW_ALL);

    // Read configuration from the old permissions dir
    readPermissions(Environment.buildPath(
            Environment.getRootDirectory(), "etc", "permissions"), ALLOW_ALL);

    // Allow ODM to customize system configs around libs, features and apps
    //从"/odm"目录下读取
    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);

    // Only allow OEM to customize features
    //从“oem”目录下读取
    readPermissions(Environment.buildPath(
        Environment.getOemDirectory(), "etc", "sysconfig"), ALLOW_FEATURES);
    readPermissions(Environment.buildPath(
        Environment.getOemDirectory(), "etc", "permissions"), ALLOW_FEATURES);
}

从上面的代码可以看出,创建SystemConfig时,将从不同的“etc”目录下读取权限信息,包括root目录、odm和oem目录,不同目录对应的可读取权限的范围不同。

我们看看readPermissions函数:

void readPermissions(File libraryDir, int permissionFlag) {
    //检测目录是否存在,是否可读
    ..........
    // Iterate over the files in the directory and scan .xml files
    File platformFile = null;
    for (File f : libraryDir.listFiles()) {
        // We'll read platform.xml last
        if (f.getPath().endsWith("etc/permissions/platform.xml")) {
            platformFile = f;
            continue;
        }

        //仅读取可读的xml文件
        ..........
        readPermissionsFromXml(f, permissionFlag);
    }

    // Read platform permissions last so it will take precedence
    if (platformFile != null) {
        readPermissionsFromXml(platformFile, permissionFlag);
    }
}

现在我们知道了,readPermissions就是从指定目录下,读取xml中的配置的权限信息。实际的手机上,可能没有代码中指定的所有目录,例如没有“odm”等,但system/etc/permissions目录一般都是有的。

1、xml文件内容举例
我手边有一台root过的android 6.0的手机,以system目录为例,看看其system/etc/permissions下的xml文件:

android.hardware.bluetooth.xml
android.hardware.camera.xml
//中间略去了一些。在中间甚至有厂商自己添加的xml
//与Android的设计初衷不符,可能是考虑到PKMS限制了"oem"目录下可以定义的权限种类,才添加到这个位置的
.......
platform.xml

1.1 platform.xml
platform.xml优先级最高,我们先看看platform.xml中的内容:

<?xml version="1.0" encoding="utf-8"?>
<!-- This file is used to define the mappings between lower-level system
     user and group IDs and the higher-level permission names managed
     by the platform.

     Be VERY careful when editing this file!  Mistakes made here can open
     big security holes.
-->

<permissions>
<!-- The following tags are associating low-level group IDs with
         permission names.  By specifying such a mapping, you are saying
         that any application process granted the given permission will
         also be running with the given group ID attached to its process,
         so it can perform any filesystem (read, write, execute) operations
         allowed for that group. -->
    <!--建立权限名与gid的映射关系,一般与需要和读写底层设备的进程会声明这些权限
        这些权限涉及和Linux内核交互,所以需要在底层权限(由不同的用户的用户组界定)和Android层权限(由不同的字符串界定)之间建立映射关系 -->
    <permission name="android.permission.BLUETOOTH_ADMIN" >
        <group gid="net_bt_admin" />
    </permission>

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

    <!-- The following tags are assigning high-level permissions to specific
         user IDs.  These are used to allow specific core system users to
         perform the given operations with the higher-level framework.  For
         example, we give a wide variety of permissions to the shell user
         since that is the user the adb shell runs under and developers and
         others should have a fairly open environment in which to
         interact with the system. -->
    <!--赋予对应uid相应的权限。例如下面第一行,uid为media时,那么就赋予其修改Audio设置的权限
        其实就是将uid加入到对应的用户组中-->
    <assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="media" />
    <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="media" />
    ............

    <!-- This is a list of all the libraries available for application
        code to link against. -->
    <!--系统提供的java库,APK运行时可以链接这些库-->
    <library name="android.test.runner"
        file="/system/framework/android.test.runner.jar" />
    ..........

    <!-- These are the standard packages that are white-listed to always have internet
         access while in power save mode, even if they aren't in the foreground. -->
    <!--指定进程在省电模式下(非Idle态)仍可访问网络)-->
    <allow-in-power-save-except-idle package="com.android.providers.downloads" />

    <!-- Whitelist of what components are permitted as backup data transports.  The
         'service' attribute here is a flattened ComponentName string. -->
    <!--指定服务可以传输备份数据-->
    <backup-transport-whitelisted-service
        service="android/com.android.internal.backup.LocalTransportService" />
    <backup-transport-whitelisted-service
        service="com.google.android.gms/.backup.BackupTransportService"
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值