PackageManagerService启动详解(八)之扫描data分区应用安装目录阶段流程分析

本文详细解析了Android PackageManagerService (PKMS) 在BOOT_PROGRESS_PMS_DATA_SCAN_START阶段如何扫描data分区应用安装目录的过程,涵盖扫描逻辑、升级应用处理及异常情况应对,特别是针对非系统应用的扫描策略和结果处理。

PKMS启动详解(八)之BOOT_PROGRESS_PMS_DATA_SCAN_START阶段流程分析


Android PackageManagerService系列博客目录:

PKMS启动详解系列博客概要
PKMS启动详解(一)之整体流程分析
PKMS启动详解(二)之怎么通过packages.xml对已安装应用信息进行持久化管理?
PKMS启动详解(三)之BOOT_PROGRESS_PMS_START流程分析
PKMS启动详解(四)之Android包信息体和包解析器(上)
PKMS启动详解(五)之Android包信息体和包解析器(中)
PKMS启动详解(六)之Android包信息体和包解析器(下)
PKMS启动详解(七)之扫描系统应用安装目录阶段流程分析
PKMS启动详解(八)之BOOT_PROGRESS_PMS_DATA_SCAN_START阶段流程分析



本篇博客编写思路总结和关键点说明:

在这里插入图片描述

为了更加方便的读者阅读博客,通过导读思维图的形式将本博客的关键点列举出来,从而方便读者取舍和阅读!



引言

  欢迎回来继续PKMS之旅,在我们上一期的博客PKMS启动详解(七)之系统应用安装目录扫描阶段流程分析之中我们重点分析了系统应用安装目录扫描流程的相关相关核心步骤和事宜!其中伤害性不大,但是侮辱性极强的相关源码处理逻辑估计够读者喝了一壶的了!总之通过上篇的处理逻辑此时我们得到了如下的核心收获:

  • 通过扫描系统应用安装目录,得到目录下相关的应用对应的Packages信息

  • 将安装应用信息添加到PKMS中进行管理,同时将其对应的相关组件注册到PKMS中,以供后续第三方查询和使用

难道我们PKMS启动的千秋大业就此整个完结了吗,如果读者是这么认为那么就是太小瞧和不理解它了!虽然系统应用安装目录扫描结束了,但是还有data分区应用安装目录以及其它的相关处理逻辑依然还没有开始,那么在今天的博客中我们就将重点分析PKMS启动BOOT_PROGRESS_PMS_DATA_SCAN_START阶段的相关处理事宜。此时的读者是不是已经非常好奇PKMS是怎么扫描data分区应用安装目录的呢?不用着急,让我们一起来开启这段奇妙的探索之旅,相信经过我们的一起努力BOOT_PROGRESS_PMS_DATA_SCAN_START扫描data分区应用安装目录阶段会很轻松的拿下的。并且对于扫描data分区应用安装目录的阶段的整体流程,在本博客中我们将会分为两大部分来分析:

  • 扫描阶段
  • 扫描收尾阶段

    主要是处理date分区中扫描后:
    1.发现上次安装存在,但是此次扫描某些不存在的非系统应用
    2.处理覆盖升级的应用(譬如覆盖升级的应用不存在了,必须恢复位于系统分区的应用)
    等相关的逻辑处理流程!

好了不多说啥了,要得开干!

如果读者已经吃透了前面扫描系统应用安装目录的相关处理逻辑,那么这里对于处理data分区应用的扫描逻辑,那真的可以说的上是手到擒来毫不费工夫了。

因为处理的套路基本不变,处理的方法基本都一致,只是通过不同的参数走入了不同的分支而已。

并且此时读者是不是有一个好奇心,为啥我们的标题不叫扫描非系统应用安装目录呢,而是说data分区应用安装目录呢,这个先买个管子,留给读者自行体会!

注意:本篇的介绍是基于Android 7.xx平台为基础的(并且为了书写简便后续PackageManagerService统一简称为PKMS),其中涉及的代码路径如下:

--- frameworks/base/services/core/java/com/android/server/pm/
	PackageManagerService.java
	PackageSetting.java 
	Settings.java
	PackageInstallerService.java

--- frameworks/base/core/java/android/content/pm/PackageParser.java

--- frameworks/native/cmds/installd/install.cpp



