深入Android系统(十)PMS-2-初始化的一些细节

permission文件的处理

PMS的构造方法中初始化了PermissionManagerService,通过PermissionManagerService.create()方法,相关调用如下:

    public static PermissionManagerInternal create(......) {
        ......
        new PermissionManagerService(context, defaultGrantCallback, externalLock);
        ......
    }
    PermissionManagerService(......) {
        ......
        mHandlerThread = new ServiceThread(TAG,
                Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
        mHandlerThread.start();
        mHandler = new Handler(mHandlerThread.getLooper());
        Watchdog.getInstance().addThread(mHandler);
        ......
        SystemConfig systemConfig = SystemConfig.getInstance();
        mSystemPermissions = systemConfig.getSystemPermissions();
        ......
    }

上面列出来PermissionManagerService初始化的过程中两个操作:

  • 注册Watchdog监听,此处列出它来主要是为了呼应前面的章节。哈哈哈,重点是第二点
  • 通过SystemConfiggetSystemPermissions方法来获取系统的Permission列表
    public SparseArray<ArraySet<String>> getSystemPermissions() {
        return mSystemPermissions;
    }
    

而对于mSystemPermissions,是在SystemConfig的构造方法中完成的,相关代码如下:

    //单例模式
    SystemConfig() {
        // 读取 /system/etc/sysconfig 目录下的 permission 文件
        readPermissions(Environment.buildPath(
                Environment.getRootDirectory(), "etc", "sysconfig"), ALLOW_ALL);
        // 读取 /system/etc/permissions 目录下的 permission 文件
        readPermissions(Environment.buildPath(
                Environment.getRootDirectory(), "etc", "permissions"), ALLOW_ALL);
        ......
        // 读取 /vendor/etc/sysconfig 目录下的 permission 文件
        readPermissions(Environment.buildPath(
                Environment.getVendorDirectory(), "etc", "sysconfig"), vendorPermissionFlag);
        // 读取 /vendor/etc/permissions 目录下的 permission 文件
        readPermissions(Environment.buildPath(
                Environment.getVendorDirectory(), "etc", "permissions"), vendorPermissionFlag);
        ...// 省略 /oem、/odm、/product对应分区目录下 readPermissions()
    }

不用说,核心是这个readPermissions()方法,看看:

    void readPermissions(File libraryDir, int permissionFlag) {
        // Read permissions from given directory.
        if (!libraryDir.exists() || !libraryDir.isDirectory()) {
            return;// 如果目录不存在或者libraryDir不是目录,直接返回
        }
        if (!libraryDir.canRead()) {
            return;// 如果目录不可读,直接返回
        }
        File platformFile = null;
        // 循环能处理目录文件
        for (File f : libraryDir.listFiles()) {
            if (f.getPath().endsWith("etc/permissions/platform.xml")) {
                // 检测到platform.xml文件,先记录,循环中不做处理
                platformFile = f;
                continue;
            }
            if (!f.getPath().endsWith(".xml")) {
                // 跳过非xml文件
                continue;
            }
            if (!f.canRead()) {
                // 跳过不可读文件
                continue;
            }
            readPermissionsFromXml(f, permissionFlag);// 解析xml
        }
        // 最后,解析platform.xml文件
        if (platformFile != null) {
            readPermissionsFromXml(platformFile, permissionFlag);
        }
    }

readPermissions()方法先检测指定目录下的xml文件,然后调用readPermissionsFromXml方法来解析文件并将解析结果填充到SystemConfig对应的数据结构中。

readPermissionsFromXml()方法内容如下:

    private void readPermissionsFromXml(File permFile, int permissionFlag) {
        FileReader permReader = new FileReader(permFile);
        ......
        try {
            XmlPullParser parser = Xml.newPullParser();
            parser.setInput(permReader);
            int type;
            // 查找 START_TAG 或  END_DOCUMENT
            while ((type=parser.next()) != parser.START_TAG
                       && type != parser.END_DOCUMENT) {
                ;
            }
            // 从 START_TAG 开始解析
            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(...);
            }
            ......
            while (true) {
                ......
                String name = parser.getName();
                // 能够识别的标签如下:
                if ("group".equals(name) && allowAll) {
                } else if ("permission".equals(name) && allowPermissions) {
                } else if ("assign-permission".equals(name) && allowPermissions) {
                } else if ("library".equals(name) && allowLibs) {
                } else if ("feature".equals(name) && allowFeatures) {
                } else if ("unavailable-feature".equals(name) && allowFeatures) {
                } else if ("allow-in-power-save-except-idle".equals(name) && allowAll) {
                } else if ("allow-in-power-save".equals(name) && allowAll) {
                } else if ("allow-in-data-usage-save".equals(name) && allowAll) {
                } else if ("allow-unthrottled-location".equals(name) && allowAll) {
                } else if ("allow-implicit-broadcast".equals(name) && allowAll) {
                } else if ("app-link".equals(name) && allowAppConfigs) {
                } else if ("system-user-whitelisted-app".equals(name) && allowAppConfigs) {
                } 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 if ("disabled-until-used-preinstalled-carrier-app".equals(name) && allowAppConfigs) {
                } else if ("privapp-permissions".equals(name) && allowPrivappPermissions) {
                } else if ("oem-permissions".equals(name) && allowOemPermissions) {
                } else if ("hidden-api-whitelisted-app".equals(name) && allowApiWhitelisting) {
                } else {
                }
            }
        }
        ......
    }

