PKMS(2)扫描APK

915 阅读9分钟

前面一篇文章对PKMS扫描前的流程做了一个分析,可谓小试牛刀。本文开始分析APK扫描工作,让我们翻起点浪花。

承接前方的PKMS构造函数代码如下

    public PackageManagerService(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
        // ...

        // Create sub-components that provide services / data. Order here is important.
        synchronized (mInstallLock) {
        synchronized (mPackages) {
            // ...
            
            // 解析系统配置文件
            mPermissionManager = PermissionManagerService.create(context,
                    mPackages /*externalLock*/);
            mDefaultPermissionPolicy = mPermissionManager.getDefaultPermissionGrantPolicy();
            // Holds information about dynamic settings.
            mSettings = new Settings(Environment.getDataDirectory(),
                    mPermissionManager.getPermissionSettings(), mPackages);
        }
        }
        
        // 保存共享用户信息
        mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
                ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
        // ...
		
        // 这个属性默认为 false
        String separateProcesses = SystemProperties.get("debug.separate_processes");
        if (separateProcesses != null && separateProcesses.length() > 0) {
            // ...
        } else {
            // 注意这两个变量,在解析APK时,有用到。
            mDefParseFlags = 0;
            mSeparateProcesses = null;
        }

        //  ...
        
        synchronized (mInstallLock) {
        // writer
        synchronized (mPackages) {
            // ...

            // ... 省略转移library信息的代码
            

            // /system/framework
            File frameworkDir = new File(Environment.getRootDirectory(), "framework");

            // ... 省略关于系统升级的代码

            // 创建APK解析的缓存目录
            mCacheDir = preparePackageParserCache();

            // Set flag to monitor and not change apk file paths when
            // scanning install directories.
            int scanFlags = SCAN_BOOTING | SCAN_INITIAL;

            if (mIsUpgrade || mFirstBoot) {
            	// 我们分析的是首次开机的过程,因此这里会设置如下flag
                scanFlags = scanFlags | SCAN_FIRST_BOOT_OR_UPGRADE;
            }

            // 1. 首先扫描overlay的APK
            scanDirTracedLI(new File(VENDOR_OVERLAY_DIR),
                    mDefParseFlags
                    | PackageParser.PARSE_IS_SYSTEM_DIR,
                    scanFlags
                    | SCAN_AS_SYSTEM
                    | SCAN_AS_VENDOR,
                    0);
            // 省略扫描其它分区的overlay apk代码
			
            // 保存静态overlay apk的信息
            mParallelPackageParserCallback.findStaticOverlayPackages();

            // 2. 扫描/system/frameworks目录,这个目录下只有frameworks-res.apk
            scanDirTracedLI(frameworkDir,
                    mDefParseFlags
                    | PackageParser.PARSE_IS_SYSTEM_DIR,
                    scanFlags
                    | SCAN_NO_DEX
                    | SCAN_AS_SYSTEM
                    | SCAN_AS_PRIVILEGED,
                    0);

            // 3. 省略扫描其它分区下的APK的代码
            

从整体看,它首先是扫描overlay apk,因为overlay apk会对某个目标apk进行资源覆盖,因此必须要先扫描。

然后再扫描 /system/framework 目录下的apk,而这个目录下只有一个 frameworks-res.apk。

最后扫描其它分区下的apk,例如 /system/priv-app、/system/app,/vendor/app 目录,等等。

对所有APK的扫描都是使用 scanDirTracedLI(),而它会直接调用scanDirLI(),代码如下

    private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime) {
        final File[] files = scanDir.listFiles();
        if (ArrayUtils.isEmpty(files)) {
            return;
        }

        try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser(
                mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir,
                mParallelPackageParserCallback)) {
            // 1. 提交请求到线程池
            int fileCount = 0;
            for (File file : files) {
                final boolean isPackage = (isApkFile(file) || file.isDirectory())
                        && !PackageInstallerService.isStageName(file.getName());
                if (!isPackage) {
                    continue;
                }
                parallelPackageParser.submit(file, parseFlags);
                fileCount++;
            }

            // 2. 处理结果
            for (; fileCount > 0; fileCount--) {
                ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();
                Throwable throwable = parseResult.throwable;
                int errorCode = PackageManager.INSTALL_SUCCEEDED;

                if (throwable == null) {
                    if (parseResult.pkg.applicationInfo.isStaticSharedLibrary()) {
                        renameStaticSharedLibraryPackage(parseResult.pkg);
                    }
                    try {
                        scanPackageChildLI(parseResult.pkg, parseFlags, scanFlags,
                                currentTime, null);
                    }
                } 
                // ...
            }
        }
    }

多么清晰的两步,首先向线程池提交解析APK的任务,然后在主线程中,阻塞式的获取结果并处理它。

这段代码演示了如何利用多线程处理任务,然后利用主线程获取结果。分析源码其实也是一个学习技术的手段!!!

看似小小的两步,其实代码量非常大,本文只分析第一步。而第二步,留到后面一篇文章再来分析。

源码分析不能一蹴而就,需要我们个个击破,然后再整合信息。

现在让我们来看看ParallelPackageParser.submit()如何提交解析APK任务

    public void submit(File scanFile, int parseFlags) {
        mService.submit(() -> {
            // 用于保存解析的结果
            ParseResult pr = new ParseResult();
            try {
                PackageParser pp = new PackageParser();
                pp.setSeparateProcesses(mSeparateProcesses);
                pp.setOnlyCoreApps(mOnlyCore);
                pp.setDisplayMetrics(mMetrics);
                pp.setCacheDir(mCacheDir);
                pp.setCallback(mPackageParserCallback);
                pr.scanFile = scanFile;
                // 用PackageParser解析APK
                pr.pkg = parsePackage(pp, scanFile, parseFlags);
            }
            // ...
            try {
                // 解析的结果保存到BlockingQueue中
                mQueue.put(pr);
            } catch (InterruptedException e) {
                // ...
            }
        });
    }

提交到线程池中的任务,首先通过PackageParser解析APK,然后把结果保存到一个BlockingQueue中。

现在让我们开始激动人心的APK解析之旅,PackageParser.parsePackage()代码如下

    public Package parsePackage(File packageFile, int flags, boolean useCaches)
            throws PackageParserException {
        // 这这里可以看出,APK解析结果可以从缓存中获取
        Package parsed = useCaches ? getCachedResult(packageFile, flags) : null;
        if (parsed != null) {
            return parsed;
        }

        if (packageFile.isDirectory()) {
            parsed = parseClusterPackage(packageFile, flags);
        } else {
            // 解析APK
            parsed = parseMonolithicPackage(packageFile, flags);
        }
        
        // 缓存APK解析结果
        cacheResult(packageFile, flags, parsed);
        return parsed;
    }

这个方法主要是负责缓存功能,其它的解析工作交给了 parseMonolithicPackage() 函数,代码如下

    @Deprecated
    public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
        // 简单解析AndroidManifest.xml一些标签属性
        final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);
        
        if (mOnlyCoreApps) {
            // ...
        }

        final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);
        try {
            // 注意第二个参数是AssetsManager,它表示APK的assets目录的资源管理器
            final Package pkg = parseBaseApk(apkFile, assetLoader.getBaseAssetManager(), flags);
            pkg.setCodePath(apkFile.getCanonicalPath());
            // 可以
            pkg.setUse32bitAbi(lite.use32bitAbi);
            return pkg;
        } // ...
    }