一.开始data分区应用安装目录扫描

  这里我们可以看到PKMS对于data分区应用安装目录扫描的处理不似对于系统应用安装目录扫描的小心翼翼和前期准备(可能此时PKMS已经是情场老手,经验老道了),此时它是单刀直入直捣黄龙。我们来先来看下该阶段的整体流程概括:

// 【 PackageManagerService.java 】
            //设置扫描的参数
            final int scanFlags = SCAN_NO_PATHS | SCAN_DEFER_DEX | SCAN_BOOTING | SCAN_INITIAL;
            ...
			//开始处理非系统应用
            if (!mOnlyCore) {
   
   
                EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
                        SystemClock.uptimeMillis());
                // 扫描/data/app和/data/priv-app
                scanDirTracedLI(mAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0);

                scanDirTracedLI(mDrmAppPrivateInstallDir, mDefParseFlags
                        | PackageParser.PARSE_FORWARD_LOCK,
                        scanFlags | SCAN_REQUIRE_KNOWN, 0);

                scanDirLI(mEphemeralInstallDir, mDefParseFlags
                        | PackageParser.PARSE_IS_EPHEMERAL,
                        scanFlags | SCAN_REQUIRE_KNOWN, 0);

				...

                /**
                 * Remove disable package settings for any updated system
                 * apps that were removed via an OTA. If they're not a
                 * previously-updated app, remove them completely.
                 * Otherwise, just revoke their system-level permissions.
                 */                 
                /* 
                	进行最后的data分区扫描的收尾工作
                	放在possiblyDeletedUpdatedSystemApps中的应用是在packge.xml中被标记成了待升级的系统应用
                    但是文件却不存在了,因此这里检查用户目录下升级文件是否还存在,然后进行处理
                    处理那些不存在的系统app!
                */
                for (String deletedAppName : possiblyDeletedUpdatedSystemApps) {
   
   
                    PackageParser.Package deletedPkg = mPackages.get(deletedAppName);
					// 从mSettings.mDisabledSysPackages变量中移除去此应用
                    mSettings.removeDisabledSystemPackageLPw(deletedAppName);

                    String msg;
                    if (deletedPkg == null) {
   
   
						// 用户目录中也没有升级包,则肯定是残留的应用信息,则把它的数据目录删除掉
						// 此时无任何扫描结果,表明这个系统中已经没有改apk了,那就删掉它
                        msg = "Updated system package " + deletedAppName
                                + " no longer exists; it's data will be wiped";
                        // Actual deletion of code and data will be handled by later
                        // reconciliation step
                    } else {
   
   
                        msg = "Updated system app + " + deletedAppName
                                + " no longer present; removing system privileges for "
                                + deletedAppName;

                        deletedPkg.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM;

                        PackageSetting deletedPs = mSettings.mPackages.get(deletedAppName);
                        deletedPs.pkgFlags &= ~ApplicationInfo.FLAG_SYSTEM;
                    }
					//报告系统发生了不一致的情况
                    logCriticalInfo(Log.WARN, msg);
                }

                /**
                 * Make sure all system apps that we expected to appear on
                 * the userdata partition actually showed up. If they never
                 * appeared, crawl back and revive the system version.
                 */
                 
                 /**
					确保所有在用户data分区的应用都显示出来了,
					如果data分区的无法显示,就显示system分区的
                 
                 	现在来处理mExpectingBetter列表,这个列表的应用是带有升级包的系统的应用,
                 	前面把他们从mPackages列表中清除了并放到mExpectingBetter列表
                 	最后也对它们进行扫描处理
                 */
                for (int i = 0; i < mExpectingBetter.size(); i++) {
   
   
                    final String packageName = mExpectingBetter.keyAt(i);
					/* 
						如果PMS仍然没有扫描到mExpectingBetter列表中的apk,说明data分区的apk无法显示
						出现这种情况的原因,可能是由于OTA或者异常导致data分区的覆盖安装的应用已经丢失了
        			 	那就要显示原来system分区的apk!
        			 */
                    if (!mPackages.containsKey(packageName)) {
   
   
                        final File scanFile = mExpectingBetter.valueAt(i);

                        logCriticalInfo(Log.WARN, "Expected better " + packageName
                                + " but never showed up; reverting to system");

                        int reparseFlags = mDefParseFlags;
						//确保应用位于下面几个系统应用目录,如果不在,则不需要处理
                        if (FileUtils.contains(privilegedAppDir, scanFile)) {
   
   
                            reparseFlags = PackageParser.PARSE_IS_SYSTEM
                                    | PackageParser.PARSE_IS_SYSTEM_DIR
                                    | PackageParser.PARSE_IS_PRIVILEGED;
                        } else if (FileUtils.contains(systemAppDir, scanFile)) {
   
   
                            reparseFlags = PackageParser.PARSE_IS_SYSTEM
                                    | PackageParser.PARSE_IS_SYSTEM_DIR;
                        } else if (FileUtils.contains(vendorAppDir, scanFile)) {
   
   
                            reparseFlags = PackageParser.PARSE_IS_SYSTEM
                                    | PackageParser.PARSE_IS_SYSTEM_DIR;
                        } else if (FileUtils.contains(oemAppDir, scanFile)) {
   
   
                            reparseFlags = PackageParser.PARSE_IS_SYSTEM
                                    | PackageParser.PARSE_IS_SYSTEM_DIR;
                        } else {
   
   
                            Slog.e(TAG, "Ignoring unexpected fallback path " + scanFile);
                            continue;
                        }
                        //现在把这个apk标示为系统应用,从mSettings.mDisabledSysPackages中删除,
                        //因为在scanDirLI->scanPackageLI中会执行mSettings.disableSystemPackageLPw
                        //所以此时包名的标签是只有<update-package>,执行到这步之后变成<package>标签,
                        //在下面的scanPackageLI中又会添加一个<update-package>标签的
                        mSettings.enableSystemPackageLPw(packageName);

                        try {
   
   
							 // 重新扫描一下这个文件,会添加一个<update-package>标签
                            scanPackageTracedLI(scanFile, reparseFlags, scanFlags, 0, null);
                        } catch (PackageManagerException e) {
   
   
                            Slog.e(TAG, "Failed to parse original system package: "
                                    + e.getMessage());
                        }
                    }
                }
            }
			//清空目录
            mExpectingBetter.clear();

            // Resolve the storage manager.
            // 获得存储管理对象!
            mStorageManagerPackage = getStorageManagerPackageName();

            // Resolve protected action filters. Only the setup wizard is allowed to
            // have a high priority filter for these actions.
            // 获得开机向导应用
            mSetupWizardPackage = getSetupWizardPackageName();
            if (mProtectedFilters.size() > 0) {
   
   
                if (DEBUG_FILTERS && mSetupWizardPackage == null) {
   
   
                    Slog.i(TAG, "No setup wizard;"
                        + " All protected intents capped to priority 0");
                }
                for (ActivityIntentInfo filter : mProtectedFilters) {
   
   
                    if (filter.activity.info.packageName.equals(mSetupWizardPackage)) {
   
   
                        if (DEBUG_FILTERS) {
   
   
                            Slog.i(TAG, "Found setup wizard;"
                                + " allow priority " + filter.getPriority() + ";"
                                + " package: " + filter.activity.info.packageName
                                + " activity: " + filter.activity.className
                                + " priority: " + filter.getPriority());
                        }
                        // skip setup wizard; allow it to keep the high priority filter
                        continue;
                    }
                    Slog.w(TAG, "Protected action; cap priority to 0;"
                            + " package: " + filter.activity.info.packageName
                            + " activity: " + filter.activity.className
                            + " origPrio: " + filter.getPriority());
                    filter.setPriority(0);
                }
            }
            mDeferProtectedFilters = false;
            mProtectedFilters.clear();

            // Now that we know all of the shared libraries, update all clients to have
            // the correct library paths.
            // 更新所有应用的动态库路径,保证他们有正确的共享库路径
            updateAllSharedLibrariesLPw();

			// 调整所有共享 uid 的 package 的指令集!
            for (SharedUserSetting setting : mSettings.getAllSharedUsersLPw()) {
   
   
                // NOTE: We ignore potential failures here during a system scan (like
                // the rest of the commands above) because there's precious little we
                // can do about it. A settings error is reported, though.
                adjustCpuAbisForSharedUserLPw(setting.packages, null /* scanned package */,
                        false /* boot complete */);
            }

            // Now that we know all the packages we are keeping,
            // read and update their last usage times.
            // 更新所有 package 的最新使用时间!
            mPackageUsage.read(mPackages);
            mCompilerStats.read();

