PMS 简析

前端之家收集整理的这篇文章主要介绍了PMS 简析前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

pms 介绍:


这是 android studio编译 apk,安装成功后的截图

adb shell pm install -r "/data/local/tmp/com.crg.installtest"

就是安装 apk。

Android系统中PackageManagerService简称PMS,主要负责各种APK的安装,卸载,优化和查询
Android系统中,pm工具是PMS的一个展现命令。通过了解pm的使用,可以了解PMS究竟提供了哪些具体的功能,进而帮助我们理解和分析PMS的源码实现。

pm使用

利用adb shell命令,进入Android设备的终端,pm工具在/system/bin中,所以可以直接使用:

bullhead:/ $ which pm
/system/bin/pm
pm list packages [options] [FILTER]

打印所有的已经安装的应用的包名,如果设置了文件过滤则值显示包含过滤文字内容.
参数:
-f 显示每个包的文件位置
-d 使用过滤器,只显示禁用的应用的包名
-e 使用过滤器,只显示可用的应用的包名
-s 使用过滤器,只显示系统应用的包名
-3 使用过滤器,只显示第三方应用的包名
-i 查看应用的安装者
示例: 显示第三方的安装包

127|bullhead:/ $ pm list packages -3
package:com.sohu.inputmethod.sogou
package:com.baidu.searchBox
package:com.crg.installtest
package:com.tencent.mobileqq
package:com.willme.topactivity

安装与卸载apk

pm install [-lrtsfd] [-i PACKAGE] [--user USER_ID] [PATH]

adb install实际上就是对pm install的封装调用
参数:
-l 锁定应用程序
-r 重新安装应用,且保留应用数据
-t 允许测试apk被安装
-i INSTALLER_PACKAGE_NAME 指定安装包的包名
-s 安装到sd卡
-f 安装到系统内置存储中(默认安装位置)
-d 允许降级安装(同一应用低级换高级)
-g 授予应用程序清单中列出的所有权限(只有6.0系统可用)

包路径

pm path package_name

卸载apk:

pm uninstall [-k] [--user USER_ID] [--versionCode VERSION_CODE] PACKAGE