这个函数被标记为@Deprecated,可能是由于历史原因造成的。这个函数主要是为了加载本APK和Split APK的asset资源,但是呢parseMonolithicPackageLite()函数并没有解析Split apk的路径,因此使用 DefaultSplitAssetLoader.getBaseAssetManager() 时,并没有加载Split apk的asset资源,这可能就是这个函数标记为废弃的原因吧。

除了加载asset资源,APK的剩余解析工作交给了parseBaseApk()函数,代码如下

    private Package parseBaseApk(File apkFile, AssetManager assets, int flags)
            throws PackageParserException {
        final String apkPath = apkFile.getAbsolutePath();
		
        // 如果APK的路径是以/mnt/expand/开头,那么解析磁盘的uuid
        String volumeUuid = null;
        if (apkPath.startsWith(MNT_EXPAND)) {
            final int end = apkPath.indexOf('/', MNT_EXPAND.length());
            volumeUuid = apkPath.substring(MNT_EXPAND.length(), end);
        }

        mParseError = PackageManager.INSTALL_SUCCEEDED;
        mArchiveSourcePath = apkFile.getAbsolutePath();

        XmlResourceParser parser = null;
        try {
            final int cookie = assets.findCookieForPath(apkPath);
            if (cookie == 0) {
                throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
                        "Failed adding asset path: " + apkPath);
            }
            parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
            // Resources代表APK的资源
            final Resources res = new Resources(assets, mMetrics, null);

            final String[] outError = new String[1];
            // 继续解析APK
            final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError);
            if (pkg == null) {
                throw new PackageParserException(mParseError,
                        apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]);
            }
			
            // 设置uuid
            pkg.setVolumeUuid(volumeUuid);
            pkg.setApplicationVolumeUuid(volumeUuid);
            pkg.setBaseCodePath(apkPath);
            pkg.setSigningDetails(SigningDetails.UNKNOWN);

            return pkg;

        } 
        // ...
    }