if else中的字符串就是xml的关键标签,解析时会分别保存到SystemConfig对应的final成员变量中,这部分就不贴源码了哈

从整个解析过程来看,这些xml文件分成了<permissions/><config/>两个大标签,然后又包含permissionlibrary等子标签。

platform.xml为例,我们来看下相关内容结构:

<permissions>
    <permission name="android.permission.BLUETOOTH_ADMIN" >
        <group gid="net_bt_admin" />
    </permission>
    <permission name="android.permission.BLUETOOTH" >
        <group gid="net_bt" />
    </permission>
    ......
    <assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="media" />
    <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="media" />
    <assign-permission name="android.permission.WAKE_LOCK" uid="media" />
    ......
    <library name="android.test.base"
            file="/system/framework/android.test.base.jar" />
    <library name="android.test.mock"
            file="/system/framework/android.test.mock.jar" />
    ......
<permissions/>

platform.xml上面主要由3块内容:

  • 标签<permission/>:将属性name中字符串表示的权限赋予<group/>标签中通过gid指定的用户组
  • 标签<assign-permission/>:将属性name中字符串表示的权限赋予通过属性uid指定的用户
  • 标签<library/>:指定系统自动加载的动态库

Android 的很多功能都是通过所谓的permission字符串来保护的,应用中如果需要使用某项受保护的功能,需要在它的AndroidManifest文件中显示声明该功能对应的permission字符串。但是,有些功能只能由特定的用户组使用,而在platform.xml文件中定义的就是这种规则

扫描应用目录的过程

Android 5.0引入了Split APK机制,主要是为了解决65536上限以及APK安装包越来越大等问题。

Split APK机制可以将一个APK拆分成多个独立APK
在引入了Split APK机制后,APK有两种分类:

  • Single APK:安装文件为一个完整的APK,即base APKAndroid称其为Monolithic
  • Mutiple APK:安装文件在一个文件目录中,其内部有多个被拆分的APK,这些APK由一个base APK和一个或多个split APK组成。Android称其为Cluster

scanDirLI

上面两种格式的扫描逻辑,是在PMS的构造方法中,通过scanDirLI来实现。