在正式开始data分区相关应用安装目录扫描具体分析前,我们先来对于data分区可能存在的两种类型的apk来简单概括一下:

  • 一种就是最最普通的非系统apk
  • 另外一种是系统apk通过覆盖安装升级的方式,被安装到了data分区的特许的系统应用apk

好了有了对于上面两种的apk的了解,那么我们开始分析PKMS是怎么处理非系统应用的扫描的。并且这里需要特别注意的几点就是通过前面扫描系统应用安装目录的分区,我们得到了如下几个非常重要的集合:

  • mExpectingBetter:用来存放那些在data分区可能有更高版本的系统app!

    // 存放在系统应用安装目录可能存在覆盖升级包的系统应用相关信息,其中key是安装包包名,value是具体路径
    final private ArrayMap<String, File> mExpectingBetter = new ArrayMap<>();
    
  • possiblyDeletedUpdatedSystemApps:用来存储那些不存在的被更新过的系统app!

            // 删除任何不再存在的系统包,这类List表示的是有可能有升级包的系统应用
            final List<String> possiblyDeletedUpdatedSystemApps = new ArrayList<String>();
    

好了,我们接着往下开撸!在正式开撸前,我们先来看下扫描阶段的整体时序图:

在这里插入图片描述


1.1 扫描data分区非系统应用安装目录