从整体看,这段代码是处理APK在/mnt/expand/目录下的情况,然后剩余的工作交给了parseBaseApk(),代码如下

从解析APK的函数调用链可以观察到,每个函数似乎只做一件事。这样每个函数职责清晰,也可以避免某一个参数代码过长。

    private Package parseBaseApk(String apkPath, Resources res, XmlResourceParser parser, int flags,
            String[] outError) throws XmlPullParserException, IOException {
        final String splitName;
        final String pkgName;

        try {
            // 1.解析了<manifest>标签的split属性
            Pair<String, String> packageSplit = parsePackageSplitNames(parser, parser);
            pkgName = packageSplit.first;
            splitName = packageSplit.second;
            // 从这里的判断可知,split的属性必须为空,真是奇怪,难道是不支持?
            if (!TextUtils.isEmpty(splitName)) {
                outError[0] = "Expected base APK, but found split " + splitName;
                mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
                return null;
            }
        } // ...

        // 2.加载overlay apk的aasets目录资源
        if (mCallback != null) {
            String[] overlayPaths = mCallback.getOverlayPaths(pkgName, apkPath);
            if (overlayPaths != null && overlayPaths.length > 0) {
                for (String overlayPath : overlayPaths) {
                    res.getAssets().addOverlayPath(overlayPath);
                }
            }
        }
		
        // PackageParser.Package是用于保存APK解析的结果
        final Package pkg = new Package(pkgName);

        // 3.解析<manifest>标签的属性
        TypedArray sa = res.obtainAttributes(parser,
                com.android.internal.R.styleable.AndroidManifest);

        // ...

        pkg.coreApp = parser.getAttributeBooleanValue(null, "coreApp", false);

        // ...

        sa.recycle();

        // 4. 继续解析其它的内容
        return parseBaseApkCommon(pkg, null, res, parser, flags, outError);
    }