scanDirLI代码如下:

    private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime) {
        final File[] files = scanDir.listFiles();
        ......
        // 创建 ParallelPackageParser apk 解析帮助类
        // ParallelPackageParser核心其实是一个 ExecutorService
        // 当前目录下所有的 apk 解析任务通过ExecutorService.submit来创建
        try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser(...)) {
            // 遍历查找指定目录下的apk文件
            int fileCount = 0;
            for (File file : files) {
                // 判读是否为apk文件
                // 或者不是smdl2tmp打头的文件夹
                final boolean isPackage = (isApkFile(file) || file.isDirectory())
                        && !PackageInstallerService.isStageName(file.getName());
                if (!isPackage) {
                    continue;
                }
                // 创建提交解析任务,解析符合条件的文件
                parallelPackageParser.submit(file, parseFlags);
                // 记录任务数量
                fileCount++;
            }
            // 根据创建的任务数量,查看任务结果
            for (; fileCount > 0; fileCount--) {
                // 此方法是阻塞的,直到队列中有结果添加进来
                ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();
                // 对解析结果进行判断
                Throwable throwable = parseResult.throwable;
                int errorCode = PackageManager.INSTALL_SUCCEEDED;
                if (throwable == null) {
                    // 如果是静态共享库,进行rename操作
                    ......
                    // 如果应用正常,执行进一步的扫描
                    if (errorCode == PackageManager.INSTALL_SUCCEEDED) {
                        scanPackageChildLI(parseResult.pkg, parseFlags, scanFlags,
                                    currentTime, null);
                    }
                }
                ...// 省略异常情况处理
                // Delete invalid userdata apps
                if ((scanFlags & SCAN_AS_SYSTEM) == 0 &&
                        errorCode != PackageManager.INSTALL_SUCCEEDED) {
                    .......
                    removeCodePathLI(parseResult.scanFile);
                }
            }
        }
    }

根据上面的代码我们可以划分为两个阶段:

  • scanDirLIparsePackage
    • APK解析阶段
    • APK的解析是通过ParallelPackageParser类的submit()方法提交解析任务来实现的
  • scanDirLIscanPackageChildLI
    • 将解析好的APK添加到PMSmPackages

我们逐一来分析

scanDirLIparsePackage

ParallelPackageParser类是Android封装的一个线程池,核心实现是ExecutorService类。主要是为了方便收集并行解析的结果。

解析任务的创建都是通过此类的submit()方法来完成的。

ParallelPackageParsersubmit()方法

我们来简单看下:

    public void submit(File scanFile, int parseFlags) {
        mService.submit(() -> {
            ParseResult pr = new ParseResult();
            PackageParser pp = new PackageParser();
            pp.setSeparateProcesses(mSeparateProcesses);
            pp.setOnlyCoreApps(mOnlyCore);
            pp.setDisplayMetrics(mMetrics);
            pp.setCacheDir(mCacheDir);
            pp.setCallback(mPackageParserCallback);
            pr.scanFile = scanFile;
            pr.pkg = parsePackage(pp, scanFile, parseFlags);
            ......
            mQueue.put(pr);
            ......
        });
    }
    protected PackageParser.Package parsePackage(......) throws PackageParser.PackageParserException {
        return packageParser.parsePackage(scanFile, parseFlags, true /* useCaches */);
    }

函数中的submit()新建了一个解析线程,线程中创建了一个PackageParser对象,然后调用它的parsePackage方法来进行解析。

PackageParserparsePackage()方法

PackageParserparsePackage()方法如下:

    public Package parsePackage(File packageFile, int flags, boolean useCaches)
            throws PackageParserException {
        ......
        // 根据是否为directory执行不同的解析逻辑
        if (packageFile.isDirectory()) {
            // 解析一组apk
            parsed = parseClusterPackage(packageFile, flags);
        } else {
            // 解析单个apk
            parsed = parseMonolithicPackage(packageFile, flags);
        }
        ......
    }

到这里我们可以发现,真正的APK解析流程就在PackageParser中,大家可以跟踪下上面两种情况下的解析,以parseClusterPackage()为例,我们看下