好了,下面我们直接来看看PKMS对于data分区的扫描过程,如下:

// 【 PackageManagerService.java 】
	final int scanFlags = SCAN_NO_PATHS | SCAN_DEFER_DEX | SCAN_BOOTING | SCAN_INITIAL;

    // 同理,扫描 /data/app 目录和 /data/app-private 目录!
    scanDirTracedLI(mAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0);

    scanDirTracedLI(mDrmAppPrivateInstallDir, mDefParseFlags
            | PackageParser.PARSE_FORWARD_LOCK,
            scanFlags | SCAN_REQUIRE_KNOWN, 0);

    scanDirLI(mEphemeralInstallDir, mDefParseFlags
            | PackageParser.PARSE_IS_EPHEMERAL,
            scanFlags | SCAN_REQUIRE_KNOWN, 0);

通过上述的源码我们可以看到在该阶段最最最重要的核心诉求点只有一个按照特定循序调用scanDirTracedLI方法扫描应用安装目录,被扫描的目录循序如下:

  • /data/app;

    • parseFlags:0
    • scanFlags:scanFlags | SCAN_REQUIRE_KNOWN
  • /data/app-private;
    - parseFlags:PackageParser.PARSE_FORWARD_LOCK

    • scanFlags:scanFlags | SCAN_REQUIRE_KNOWN
  • /data/app-ephemeral;

    • parseFlags:PackageParser.PARSE_IS_EPHEMERAL
    • scanFlags:scanFlags | SCAN_REQUIRE_KNOWN

这里的步骤和扫描system分区是一样的,但是因为只是parseFlags和scanFlags的取值有所不同,所以会涉及不同的逻辑,所以一定要有对比思想,看看它和系统应用安装目录扫描的区别和联系。这里我们以扫描/data/app目录为例来展开相关的说明!


1.2 PKMS.scanDirTracedLI(…)

从此方法开始开始真正开启扫描流程,从此天涯是路人!我们先来看下此方法:

// 【 PackageManagerService.java 】
    private void scanDirTracedLI(File dir, final int parseFlags, int scanFlags, long currentTime) {
   
   
        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir");
        try {
   
   
            scanDirLI(dir, parseFlags, scanFlags, currentTime);// 详见章节 【 1.3 】
        } finally {
   
   
            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
        }
    }

可以看到此方法平淡无奇,只是接着调用另一个方法scanDirLI继续处理,但是我这里有必要对其中它的两个入参parseFlags和scanFlags捯饬捯饬一下:

  • parseFlags:此参数的取值表明的是对于被扫描目录的处理逻辑的flag值,影响的是扫描目录源码的分支走向

  • scanFlags:此参数的取值表明的是对于扫描目录中的应用安装包解析处理逻辑的flag值,影响的是对具体的安装包的解析逻辑,即最终PackageParser.parsePackagesparsePackages的处理逻辑

好了我们接着往下看!