从整体看,这一步主要是完成<manifest>一部分属性解析工作,当然还有加载overlay apk资源的工作,最后把剩余的解析工作交给了parseBaseApkCommon()函数,代码如下

    private Package parseBaseApkCommon(Package pkg, Set<String> acceptedTags, Resources res,
            XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException,
            IOException {
        // ...

        // 1. 解析<manifest>的sharedUserId标签
        String str = sa.getNonConfigurationString(
                com.android.internal.R.styleable.AndroidManifest_sharedUserId, 0);
        if (str != null && str.length() > 0) {
            String nameError = validateName(str, true, true);
            if (nameError != null && !"android".equals(pkg.packageName)) {
                outError[0] = "<manifest> specifies bad sharedUserId name \""
                    + str + "\": " + nameError;
                mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID;
                return null;
            }
            pkg.mSharedUserId = str.intern();
            pkg.mSharedUserLabel = sa.getResourceId(
                    com.android.internal.R.styleable.AndroidManifest_sharedUserLabel, 0);
        }

        // ...
        
        // 2. 循环解析<manifest>下的所有子标签
        int outerDepth = parser.getDepth();
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
            // ...

            if (tagName.equals(TAG_APPLICATION)) {
                // 从这里可以看出,只能有一个<application>标签
                if (foundApp) {
                   // ...
                }

                foundApp = true;
                if (!parseBaseApplication(pkg, res, parser, flags, outError)) {
                    return null;
                }
            } else if (tagName.equals(TAG_OVERLAY)) {
                sa = res.obtainAttributes(parser,
                        com.android.internal.R.styleable.AndroidManifestResourceOverlay);
                pkg.mOverlayTarget = sa.getString(
                        com.android.internal.R.styleable.AndroidManifestResourceOverlay_targetPackage);
                pkg.mOverlayTargetName = sa.getString(
                        com.android.internal.R.styleable.AndroidManifestResourceOverlay_targetName);
                pkg.mOverlayCategory = sa.getString(
                        com.android.internal.R.styleable.AndroidManifestResourceOverlay_category);
                pkg.mOverlayPriority = sa.getInt(
                        com.android.internal.R.styleable.AndroidManifestResourceOverlay_priority,
                        0);
                pkg.mOverlayIsStatic = sa.getBoolean(
                        com.android.internal.R.styleable.AndroidManifestResourceOverlay_isStatic,
                        false);
                final String propName = sa.getString(
                        com.android.internal.R.styleable
                        .AndroidManifestResourceOverlay_requiredSystemPropertyName);
                final String propValue = sa.getString(
                        com.android.internal.R.styleable
                        .AndroidManifestResourceOverlay_requiredSystemPropertyValue);
                sa.recycle();

                // 必须指定 targetPackage属性
                if (pkg.mOverlayTarget == null) {
                    outError[0] = "<overlay> does not specify a target package";
                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                    return null;
                }

                // priority属性值范围为[0, 9999]
                if (pkg.mOverlayPriority < 0 || pkg.mOverlayPriority > 9999) {
                    outError[0] = "<overlay> priority must be between 0 and 9999";
                    mParseError =
                        PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                    return null;
                }

                // 检测requiredSystemPropertyName指定的属性名对应的值与requiredSystemPropertyValue指定的值是否相等
                if (!checkOverlayRequiredSystemProperty(propName, propValue)) {
                    Slog.i(TAG, "Skipping target and overlay pair " + pkg.mOverlayTarget + " and "
                        + pkg.baseCodePath+ ": overlay ignored due to required system property: "
                        + propName + " with value: " + propValue);
                    return null;
                }

                // PRIVATE_FLAG_IS_RESOURCE_OVERLAY 标签为overlay 资源
                pkg.applicationInfo.privateFlags |=
                    ApplicationInfo.PRIVATE_FLAG_IS_RESOURCE_OVERLAY;

                XmlUtils.skipCurrentTag(parser);

            } else if (tagName.equals(TAG_KEY_SETS)) {
                // ...
            } else if (tagName.equals(TAG_PERMISSION_GROUP)) {
                // ...
            } else if (tagName.equals(TAG_PERMISSION)) {
               // ...
            } else if (tagName.equals(TAG_PERMISSION_TREE)) {
                // ...
            } else if (tagName.equals(TAG_USES_PERMISSION)) {
                // ...
            } else if (tagName.equals(TAG_USES_PERMISSION_SDK_M)
                    || tagName.equals(TAG_USES_PERMISSION_SDK_23)) {
                // ...
            } else if (tagName.equals(TAG_USES_CONFIGURATION)) {
                // ...
            } else if (tagName.equals(TAG_USES_FEATURE)) {
                // ...
            } else if (tagName.equals(TAG_FEATURE_GROUP)) {
                // ...
            } else if (tagName.equals(TAG_USES_SDK)) {
                // ...
            } else if (tagName.equals(TAG_SUPPORT_SCREENS)) {
                // ...
            } else if (tagName.equals(TAG_PROTECTED_BROADCAST)) {
                // 受保存广播只能由系统应用定义,并且由系统发送
            } else if (tagName.equals(TAG_INSTRUMENTATION)) {
                // ...
            } else if (tagName.equals(TAG_ORIGINAL_PACKAGE)) {
                // <original-package>是用于系统升级,替换某个系统app,并保留它的数据
                sa = res.obtainAttributes(parser,
                        com.android.internal.R.styleable.AndroidManifestOriginalPackage);

                String orig =sa.getNonConfigurationString(
                        com.android.internal.R.styleable.AndroidManifestOriginalPackage_name, 0);
                if (!pkg.packageName.equals(orig)) {
                    if (pkg.mOriginalPackages == null) {
                        // 这这里可知,可以有多个<original-package>标签,也就是说系统升级可以替换多个应用?
                        pkg.mOriginalPackages = new ArrayList<String>();
                        pkg.mRealPackage = pkg.packageName;
                    }
                    pkg.mOriginalPackages.add(orig);
                }

                sa.recycle();

                XmlUtils.skipCurrentTag(parser);

            } /// ...
            } else if (tagName.equals(TAG_PACKAGE)) {
                // ...
            } else if (tagName.equals(TAG_RESTRICT_UPDATE)) {
                //...
            } //...
        }

        // ...

        return pkg;
    }