parseClusterPackage()解析split应用

    private Package parseClusterPackage(File packageDir, int flags) throws PackageParserException {
        // 获取应用目录的 PackageLite 对象,这个对象中分开保存了目录下的
        // base应用和其他非核心应用
        final  PackageLite lite = parseClusterPackageLite(packageDir, 0);
        ......
        try {
            // 先装载base应用资源
            final AssetManager assets = assetLoader.getBaseAssetManager();
            final File baseApk = new File(lite.baseCodePath);
            final Package pkg = parseBaseApk(baseApk, assets, flags);
            ......
            // 再装载split应用资源
            if (!ArrayUtils.isEmpty(lite.splitNames)) {
                final int num = lite.splitNames.length;
                pkg.splitNames = lite.splitNames;
                pkg.splitCodePaths = lite.splitCodePaths;
                ......
                for (int i = 0; i < num; i++) {
                    final AssetManager splitAssets = assetLoader.getSplitAssetManager(i);
                    parseSplitApk(pkg, i, splitAssets, flags);
                }
            }
            pkg.setCodePath(packageDir.getCanonicalPath());
            pkg.setUse32bitAbi(lite.use32bitAbi);
            return pkg;
        }
        ......
    }
  • parseClusterPackage()方法中首先调用parseClusterPackageLite()方法对目录下的APK进行初步分析
    • 主要区分basesplit应用:base应用只有一个。split应用可以没有或者多个
    • split应用的作用是用来保存资源和代码
    • parseClusterPackageLite()方法会将分析结果通过PackageLite对象返回
  • parseClusterPackage()方法接下来:
    • base应用通过parseBaseApk()来进行分析
    • split应用通过parseSplitApk()来进行分析
    • 把分析结果存放到Package对象中

对于parseMonolithicPackage()方法省去了parseSplitApk的过程。但在解析时二者都会执行到parseBaseApk()方法,而parseBaseApk()最后会执行到parseBaseApkCommon()方法中。

parseBaseApkCommon()解析AndroidManifest.xml

parseBaseApkCommon()其实主要是对AndroidManifest.xml的解析,简要方法如下:

    private Package parseBaseApkCommon(Package pkg, Set<String> acceptedTags, Resources res,
            XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException,
            IOException {
        ......
        TypedArray sa = res.obtainAttributes(parser,
                com.android.internal.R.styleable.AndroidManifest);
        // 读取 AndroidManifest 的sharedUserId信息
        String str = sa.getNonConfigurationString(
                com.android.internal.R.styleable.AndroidManifest_sharedUserId, 0);
        ......
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
            ......
            String tagName = parser.getName();
            ......
            if (tagName.equals(TAG_APPLICATION)) {
                ......
                // 解析 <application/> 标签信息
                if (!parseBaseApplication(pkg, res, parser, flags, outError)) {
                    return null;
                }
            }
            // 省略各种 AndroidManifest 的标签解析
            ......
            else if (tagName.equals(TAG_PERMISSION)) {
                // 解析 permission 标签
                if (!parsePermission(pkg, res, parser, outError)) {
                    return null;
                }
            }
            ......
        }
        ......
        return pkg;
    }

这个方法对AndroidManifest.xml的标签进行解析,把解析结果存放到到Package对象中,并返回。