1.3 PKMS.scanDirLI(…)

没有啥前提要交代的,继续接着干!

// 【 PackageManagerService.java 】
    private void scanDirLI(File dir, final int parseFlags, int scanFlags, long currentTime) {
   
   
        final File[] files = dir.listFiles();
        if (ArrayUtils.isEmpty(files)) {
   
   
            Log.d(TAG, "No files in app dir " + dir);
            return;
        }

        if (DEBUG_PACKAGE_SCANNING) {
   
   
            Log.d(TAG, "Scanning app dir " + dir + " scanFlags=" + scanFlags
                    + " flags=0x" + Integer.toHexString(parseFlags));
        }

        Log.d(TAG, "start scanDirLI:"+dir);
        // use multi thread to speed up scanning
        // 通过线程加速扫描
        int iMultitaskNum = SystemProperties.getInt("persist.pm.multitask", 6);
        Log.d(TAG, "max thread:" + iMultitaskNum);
        final MultiTaskDealer dealer = (iMultitaskNum > 1) ? MultiTaskDealer.startDealer(
                MultiTaskDealer.PACKAGEMANAGER_SCANER, iMultitaskNum) : null;

        for (File file : files) {
   
   
			// 判断file是否是Package,必须要同时满足下面2个条件
			// 1、file 以 ".apk" 结尾或者file是一个目录;
        	// 2、file不是安装阶段临时的apk类型的文件;
            final boolean isPackage = (isApkFile(file) || file.isDirectory())
                    && !PackageInstallerService.isStageName(file.getName());
            /*********** 这里为了方便演示直接展开了 ****************************/
				    public static boolean isStageName(String name) {
   
   
				        final boolean isFile = name.startsWith("vmdl") && name.endsWith(".tmp");
				        final boolean isContainer = name.startsWith("smdl") && name.endsWith(".tmp");
				        final boolean isLegacyContainer = name.startsWith("smdl2tmp");
				        return isFile || isContainer || isLegacyContainer;
				    }     
			/*********** 这里为了方便演示直接展开了 ****************************/      
			
            if (!isPackage) {
   
   
                continue;
            }
           if (RegionalizationEnvironment.isSupported()) {
   
   
             if (RegionalizationEnvironment.isExcludedApp(file.getName())) {
   
   
               Slog.d(TAG, "Regionalization Excluded:" + file.getName());
               continue;
            }
           }

            final File ref_file = file;
            final int ref_parseFlags = parseFlags;
            final int ref_scanFlags = scanFlags;
            final long ref_currentTime = currentTime;
            Runnable scanTask = new Runnable() {
   
   
                public void run() {
   
   
                    try {
   
   
						// 注意此时增加了一个解析参数就是PARSE_MUST_BE_APK
                        scanPackageTracedLI(ref_file, ref_parseFlags | PackageParser.PARSE_MUST_BE_APK,
                                ref_scanFlags, ref_currentTime, null);// 详见章节 【 1.4 】
                    } catch (PackageManagerException e) {
   
   
                        Slog.w(TAG, "Failed to parse " + ref_file + ": " + e.getMessage());

                        // 如果扫描data分区的Apk失败,则删除data分区扫描失败的文件
                        if ((ref_parseFlags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
                                e.error == PackageManager.INSTALL_FAILED_INVALID_APK) {
   
   
                            logCriticalInfo(Log.WARN, "Deleting invalid package at " + ref_file);
                            removeCodePathLI(ref_file);
                        }
                    }
                }
            };

            if (dealer != null)
                dealer.addTask(scanTask);
            else
                scanTask.run();// 开始扫描
        }

        if (dealer != null)
            dealer.waitAll();
        Log.d(TAG, "end scanDirLI:"+dir);
    }

此处源码的逻辑清晰明了,主要分为两大步骤:

  • 首先判断被扫描目录下各个安装包路径的合法性,它必须满足如下的两个条件:

    • 首先各个安装包路径必须是apk文件或者是一个文件夹

    • 然后各个安装包文件名称不能是应用安装过程的临时存储类型的文件

      文件名称不可以是:
      1.不能以vmdl开头且不以 .tmp结尾;
      2.不能以smdl开头且不以 .tmp结尾;
      3.不能以smdl2tmp开头;

      读者是不是很好奇,什么时候会产生上述类型的安装包文件呢,在应用的安装过程中在/data目录下会产生上述类型的临时文件/data/app/vmdl1611774040.tmp/base.apk。关于这块在后续的博客应用的安装流程中会涉及到。

  • 然后通过多线程的方式继续调用scanPackageTracedLI进行下一步的扫描流程

    关于此处有两点需要注意:

    1、在Android AOSP的源码中,scanPackageTracedLI方法并不是在多线程中进行的,这是方案厂为了优化开机速度慢而进行优化的测量,我想这个手段很多做ROM开发的都有使用过(因为PKMS的扫描时很耗时间的)

    2.注意这里的try catch语句,后面方法抛出的异常都是在这个进 catch的,并且如果是扫描的/data分区的安装应用目录,在catch代码执行逻辑中会,若扫描失败则删除data分区扫描失败的文件。

好了我们接着往下看!


1.4 PKMS.scanPackageTracedLI(File…)

此处有一点需要注意的是scanPackageTracedLI方法存在重载,所以要认清具体调用的是哪个,继续接着干!

// 【 PackageManagerService.java 】
    private PackageParser.Package scanPackageTracedLI(File scanFile, final int parseFlags,
            int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
   
   
        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanPackage");
        try {
   
   			
            return scanPackageLI(scanFile, parseFlags, scanFlags, currentTime, user);// 详见章节 【 1.5 】
        } finally {
   
   
            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
        }
    }

此代码比较懒,啥也没有干,直接当了个传递手调用另外一个方法scanPackageLI继续执行扫描逻辑。


1.5 PKMS.scanPackageLI(File …)

还记得为啥这一篇和前面分析PKMS中间差了三个篇章吗,这里就是原因了,因为这其中涉及到了解析Android安装包的流程,并其解析流程parsePackage是一个比较繁琐的,所以特意花了三个篇章来分析。

并且由于前面已经重点分析过了解析安装包的流程,这里就不需要大费周章的重复了,具体的就可以参见PKMS启动详解(四)之Android包信息体和包解析器了。

此处有一点需要注意的是scanPackageLI方法存在重载,所以要认清具体调用的是哪个,继续接着干,一直撸一直爽!

// 【 PackageManagerService.java 】
	/*
		此时parseFlags的取值为: 0 | PackageParser.PARSE_MUST_BE_APK
		此时scanFlags的取值是SCAN_NO_PATHS | SCAN_DEFER_DEX | SCAN_BOOTING | SCAN_INITIAL | SCAN_REQUIRE_KNOWN
	*/
    private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,
            long currentTime, UserHandle user) throws PackageManagerException {
   
   
        if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanFile);

		// 构建一个PackagePares包解析器对象实例,用于解析包
		// 并且填充前面PKMS构造方法中的相关成员信息
        PackageParser pp = new PackageParser(mContext);
        pp.setSeparateProcesses(mSeparateProcesses);
        pp.setOnlyCoreApps(mOnlyCore);
        pp.setOnlyPowerOffAlarmApps(mOnlyPowerOffAlarm);
        pp.setDisplayMetrics(mMetrics);

		//判断扫描模式
        if ((scanFlags & SCAN_TRUSTED_OVERLAY) != 0) {
   
   
            parseFlags |= PackageParser.PARSE_TRUSTED_OVERLAY;
        }

        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
		// 构建PackageParser.Package实例对象pkg用来保存解析结果
        final PackageParser.Package pkg;
        try {
   
   
			/*
				这里解析具体某个App中的AndroidManifestxml文件
				解析出来的数据放在PackageParser的内部类Package的实例
				pkg中,这里的parseFlags也是重点,系统(system)和
				非系统文件夹(/data/app)下扫描的tag是不一样的
			*/
            pkg = pp.parsePackage(scanFile, parseFlags);
        } catch (PackageParserException e) {
   
   
            throw PackageManagerException.from(e);
        } finally {
   
   
            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
        }
		// 然后需要将pkg中的信息传递给PKMS服务,继续进行相关的扫描处理
        return scanPackageLI(pkg, scanFile, parseFlags, scanFlags, currentTime, user);// 详见章节 【 1.6 】
    }