这里首先解析了<manifest>标签的sharedUserId属性,然后再循环解析了<manifest>下的所有子标签,例如<application>。

这里我们来谈下<overlay>标签,这个是关于overlay apk的,我们先看下系统中一个overlay apk的AndroidManifest.xml如休写的

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="android.overlay.target">
    <overlay android:targetPackage="android" android:isStatic="true" android:priority="500"/>
</manifest>

<overlay>标签中,targetPackage表示要覆盖资源的目标app,这里的值是android,也就是说覆盖frameworks-res.apk的资源。isStatic表示是静态overlay app,这个是必不可少的。最后priority表示如果多个overlay app,取优先级最高的覆盖资源。

既然有静态overlay app,是否也有动态 overlay app?好像是有,但是我并没有深入了解这个东西。

可能我们最关心的就是如何解析<application>标签,因为这里面有我们学用的四大组件,这是由parseBaseApplication()函数解析的

    private boolean parseBaseApplication(Package owner, Resources res,
            XmlResourceParser parser, int flags, String[] outError)
        throws XmlPullParserException, IOException {
        // <application>标签的信息保存到ApplicationInfo中
        final ApplicationInfo ai = owner.applicationInfo;
        
        // ...

        // ai.name 是由 <application>的name属性值,也就是自定义的Application类
        if (ai.name != null) {
            ai.className = ai.name;
        }

        // ...

        // 关于备份功能
        boolean allowBackup = sa.getBoolean(
                com.android.internal.R.styleable.AndroidManifestApplication_allowBackup, true);
        if (allowBackup) {
            // ...
        }

        // ...

        // persistent属性
        if (sa.getBoolean(
                com.android.internal.R.styleable.AndroidManifestApplication_persistent,
                false)) {
            // Check if persistence is based on a feature being present
            final String requiredFeature = sa.getNonResourceString(com.android.internal.R.styleable
                    .AndroidManifestApplication_persistentWhenFeatureAvailable);
            if (requiredFeature == null || mCallback.hasFeature(requiredFeature)) {
                // FLAG_PERSISTENT 表示app是persistent app
                ai.flags |= ApplicationInfo.FLAG_PERSISTENT;
            }
        }

        // ...

        // hardwareAccelerated属性
        owner.baseHardwareAccelerated = sa.getBoolean(
                com.android.internal.R.styleable.AndroidManifestApplication_hardwareAccelerated,
                owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH);
        if (owner.baseHardwareAccelerated) {
            // FLAG_HARDWARE_ACCELERATED 表示开启硬件加速
            ai.flags |= ApplicationInfo.FLAG_HARDWARE_ACCELERATED;
        }

        // ...

        // directBootAware属性
        if (sa.getBoolean(
                R.styleable.AndroidManifestApplication_directBootAware,
                false)) {
            // PRIVATE_FLAG_DIRECT_BOOT_AWARE 表示这个app会在ams中启动
            ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE;
        }

        // ...

        // 这个表示使用使用老式的外部存储
        if (sa.getBoolean(
                R.styleable.AndroidManifestApplication_requestLegacyExternalStorage,
                owner.applicationInfo.targetSdkVersion < Build.VERSION_CODES.Q)) {
            ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE;
        }

        // ...

        // app 分类,例如message, calendar分类
        ai.category = sa.getInt(
                com.android.internal.R.styleable.AndroidManifestApplication_appCategory,
                ApplicationInfo.CATEGORY_UNDEFINED);

        // ...

        if (outError[0] == null) {
            CharSequence pname;
            // <manifest>的process属性
            if (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.FROYO) {
                pname = sa.getNonConfigurationString(
                        com.android.internal.R.styleable.AndroidManifestApplication_process,
                        Configuration.NATIVE_CONFIG_VERSION);
            } else {
                // Some older apps have been seen to use a resource reference
                // here that on older builds was ignored (with a warning).  We
                // need to continue to do this for them so they don't break.
                pname = sa.getNonResourceString(
                        com.android.internal.R.styleable.AndroidManifestApplication_process);
            }
            // 如果process属性的值以分号开头,那么最终的值为packageName+process
            // 否则最终的值为 process属性的值
            ai.processName = buildProcessName(ai.packageName, null, pname,
                    flags, mSeparateProcesses, outError);

            // ...
        }

        // ...
        
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                continue;
            }

            String tagName = parser.getName();
            if (tagName.equals("activity")) {
                Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs, false,
                        owner.baseHardwareAccelerated);
                // ...
                owner.activities.add(a);
            } else if (tagName.equals("receiver")) {
                Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs,
                        true, false);
                // ...
                owner.receivers.add(a);
            } else if (tagName.equals("service")) {
                Service s = parseService(owner, res, parser, flags, outError, cachedArgs);
                // ...
                owner.services.add(s);
            } else if (tagName.equals("provider")) {
                Provider p = parseProvider(owner, res, parser, flags, outError, cachedArgs);
                // ...
                owner.providers.add(p);
            } else if (tagName.equals("activity-alias")) {
                // ...
            } else if (parser.getName().equals("meta-data")) {
                // ...
            } else if (tagName.equals("static-library")) {
                // 编译成静态库
            } else if (tagName.equals("library")) {
                // 编译成动态库
            } else if (tagName.equals("uses-static-library")) {
                // 使用静态库
            } else if (tagName.equals("uses-library")) {
                // 使用动态库
            } else if (tagName.equals("uses-package")) {
                XmlUtils.skipCurrentTag(parser);
            } else if (tagName.equals("profileable")) {
                // ...
            } // ...
        }

        // ...

        return true;
    }

函数的最开始获取了PackageParser.Package.applicationInfo变量,它的类型是ApplicationInfo,用于保存Application标签的信息。

这段代码中保存了一些<application>标签属性解析的代码,是一些常用的。在这段代码末尾阶段,它解析了<application>标签下的子标签,例如四大组件的标签。四大组件分别使用PackageParser.Package.activities, PackageParser.Package.receivers , PackageParser.Package.services ,PackageParser.Package.providers 保存。

到此,APK的扫描工作已经完成,可谓是波澜不惊,现在我们要记住一点,APK扫描结果现在保存在PackageParser.Package中。然而这只是一个初步扫描过程,PKMS还会对结果进行处理,更重要的是把这个结果转移到PKMS的数据结构中,这就是下一篇的内容。