我们在while循环中省略了很多AndroidManifest.xml的标签,简单介绍下应用常见的一些标签:

  • <manifest/>AndroidManifest.xml的根元素。
    • 必须包含一个<application/>元素
    • 必须指定xlmns:android属性:androidXML命名空间
    • 必须指定package属性:应用的包名
    • 可选属性versionCode:指定应用版本号,整数类型
    • 可选属性versionName:显示给用户的版本号,字符串类型
  • <uses-permission/>:声明某种权限。应用必须声明可权限才可以使用对应的功能
  • <permission-group/>:定义权限组
  • <permission-tree/>:定义一颗权限树,可以通过PackageManager.addPermission插入Permission
  • <path-permission/>:为ContentProvider下的数据目录定义访问权限
  • <application/>:应用配置的根元素,包含所有与应用有关的属性配置
  • <activity/>Activity的声明标签
  • <activity-alias/>Activity的别名定义标签
  • <service/>Service组件的声明标签
  • <provider/>ContentProvider的声明标签
  • <receiver/>Broadcast Receiver的声明标签
  • <intent-filter/><intent-filter/>表示Intent过滤的声明
    • <intent-filter/>可以放在<activity/><activity-alias/><service/><receiver/>等标签下
    • <intent-filter/>必须包含有<action/>,用于描述Intent的名称
    • 可选标签<category/>,用于描述Intent的类别
    • 可选标签<data/>,用于描述Intent需要处理的数据格式
  • <meta-data/>:用于储存预定义的数据。
    • <meta-data/>可以放在<activity/><activity-alias/><application/><service/><receiver/>等标签下
    • <meta-data/>数据一般以键值对的形式出现,可以使用ActivityInfoServiceInfoApplicationInfo队形的metaData变量来访问它们
  • <instrumentation/>:用来声明Instrumentation测试类来监视应用的行为
  • <uses-sdk/>:指定应用所需使用的Android SDK的最高版本、最低版本、目标版本。
  • <uses-configration/>:声明应用需要的一些系统配置,一共有五种定义
    <uses-configuration
        android:reqFiveWayNav=["true" | "false"]
        android:reqHardKeyboard=["true" | "false"]
        android:reqKeyboardType=["undefined" | "nokeys" | "qwerty" | "twelvekey"]
        android:reqNavigation=["undefined" | "nonav" | "dpad" | "trackball" | "wheel"]
        android:reqTouchScreen=["undefined" | "notouch" | "stylus" | "finger"] />
    
  • <uses-feture/>:声明应用使用的软件或硬件feature
  • <uses-library/>:指定应用需要使用的用户库,系统将会在应用运行时装载这些库
  • <support-screens/>:指定应用支持的屏幕种类

到这里,Android已经把APK解析完成,并创建了一个完整的Package对象。那么接下来就是通过scanPackageChildLI方法将解析好的Package对象添加到系统中的mPackages中了

scanDirLIscanPackageChildLI

前面已经讲过,对于扫描成功的应用,会循环执行scanPackageChildLI()将其添加到系统中。

代码如下:

    private PackageParser.Package scanPackageChildLI(......){
        ......
        // 扫描当前应用
        PackageParser.Package scannedPkg = addForInitLI(pkg, parseFlags,
                scanFlags, currentTime, user);
        // 循环扫描子应用
        final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
        for (int i = 0; i < childCount; i++) {
            PackageParser.Package childPackage = pkg.childPackages.get(i);
            addForInitLI(childPackage, parseFlags, scanFlags,
                    currentTime, user);
        }
        ......// 省略一个递归入口
        return scannedPkg;
    }