参数:
-k 卸载应用且保留数据与缓存(如果不加-k则全部删除

PMS运行时的一些规则

PMS相关的目录与文件,以及PMS操作它们的规则

APK文件路径

APK主要分为两类:

系统自带的APK

用户安装的第三方APK
对于系统自带的APK,可以从下面的路径中查找:

/system/priv-app
该路径存放一些系统底层的应用,比如Setting,systemUI等。该目录中的app拥有较高的系统权限,而且如果要使用

android:protectionLevel=signatureOrSystem

那么该app必须放到priv-app目录中去。
——————————————————————————————————————————————————————
/system/app
该目录中存放的系统app权限相对较低,而且当拥有root权限时,就有可能卸载掉这些app。
——————————————————————————————————————————————————————
/vendor/app
该目录存放vendor厂商的app
——————————————————————————————————————————————————————
/oem/app
该目录中存放oem特有的app。
——————————————————————————————————————————————————————
/data/app
用户安装的第三方app
PMS启动的时候,也是按照上述顺序逐个扫描解析这些目录中的apk的。

安装APK的方法

系统应用的安装
PMS启动的时候完成,没有安装界面。

通过设备自带的应用商店下载的apk
下载完APK之后,调用Packagemanager接口安装,没有安装界面。

ADB命令安装
adb命令实际上是通过pm命令来安装的,同样没有安装界面。

第三方应用安装
通过SD卡里的APK文件安装,有安装界面,由packageinstaller.apk应用处理安装及卸载过程的界面。

在ANdroid 5.0之前,Android系统会监视前面所说的几个存放apk的路径,一旦发现有新的apk被放进去了,就会自带安装。5.0之后,不在采取此策略。只有系统启动的时候,才会扫描这写路径中的apk。


apk数据存储的位置
apk安装之后,产生的数据都存储在/data文件中。

/data/app
用户安装的第三方apk,以及app所依赖的native library都放在这里。
在Android 6.0时,此目录了一个文件夹“oat”,用来存放此app,第一次运行时由dex2oat生成的此app的oat文件
手百的目录:

bullhead:/data/app/com.baidu.searchBox-EeE_6P6R7H8-djrQTbZyxg== # ls -al
total 49676
drwxr-xr-x 4 system system      4096 1970-01-04 23:31 .
drwxrwx--x 7 system system      4096 1970-01-04 23:31 ..
-rw-r--r-- 1 system system  50850328 1970-01-04 23:31 base.apk
drwxr-xr-x 3 system system      4096 1970-01-04 23:31 lib
drwxrwx--x 3 system install     4096 1970-01-04 23:31 oat

oat 目录:

bullhead:/data/app/com.baidu.searchBox-EeE_6P6R7H8-djrQTbZyxg==/oat/arm # ls -al
total 31500
drwxrwx--x 2 system install     4096 1970-01-05 00:00 .
drwxrwx--x 3 system install     4096 1970-01-04 23:31 ..
-rw-r----- 1 system all_a66   131072 1970-01-05 00:00 base.art
-rw-r----- 1 system all_a66  3035560 1970-01-04 23:31 base.odex
-rw-r----- 1 system all_a66 29073860 1970-01-05 00:00 base.vdex

在之前的Android版本中,用户安装的app的oat文件存储在

/data/dalvik-cache

中。6.0时,此目录只存放系统自带的apk的oat文件
——————————————————————————————————————————————————————
/data/data/
是系统当前用户安装的所有app的沙箱目录。该目录实际上是
/data/user/用户ID
这个目录的引用。随着用户的切换,”/data/data/“也会映射为不同的用户

bullhead:/data/user # ls -al
total 12
drwx--x--x 2 system system 4096 1970-01-04 22:38 .
drwxrwx--x 41 system system 4096 1970-01-04 22:38 ..
lrwxrwxrwx  1 root   root     10 1970-01-04 22:38 0 -> /data/data

PMS的配置文件

PMS会产生一些配置文件,用来记录系统当前安装的app,这些文件存储在:

/data/system/
  1. packages.xml

记录系统中所有已经安装的应用信息,包括基本信息,签名和权限。
如下图所示:

<package name="com.baidu.searchBox" codePath="/data/app/com.baidu.searchBox-EeE_6P6R7H8-djrQTbZyxg==" nativeLibraryPath="/data/app/com.baidu.searchBox-EeE_6P6R7H8-djrQTbZyxg==/lib" primarycpuAbi="armeabi" publicFlags="945307236" privateFlags="0" ft="147f40e0" it="147f7179" ut="147f7179" version="38274304" userId="10066">
        <sigs count="1">
            <cert index="5" key="30820253308201bca00302010202044b9dd8e7300d06092a864886f70d0101050500306d310b300906035504061302434e3111300f060355040813085368616e676861693111300f060355040713085368616e6768616931133011060355040a130a426169647520496e632e31133011060355040b130a426169647520496e632e310e300c0603550403130542616964753020170d3130303331353036353131395a180f32303634313231363036353131395a306d310b300906035504061302434e3111300f060355040813085368616e676861693111300f060355040713085368616e6768616931133011060355040a130a426169647520496e632e31133011060355040b130a426169647520496e632e310e300c06035504031305426169647530819f300d06092a864886f70d010101050003818d00308189028181009c7a58a39572c4b379ddfca6765e95d3aec69fe362ce622e629647cf441b9e4b7b695e540fd29b7da7b2ab64793089f2b69112d11ac5776973dd68cff88b671826c1286e57c7294c76c7c118ae41bf9336ff9ae0aa90c65ed7db0749ff137b815b6d3b53abaad72d7817b0b8900caef12eea13d12baf0b8cb30543bfb3489c230203010001300d06092a864886f70d01010505000381810063231fb3859d01f75cd7ed810aa5c08eb8fba5b7b7bf11f2c65ae70aa69365b7c985334a38be2c6712c77a1b8aa09d1ae84b51b0062968734700f795b08a7ff5dd73751cd63254f211cb6386fa733690d826b44c169f76c23b82f813b15c1da47a2be69369cd75bf7cdaa337d2ea38726a778583838409b482efc126f7e668b3" />
        </sigs>
        <perms>
            <item name="android.permission.RESTART_PACKAGES" granted="true" flags="0" />
            <item name="android.permission.USE_CREDENTIALS" granted="true" flags="0" />
            <item name="android.permission.MODIFY_AUdio_SETTINGS" granted="true" flags="0" />
            <item name="android.permission.MANAGE_ACCOUNTS" granted="true" flags="0" />
            <item name="com.baidu.searchBox.permission.DOWNLOAD_STORY" granted="true" flags="0" />
            <item name="android.permission.NFC" granted="true" flags="0" />
            <item name="android.permission.CHANGE_NETWORK_STATE" granted="true" flags="0" />
            <item name="android.permission.RECEIVE_BOOT_COMPLETED" granted="true" flags="0" />
            <item name="android.permission.EXPAND_STATUS_BAR" granted="true" flags="0" />
            <item name="com.android.launcher.permission.UNINSTALL_SHORTCUT" granted="true" flags="0" />
            <item name="android.permission.BLUETOOTH" granted="true" flags="0" />
            <item name="android.permission.GET_TASKS" granted="true" flags="0" />
            <item name="android.permission.INTERNET" granted="true" flags="0" />
            <item name="com.android.browser.permission.READ_HISTORY_BOOKMARKS" granted="true" flags="0" />
            <item name="com.baidu.searchBox.permission.APS_INSTALL" granted="true" flags="0" />
            <item name="baidu.push.permission.WRITE_PUSHINFOPROVIDER.com.baidu.searchBox" granted="true" flags="0" />
            <item name="android.permission.BROADCAST_STICKY" granted="true" flags="0" />
            <item name="android.permission.CHANGE_WIFI_STATE" granted="true" flags="0" />
            <item name="android.permission.FLASHLIGHT" granted="true" flags="0" />
            <item name="android.permission.ACCESS_NETWORK_STATE" granted="true" flags="0" />
            <item name="android.permission.DISABLE_KEYGUARD" granted="true" flags="0" />
            <item name="android.permission.SET_WALLPAPER" granted="true" flags="0" />
            <item name="android.permission.USE_FINGERPRINT" granted="true" flags="0" />
            <item name="android.permission.VIBRATE" granted="true" flags="0" />
            <item name="com.baidu.searchBox.permission.MIPUSH_RECEIVE" granted="true" flags="0" />
            <item name="android.permission.ACCESS_WIFI_STATE" granted="true" flags="0" />
            <item name="com.android.launcher.permission.INSTALL_SHORTCUT" granted="true" flags="0" />
            <item name="android.permission.WAKE_LOCK" granted="true" flags="0" />
        </perms>
        <proper-signing-keyset identifier="9" />
    </package>

当操作该文件的时候,总会创建备份文件packages-backup.xml.当正常操作完成的时候,会删除该备份。否则,当PMS下次启动的时候,一旦发现有backup文件,就会优先解析备份文件
当一个app被升级覆盖安装时,会使用updated-packages表示,当新旧版本app的包名发生改变时,会使用renamed-package记录。
——————————————————————————————————————————————————————
packages-stoped.xml
记录系统中被强制停止运行的app的信息。它同样可能存在一个packages-stoped-backup.xml的备份文件,当备份文件存在的时候,优先使用备份文件。因为原文件可能已经损坏了。

packages.list
保存应用的数据目录和uid信息。如:

com.android.camera2 10036 0 /data/user/0/com.android.camera2 default:targetSdkVersion=24 3003
com.baidu.searchBox 10066 0 /data/user/0/com.baidu.searchBox default:targetSdkVersion=26 3002,3003
com.crg.installtest 10062 1 /data/user/0/com.crg.installtest default:targetSdkVersion=25 none

第一列为app的包名。
10067为此app的用户ID.
第三列中的0,表示此app所属的系统用户ID.
第四列为此app的沙箱目录。
default为seinfo,SEAndroid相关机制会使用该字段。
最后一列记录了该app所在的权限组,也就是说拥有哪些权限。
——————————————————————————————————————————————————————

系统硬件特性和权限

PMS启动的时候会从

/system/etc/permissions/

中读取当前Android设备的硬件特性和设定的相关权限。
所谓的硬件特性,狭义上可以理解为当前设备支持哪些外设,比如camera,NFC,wifi,usb等等。
正是因为解析了该目录中的文件,所以可以通过pm命令查看features和permissions等信息。

//系统硬件特性
pm list features
//设备依赖的java库
pm list libraries
//dump包信息
pm dump package_name

用户管理

PMS还要对多用户进行管理。因为安装apk的时候,可以PMS可以指定给某个特定的用户,也可以安装给全部的用户

权限动态管理

Android M 中 允许动态授权和取消App中申请的权限。
Android 6.0之后,允许授权和取消权限:

pm grant <package_name> <permission>    
pm revoke <package_name> <permission>

授权和取消是针对APK中申请的权限的来说的。即APK中没有申请的权限,是没办法通过此命令添加的。

PMS的入口:

PMS是由SystemServer启动的。

public static void main(String[] args) {
        new SystemServer().run();
    }

    public SystemServer() {
        // Check for factory test mode.
        mFactoryTestMode = FactoryTest.getMode();
        // Remember if it's runtime restart(when sys.boot_completed is already set) or reboot
        mRuntimeRestart = "1".equals(SystemProperties.get("sys.boot_completed"));
    }

    private void run() {
    ...

        // Start services.
        try {
            traceBeginAndSlog("StartServices");

            // 启动系统服务
            startBootstrapServices();
            startCoreServices();
            startOtherServices();
            SystemServerInitThreadPool.shutdown();
private void startBootstrapServices() {
        .........
        traceBeginAndSlog("StartPackageManagerService");
        mPackageManagerService = PackageManagerService.main(mSystemContext,installer,mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF,mOnlyCore);
        mFirstBoot = mPackageManagerService.isFirstBoot();
        mPackageManager = mSystemContext.getPackageManager();
        traceEnd();

这样就找到了分析PMS的入口点,PackageManagerService.java的main方法

public static PackageManagerService main(Context context,Installer installer,boolean factoryTest,boolean onlyCore) {
        // Self-check for initial settings.
        PackageManagerServiceCompilerMapping.checkProperties();

        PackageManagerService m = new PackageManagerService(context,factoryTest,onlyCore);
        m.enableSystemUserPackages();
        ServiceManager.addService("package",m);
        return m;
    }

main函数中的第二个参数installer负责和native层中的installd守护进程进行socket通信,这里在启动Installer 的时候会有installd创建一些关键目录,后续会详细介绍installd守护进程。
第四个参数mOnlyCore用于判断是否仅仅扫描系统的目录,只有在与data分区加解密时才会设置为true,其他情况一般都为false。

这里主要做了两件事:

  1. 创建PackageManagerService对象,也就是PMS的实体
    将PMS向SMS中注册,即加入SMS中,方便后续其他进程或者app通过SMS获得PMS服务
  2. PMS的构造函数中,做了大量的工作,总结起来就是扫描Android系统中的apk,并且建立相应的数据结构去管理Package的信息,四大组件的信息,权限信息等内容

PMS构造函数分析之Settings

接着创建一个DisplayMetrics对象,用于保存屏幕像素参数。

创建一个Settings对象。

mPermissionReviewrequired = context.getResources().getBoolean(
                R.bool.config_permissionReviewrequired);

        mFactoryTest = factoryTest;
        mOnlyCore = onlyCore;
        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.PRIVATE_FLAG_PRIVILEGED);
        mSettings.addSharedUserLPw("android.uid.log",LOG_UID,ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
        mSettings.addSharedUserLPw("android.uid.nfc",NFC_UID,ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
        mSettings.addSharedUserLPw("android.uid.bluetooth",BLUETOOTH_UID,ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
        mSettings.addSharedUserLPw("android.uid.shell",SHELL_UID,ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);

Settings构造方法

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

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

        mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock);

        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);
        mSettingsFilename = new File(mSystemDir,"packages.xml");
        mBackupSettingsFilename = new File(mSystemDir,"packages-backup.xml");
        mPackageListFilename = new File(mSystemDir,"packages.list");
        FileUtils.setPermissions(mPackageListFilename,0640,SYSTEM_UID,PACKAGE_INFO_GID);

        final File kernelDir = new File("/config/sdcardfs");
        mKernelMappingFilename = kernelDir.exists() ? kernelDir : null;

        // Deprecated: Needed for migration
        mStoppedPackagesFilename = new File(mSystemDir,"packages-stopped.xml");
        mBackupStoppedPackagesFilename =

dataDir为
/data
所以mSystemDir为
/data/system
该构造函数中主要创建了前面文章中介绍的关于PMS的若干配置文件的File对象,并且设置了权限等。

mSettings.addSharedUserLPw
addSharedUserLPw方法

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;
        }
        s = new SharedUserSetting(name,pkgFlags,pkgPrivateFlags);
        s.userId = uid;
        if (addUserIdLPw(uid,s,name)) {
            mSharedUsers.put(name,s);
            return s;
        }
        return null;
    }

方法主要是创建共享UID的相关信息。
这里先以name为key在mSharedUsers中查看找是否已经有名为name的共享UID了。如果有的话,判断此UID和传入的uid是否相等,不相等的就报错。这意味着,不能对已有的共享UID信息,绑定新的uid。
如果没有的话,创建一个新的SharedUserSetting,并将其加入mSharedUsers中去。
pkgFlags为ApplicationInfo.FLAG_SYSTEM表明该app是system app.
pkgPrivateFlags为ApplicationInfo.PRIVATE_FLAG_PRIVILEGED表明具备system权限。

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

        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;
    }

主要针对普通uid和system uid进行不同处理。
每个普通的app安装的时候,都会被分配一个uid,这和Android系统一些系统服务和具备高权限的app的uid是不在一个区间的。
普通的app的uid加入mUsrIds,其他的加入mOtherUserIds.
PMS构造方法中创建了android.uid.system,android.uid.phone,android.uid.log,android.uid.nfc,android.uid.bluetooth,android.uid.shell这些共享uid。主要给以下场景使用提供便利:
在一些系统app的AndroidManifest.xml中会有下面的信息:

``` 或者android.uid.phone等,如果不提前创建好,就没办法拥有这些共享uid所具备的权限了。
——————————————————————————————————————————————————————————————————————————————————————————————————————————————
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER,"get system config");
    SystemConfig systemConfig = SystemConfig.getInstance();
    mGlobalGids = systemConfig.getGlobalGids();
    mSystemPermissions = systemConfig.getSystemPermissions();
    mAvailableFeatures = systemConfig.getAvailableFeatures();
    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
创建和初始化SystemConfig
SystemConfig会读取

/system/etc/permissions

文件夹中的相关文件

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

其中rootDirectory是“/system”.oemDirectory是”/oem”.也就是会尝试依次读取

/system/etc/sysconfig
/system/etc/permissions
/oem/etc/sysconfig
/oem/etc/permissions

这四个目录中的permission文件。
permission文件的读取是通过readPermissions函数完成的:

void readPermissions(File libraryDir,int permissionFlag) {
// Read permissions from given directory.
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;
}

// 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;
        }

        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);
    }

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

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;
    }

“`
readPermissions方法的作用就是读取指定目录下的xml文件,然后调用readPermissionsFromXml方法来解析xml文件
这些xml文件中指明了当前设备的硬件特性:

SystemConfig中有一个ArrayMap的mPermissions变量,创建的PermissionEntry会存入该变量中。而group中的uid则会保存在SystemConfig中的mGlobalGids整型数组中。 assign-permission表示把属性name中的字符串表示的权限赋予属性uid中的用户。uid和name则存入SystemConfig中的SparseArray> 类型的mSystemPermissions变量中。

原文链接:https://www.f2er.com/bash/390925.html

猜你在找的Bash相关文章