此处scanPackageLI方法的处理逻辑层次分明,主要分为三大部分:

  • 构建PackageParser安装包解析实例对象,并且使用PKMS中相关的成员信息填充它

    关于PackageParser我想就不用过大的介绍了,它是一个用于解析Android安装包的工具类,通过它可以得到完整的一个安装包的所有信息。

  • 接着调用PackageParser解析器的parsePackage方法正式开始解析安装包,得到安装包信息对象Package实例对象

    关于此处具体的就可以参见PKMS启动详解(四)之Android包信息体和包解析器,里面有特别详尽的对于Android包解析器和安装包信息体从设计到实施的全方位解读!

  • 将前面解析安装包得到的Package安装包信息对象继续交由重载的scanPackageLI进行下一步处理,然后返回得到继续处理的Pakcage实例对象

    虽然通过parsePackage方法解析得到了安装包的信息,但是这个不是最终的还必须进过进一步的加工和验证才可以!

我们接着继续往下一阶段进军!


1.6 PKMS.scanPackageLI(PackageParser.Package …,6)

该方法的参数入参是六个,不要和后面2.7章节的搞混淆了!注意,注意!

此处无声胜有声,接着往下看!

// 【 PackageManagerService.java 】
	/*
		此时policyFlags的取值为:0 | PackageParser.PARSE_MUST_BE_APK
        它的值来源于前面的parseFlags
		此时scanFlags的取值是SCAN_NO_PATHS | SCAN_DEFER_DEX | SCAN_BOOTING | SCAN_INITIAL
	*/
    private PackageParser.Package scanPackageLI(PackageParser.Package pkg, File scanFile,
            final int policyFlags, int scanFlags, long currentTime, UserHandle user)
            throws PackageManagerException {
   
   
        // SCAN_CHECK_ONLY标签是为了检测是否所有的包(parent 和child)都可以被成功的扫描到
        if ((scanFlags & SCAN_CHECK_ONLY) == 0) {
   
   
            if (pkg.childPackages != null && pkg.childPackages.size() > 0) {
   
   
                scanFlags |= SCAN_CHECK_ONLY;
            }
        } else {
   
   
            scanFlags &= ~SCAN_CHECK_ONLY;
        }

        // 继续处理当前解析得到的package信息,然后得到最终扫描处理的scannedPkg信息为扫描的包信息!
        PackageParser.Package scannedPkg = scanPackageInternalLI(pkg, scanFile, policyFlags,
                scanFlags, currentTime, user);// 详见章节 【 2.6 】

        // 扫描当前package的子pacakge(如果有),我们不管子包存在的情况
        final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
        for (int i = 0; i < childCount; i++) {
   
   
            PackageParser.Package childPackage = pkg.childPackages.get(i);
            scanPackageInternalLI(childPackage, scanFile, policyFlags, scanFlags,
                    currentTime, user);
        }

		// 如果设置SCAN_CHECK_ONLY标志位, 就调用自身,再次处理!,当前扫描条件不会进入此分支
        if ((scanFlags & SCAN_CHECK_ONLY) != 0) {
   
   
            return scanPackageLI(pkg, scanFile, policyFlags, scanFlags, currentTime, user);
        }

        return scannedPkg;
    }

关于此处对于被扫描的package有child package我们不予考虑,最终该方法会继续调用scanPackageInternalLI执行余下的扫描处理逻辑。

如果被扫描的package有child package,并且是第一次进入该方法的话,就需要检测是否所有的包(parent和child)是否都可以被成功的扫描到。

对于有child package的情况的scanFlags本来是没有SCAN_CHECK_ONLY位的,所以这里会将其SCAN_CHECK_ONLY置为1,这样在方法的最后,又会调用自身,这次又会将 SCAN_CHECK_ONLY 位置为 0!


1.7 PKMS.scanPackageInternalLI(…)

如果说前面的逻辑读者理解起来毫不费力,那么从此处开始我想读者将不得不打气十二分精神了!

因为此处源码的逻辑牵涉到很多关于应用安装包处理的情况,如果是刚开始肯定是一遍过不了的,没有关系多整几遍,捋顺了关系就可以了。当然如果读者能一遍给整清楚那是最好不过的了(反正当初我学习的时候,是没有做到一次就OVER了)。

我们继续来看,通过前面的处理逻辑,我们解析获得了应用程序的PackageParser.Package对象,同时,我们也已经获得了上一次的应用的安装

评论 13
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值