scanPackageChildLI()方法最重要的操作就是通过addForInitLI()方法来对当前应用做进一步的解析,主要流程如下:

    private PackageParser.Package addForInitLI(PackageParser.Package pkg,
            @ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime,
            @Nullable UserHandle user)
                    throws PackageManagerException {
        ......
        // 设置应用资源、代码等相关的路径
        pkg.setApplicationVolumeUuid(pkg.volumeUuid);
        pkg.setApplicationInfoCodePath(pkg.codePath);
        pkg.setApplicationInfoBaseCodePath(pkg.baseCodePath);
        pkg.setApplicationInfoSplitCodePaths(pkg.splitCodePaths);
        pkg.setApplicationInfoResourcePath(pkg.codePath);
        pkg.setApplicationInfoBaseResourcePath(pkg.baseCodePath);
        pkg.setApplicationInfoSplitResourcePaths(pkg.splitCodePaths);
        // 判断系统应用是否需要升级
        synchronized (mPackages) {
            ......
            // 检查应用是否属于rename标签
            renamedPkgName = mSettings.getRenamedPackageLPr(pkg.mRealPackage);
            ......
            // 检查是否属于update标签
            disabledPkgSetting = mSettings.getDisabledSystemPkgLPr(disabledPkgName);
            ......
        }
        // 判断升级的重点是将当前package与mSettings中的进行比对
        // 我们知道mSettings中的package数据基本上都是从platform.xml文件中解析出来的
        // 这种对比相当于当前扫描到的应用和其历史信息的比对,这里主要是检查<renamed-package/>和<updated-package/>标签
        ......
        // 扫描应用文件的签名
        collectCertificatesLI(pkgSetting, pkg, forceCollect, skipVerify);
        ......
        // 处理应用包名有冲突的情况
        boolean shouldHideSystemApp = false;
        if (scanSystemPartition && !isSystemPkgUpdated && pkgAlreadyExists
                && !pkgSetting.isSystem()) {
            // 当前扫描到的应用是系统应用,但是也存在一个同名的普通应用
            // 先比较签名
            if (...) {
                ......
                // 签名不匹配,删除扫描到系统的应用
                deletePackageLIF(pkg.packageName, null, true, null, 0, null, false, null);
                pkgSetting = null;
            } else if (newPkgVersionGreater) {
                ......
                // 签名相同,当前系统应用版本高,移除普通应用
                InstallArgs args = createInstallArgsForExisting(...);
                synchronized (mInstallLock) {
                    args.cleanUpResourcesLI();
                }
            } else {
                // 签名相同,当前系统应用版本低,需要记录升级关系
                // 先把系统应用隐藏
                shouldHideSystemApp = true;
                ......
            }
        }
        // 调用 scanPackageNewLI 继续扫描
        final PackageParser.Package scannedPkg = scanPackageNewLI(pkg, parseFlags, scanFlags
                | SCAN_UPDATE_SIGNATURE, currentTime, user);

        if (shouldHideSystemApp) {
            synchronized (mPackages) {
                mSettings.disableSystemPackageLPw(pkg.packageName, true);
            }
        }
        return scannedPkg;
    }

又继续调用了scanPackageNewLI()来扫描,我们简单看看做了什么:

    private PackageParser.Package scanPackageNewLI(......) throws PackageManagerException {
            ......
        synchronized (mPackages) {
            ......
            SharedUserSetting sharedUserSetting = null;
            if (pkg.mSharedUserId != null) {
                // 如果当前应用的sharedUserId不为空的话
                // 将该sharedUserId添加到Settings的集合中
                sharedUserSetting = mSettings.getSharedUserLPw(
                        pkg.mSharedUserId, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/, true /*create*/);
                ......
            }
            ......
            try {
                ......
                // 继续调用 scanPackageOnlyLI 进行扫描操作
                // scanPackageOnlyLI 基本上就是组装PackageSetting对象的过程
                // ScanResult 中会包含扫描解析出的PackageSetting对象
                final ScanResult result = scanPackageOnlyLI(request, mFactoryTest, currentTime);
                if (result.success) {
                    // 扫描成功后,将应用相关数据组织到系统中
                    commitScanResultsLocked(request, result);
                }
            }
            ......
        }
        return pkg;
    }

代码中会先对应用的sharedUserId进行检查,不为空的话添加到SettingsmSharedUsers集合中。

接下来,调用scanPackageOnlyLI做进一步的扫描,扫描成功后会调用commitScanResultsLocked()package添加到PMSmPackages中。

scanPackageOnlyLIcommitScanResultsLocked()就不细讲了哈,对这两个方法留有印象就好啦。

我们下面重点了解下PMS启动后应用安装的过程。下一篇深入Android系统(十)PMS-3-应用安装过程

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 技术工厂 设计师:CSDN官方博客 返回首页