• Android 解析APK包


      它实现在ParsingPackageUtils类的静态方法parseDefault(ParseInput input, File file, @ParseFlags int parseFlags, @NonNull List splitPermissions, boolean collectCertificates)中,看一下它的代码:

        @NonNull
        public static ParseResult<ParsingPackage> parseDefault(ParseInput input, File file,
                @ParseFlags int parseFlags,
                @NonNull List<PermissionManager.SplitPermissionInfo> splitPermissions,
                boolean collectCertificates) {
            ParseResult<ParsingPackage> result;
    
            ParsingPackageUtils parser = new ParsingPackageUtils(false, null, null, splitPermissions,
                    new Callback() {
                        @Override
                        public boolean hasFeature(String feature) {
                            // Assume the device doesn't support anything. This will affect permission
                            // parsing and will force  declarations to include all
                            // requiredNotFeature permissions and exclude all requiredFeature
                            // permissions. This mirrors the old behavior.
                            return false;
                        }
    
                        @Override
                        public ParsingPackage startParsingPackage(
                                @NonNull String packageName,
                                @NonNull String baseApkPath,
                                @NonNull String path,
                                @NonNull TypedArray manifestArray, boolean isCoreApp) {
                            return new ParsingPackageImpl(packageName, baseApkPath, path,
                                    manifestArray);
                        }
                    });
            try {
                result = parser.parsePackage(input, file, parseFlags);
                if (result.isError()) {
                    return result;
                }
            } catch (PackageParser.PackageParserException e) {
                return input.error(PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                        "Error parsing package", e);
            }
    
            try {
                ParsingPackage pkg = result.getResult();
                if (collectCertificates) {
                    pkg.setSigningDetails(
                            ParsingPackageUtils.getSigningDetails(pkg, false /* skipVerify */));
                }
    
                // Need to call this to finish the parsing stage
                pkg.hideAsParsed();
    
                return input.success(pkg);
            } catch (PackageParser.PackageParserException e) {
                return input.error(PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                        "Error collecting package certificates", e);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

       1、新建 ParsingPackageUtils 实例 在这里设置的回调Callback的startParsingPackage() 方法,它是用来生成parseDefault()实际返回的对象类型。
       2、调用新生成的ParsingPackageUtils 实例的parsePackage()得到ParseResult类型result。
       3、根据参数 collectCertificates 来判断是否执行签名验证。可以参考Android APK文件的签名V2查找、验证Android APK文件完整性验证
      其中比较主要的是第二步,解析包得到包信息。

    解析包

      相关代码如下:

        public ParseResult<ParsingPackage> parsePackage(ParseInput input, File packageFile,
                int flags)
                throws PackageParserException {
            if (packageFile.isDirectory()) {
                return parseClusterPackage(input, packageFile, flags);
            } else {
                return parseMonolithicPackage(input, packageFile, flags);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

      根据参数packageFile 是目录还是文件分别执行 parseClusterPackage(input, packageFile, flags) 还是 parseMonolithicPackage(input, packageFile, flags)。
      像用户手动安装应用的时候,就是文件形式的。在开机时安装应用,则是目录的形式。

    文件形式解析

        private ParseResult<ParsingPackage> parseMonolithicPackage(ParseInput input, File apkFile,
                int flags) throws PackageParserException {
            final ParseResult<PackageLite> liteResult =
                    ApkLiteParseUtils.parseMonolithicPackageLite(input, apkFile, flags);
            if (liteResult.isError()) {
                return input.error(liteResult);
            }
    
            final PackageLite lite = liteResult.getResult();
            if (mOnlyCoreApps && !lite.isCoreApp()) {
                return input.error(INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED,
                        "Not a coreApp: " + apkFile);
            }
    
            final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);
            try {
                final ParseResult<ParsingPackage> result = parseBaseApk(input,
                        apkFile,
                        apkFile.getCanonicalPath(),
                        assetLoader, flags);
                if (result.isError()) {
                    return input.error(result);
                }
    
                return input.success(result.getResult()
                        .setUse32BitAbi(lite.isUse32bitAbi()));
            } catch (IOException e) {
                return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                        "Failed to get path: " + apkFile, e);
            } finally {
                IoUtils.closeQuietly(assetLoader);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

      首先通过ApkLiteParseUtils.parseMonolithicPackageLite(input, apkFile, flags)得到ParseResult类型liteResult。
      其次生成一个DefaultSplitAssetLoader类对象。
      通过parseBaseApk()得到ParseResult类对象result。
      这里提到的lite.isUse32bitAbi(),该值是Manifest配置文件中标签 application 下的 use32bitAbi 属性值,如果不配置 默认为false。

    解析得到apk文件的轻量级详情包

      它实现在ApkLiteParseUtils类的parseMonolithicPackageLite()方法中:

        /**
         * Parse lightweight details about a single APK files.
         */
        public static ParseResult<PackageLite> parseMonolithicPackageLite(ParseInput input,
                File packageFile, int flags) {
            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
            try {
                final ParseResult<ApkLite> result = parseApkLite(input, packageFile, flags);
                if (result.isError()) {
                    return input.error(result);
                }
    
                final ApkLite baseApk = result.getResult();
                final String packagePath = packageFile.getAbsolutePath();
                return input.success(
                        new PackageLite(packagePath, baseApk.getPath(), baseApk, null,
                                null, null, null, null, null, baseApk.getTargetSdkVersion()));
            } finally {
                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

      可见这里主要是调用parseApkLite(input, packageFile, flags)得到ApkLite对象,然后将它封装到PackageLite对象中,并且返回。其中,PackageLite的构造函数中,使用了packagePath和baseApk.getPath()。packagePath是文件的绝对路径。baseApk.getPath()是什么呢,在这里也是文件的绝对路径。这俩值是赋值给了PackageLite的成员变量mPath和mBaseApkPath。看其注释,说它俩可能是不同的,因为移动或者重命名APK文件。
      parseApkLite(input, packageFile, flags)主要接着调用parseApkLiteInner(ParseInput input, File apkFile, FileDescriptor fd, String debugPathName, int flags),它再调用parseApkLite(ParseInput input, String codePath, XmlResourceParser parser, PackageParser.SigningDetails signingDetails),主要解析的就是"AndroidManifest.xml"文件,但是它不会解析四大组件相关的属性信息。所以说它是轻量级。主要的解析信息都在parseApkLite(ParseInput input, String codePath, XmlResourceParser parser, PackageParser.SigningDetails signingDetails)中,看一下最后生成的ApkLite对象:

            return input.success(
                    new ApkLite(codePath, packageSplit.first, packageSplit.second, isFeatureSplit,
                            configForSplit, usesSplitName, isSplitRequired, versionCode,
                            versionCodeMajor, revisionCode, installLocation, verifiers, signingDetails,
                            coreApp, debuggable, profilableByShell, multiArch, use32bitAbi,
                            useEmbeddedDex, extractNativeLibs, isolatedSplits, targetPackage,
                            overlayIsStatic, overlayPriority, minSdkVersion, targetSdkVersion,
                            rollbackDataPolicy));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

      这里面的值都是从"AndroidManifest.xml"文件中取得的,后面用到再说。

    解析得到apk文件的解析包

      它主要实现在parseBaseApk(ParseInput input, File apkFile, String codePath, SplitAssetLoader assetLoader, int flags),

        private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, File apkFile,
                String codePath, SplitAssetLoader assetLoader, int flags)
                throws PackageParserException {
            final String apkPath = apkFile.getAbsolutePath();
    
            String volumeUuid = null;
            if (apkPath.startsWith(MNT_EXPAND)) {
                final int end = apkPath.indexOf('/', MNT_EXPAND.length());
                volumeUuid = apkPath.substring(MNT_EXPAND.length(), end);
            }
    
            if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);
    
            final AssetManager assets = assetLoader.getBaseAssetManager();
            final int cookie = assets.findCookieForPath(apkPath);
            if (cookie == 0) {
                return input.error(INSTALL_PARSE_FAILED_BAD_MANIFEST,
                        "Failed adding asset path: " + apkPath);
            }
    
            try (XmlResourceParser parser = assets.openXmlResourceParser(cookie,
                    ANDROID_MANIFEST_FILENAME)) {
                final Resources res = new Resources(assets, mDisplayMetrics, null);
    
                ParseResult<ParsingPackage> result = parseBaseApk(input, apkPath, codePath, res,
                        parser, flags);
                if (result.isError()) {
                    return input.error(result.getErrorCode(),
                            apkPath + " (at " + parser.getPositionDescription() + "): "
                                    + result.getErrorMessage());
                }
    
                final ParsingPackage pkg = result.getResult();
                if (assets.containsAllocatedTable()) {
                    final ParseResult<?> deferResult = input.deferError(
                            "Targeting R+ (version " + Build.VERSION_CODES.R + " and above) requires"
                                    + " the resources.arsc of installed APKs to be stored uncompressed"
                                    + " and aligned on a 4-byte boundary",
                            DeferredError.RESOURCES_ARSC_COMPRESSED);
                    if (deferResult.isError()) {
                        return input.error(INSTALL_PARSE_FAILED_RESOURCES_ARSC_COMPRESSED,
                                deferResult.getErrorMessage());
                    }
                }
    
                ApkAssets apkAssets = assetLoader.getBaseApkAssets();
                boolean definesOverlayable = false;
                try {
                    definesOverlayable = apkAssets.definesOverlayable();
                } catch (IOException ignored) {
                    // Will fail if there's no packages in the ApkAssets, which can be treated as false
                }
    
                if (definesOverlayable) {
                    SparseArray<String> packageNames = assets.getAssignedPackageIdentifiers();
                    int size = packageNames.size();
                    for (int index = 0; index < size; index++) {
                        String packageName = packageNames.valueAt(index);
                        Map<String, String> overlayableToActor = assets.getOverlayableMap(packageName);
                        if (overlayableToActor != null && !overlayableToActor.isEmpty()) {
                            for (String overlayable : overlayableToActor.keySet()) {
                                pkg.addOverlayable(overlayable, overlayableToActor.get(overlayable));
                            }
                        }
                    }
                }
    
                pkg.setVolumeUuid(volumeUuid);
    
                if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) {
                    pkg.setSigningDetails(getSigningDetails(pkg, false));
                } else {
                    pkg.setSigningDetails(SigningDetails.UNKNOWN);
                }
    
                return input.success(pkg);
            } catch (Exception e) {
                return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                        "Failed to read manifest from " + apkPath, e);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81

      首先得到解析包的volumeUuid,当然这得是它的安装路径是以"/mnt/expand/“开头。然后通过截取”/mnt/expand/“和它的下一个”/“之间的值作为volumeUuid。
      assetLoader在这里是DefaultSplitAssetLoader对象,它是可以用来加载splits APK的,所以它可能对应有多个APK文件。这里通过文件路径apkPath找到AssetManager中对应的APK的位置。然后再通过cookie找到对应APK文件的"AndroidManifest.xml”,最后它的解析信息在XmlResourceParser类型的parser对象中。
      其实解析APK文件也是挺复杂的,它的内容在AssetManager的getBaseAssetManager()中。它涉及到C++层的实现。这里只是简单描述一下相应过程,说通逻辑。
      接着就是调用另一重载函数parseBaseApk(),来生成ParsingPackage对象。看一下它的实现:

        private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, String apkPath,
                String codePath, Resources res, XmlResourceParser parser, int flags)
                throws XmlPullParserException, IOException {
            final String splitName;
            final String pkgName;
    
            ParseResult<Pair<String, String>> packageSplitResult =
                    ApkLiteParseUtils.parsePackageSplitNames(input, parser);
            if (packageSplitResult.isError()) {
                return input.error(packageSplitResult);
            }
    
            Pair<String, String> packageSplit = packageSplitResult.getResult();
            pkgName = packageSplit.first;
            splitName = packageSplit.second;
    
            if (!TextUtils.isEmpty(splitName)) {
                return input.error(
                        PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME,
                        "Expected base APK, but found split " + splitName
                );
            }
    
            final TypedArray manifestArray = res.obtainAttributes(parser, R.styleable.AndroidManifest);
            try {
                final boolean isCoreApp =
                        parser.getAttributeBooleanValue(null, "coreApp", false);
                final ParsingPackage pkg = mCallback.startParsingPackage(
                        pkgName, apkPath, codePath, manifestArray, isCoreApp);
                final ParseResult<ParsingPackage> result =
                        parseBaseApkTags(input, pkg, manifestArray, res, parser, flags);
                if (result.isError()) {
                    return result;
                }
    
                return input.success(pkg);
            } finally {
                manifestArray.recycle();
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

      ApkLiteParseUtils.parsePackageSplitNames(input, parser)是为了解析"package"、"split"属性,解析完之后,将对应值放在Pair里。
      接下里会调用回调mCallback.startParsingPackage()方法,我们返回到ParsingPackageUtils的定义处。可知,它的实际类型是ParsingPackageImpl对象。
      接着调用parseBaseApkTags(input, pkg, manifestArray, res, parser, flags)来得到具体的解析包对象。

        private ParseResult<ParsingPackage> parseBaseApkTags(ParseInput input, ParsingPackage pkg,
                TypedArray sa, Resources res, XmlResourceParser parser, int flags)
                throws XmlPullParserException, IOException {
            ParseResult<ParsingPackage> sharedUserResult = parseSharedUser(input, pkg, sa);
            if (sharedUserResult.isError()) {
                return sharedUserResult;
            }
    
            pkg.setInstallLocation(anInteger(PARSE_DEFAULT_INSTALL_LOCATION,
                    R.styleable.AndroidManifest_installLocation, sa))
                    .setTargetSandboxVersion(anInteger(PARSE_DEFAULT_TARGET_SANDBOX,
                            R.styleable.AndroidManifest_targetSandboxVersion, sa))
                    /* Set the global "on SD card" flag */
                    .setExternalStorage((flags & PARSE_EXTERNAL_STORAGE) != 0);
    
            boolean foundApp = false;
            final int depth = parser.getDepth();
            int type;
            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                    && (type != XmlPullParser.END_TAG
                    || parser.getDepth() > depth)) {
                if (type != XmlPullParser.START_TAG) {
                    continue;
                }
    
                String tagName = parser.getName();
                final ParseResult result;
    
                //  has special logic, so it's handled outside the general method
                if (TAG_APPLICATION.equals(tagName)) {
                    if (foundApp) {
                        if (RIGID_PARSER) {
                            result = input.error(" has more than one ");
                        } else {
                            Slog.w(TAG, " has more than one ");
                            result = input.success(null);
                        }
                    } else {
                        foundApp = true;
                        result = parseBaseApplication(input, pkg, res, parser, flags);
                    }
                } else {
                    result = parseBaseApkTag(tagName, input, pkg, res, parser, flags);
                }
    
                if (result.isError()) {
                    return input.error(result);
                }
            }
    
            if (!foundApp && ArrayUtils.size(pkg.getInstrumentations()) == 0) {
                ParseResult<?> deferResult = input.deferError(
                        " does not contain an  or ",
                        DeferredError.MISSING_APP_TAG);
                if (deferResult.isError()) {
                    return input.error(deferResult);
                }
            }
    		…………
            return input.success(pkg);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

      可以看到这里主要是manifest文件中"application"标签和同级其他标签下的属性和标签。"application"标签下的主要是调用parseBaseApplication(input, pkg, res, parser, flags),和"application"标签同级的使用parseBaseApkTag(tagName, input, pkg, res, parser, flags)解析。
      在这里parseBaseApplication(input, pkg, res, parser, flags)就会将四大组件的信息都解析出来。
      这样执行完毕,就将解析包信息都放在ParsingPackageImpl对象中。

    目录形式解析

      所谓以目录形式解析,就是给了一个文件是目录,安装文件包括在它包括的文件中。
      看下它的实现:

        private ParseResult<ParsingPackage> parseClusterPackage(ParseInput input, File packageDir,
                int flags) {
            final ParseResult<PackageLite> liteResult =
                    ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, 0);
            if (liteResult.isError()) {
                return input.error(liteResult);
            }
    
            final PackageLite lite = liteResult.getResult();
            if (mOnlyCoreApps && !lite.isCoreApp()) {
                return input.error(INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED,
                        "Not a coreApp: " + packageDir);
            }
    
            // Build the split dependency tree.
            SparseArray<int[]> splitDependencies = null;
            final SplitAssetLoader assetLoader;
            if (lite.isIsolatedSplits() && !ArrayUtils.isEmpty(lite.getSplitNames())) {
                try {
                    splitDependencies = SplitAssetDependencyLoader.createDependenciesFromPackage(lite);
                    assetLoader = new SplitAssetDependencyLoader(lite, splitDependencies, flags);
                } catch (SplitAssetDependencyLoader.IllegalDependencyException e) {
                    return input.error(INSTALL_PARSE_FAILED_BAD_MANIFEST, e.getMessage());
                }
            } else {
                assetLoader = new DefaultSplitAssetLoader(lite, flags);
            }
    
            try {
                final File baseApk = new File(lite.getBaseApkPath());
                final ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk,
                        lite.getPath(), assetLoader, flags);
                if (result.isError()) {
                    return input.error(result);
                }
    
                ParsingPackage pkg = result.getResult();
                if (!ArrayUtils.isEmpty(lite.getSplitNames())) {
                    pkg.asSplit(
                            lite.getSplitNames(),
                            lite.getSplitApkPaths(),
                            lite.getSplitRevisionCodes(),
                            splitDependencies
                    );
                    final int num = lite.getSplitNames().length;
    
                    for (int i = 0; i < num; i++) {
                        final AssetManager splitAssets = assetLoader.getSplitAssetManager(i);
                        parseSplitApk(input, pkg, i, splitAssets, flags);
                    }
                }
    
                pkg.setUse32BitAbi(lite.isUse32bitAbi());
                return input.success(pkg);
            } catch (PackageParserException e) {
                return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                        "Failed to load assets: " + lite.getBaseApkPath(), e);
            } finally {
                IoUtils.closeQuietly(assetLoader);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

      先通过ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, 0)得到PackageLite对象。
      接着创建SplitAssetLoader对象。lite.isIsolatedSplits()来自基础APK的配置文件里的"isolatedSplits"属性值,默认为false。如果它设置为true,并且存在splite apk,则会构造SplitAssetDependencyLoader。它会构造split apk间的依赖关系,如果存在依赖,会在加载apk(这里主要是加载ID资源表)的时候,一起加载。
      再通过parseBaseApk(input, baseApk, lite.getPath(), assetLoader, flags)得到ParsingPackage对象。
      如果是split apk,会接着调用parseSplitApk(input, pkg, i, splitAssets, flags)。

    得到APK目录相关的轻量级详情

      看下ApkLiteParseUtils类的parseClusterPackageLite()方法:

        /**
         * Parse lightweight details about a directory of APKs.
         */
        public static ParseResult<PackageLite> parseClusterPackageLite(ParseInput input,
                File packageDir, int flags) {
            final File[] files = packageDir.listFiles();
            if (ArrayUtils.isEmpty(files)) {
                return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
                        "No packages found in split");
            }
            // Apk directory is directly nested under the current directory
            if (files.length == 1 && files[0].isDirectory()) {
                return parseClusterPackageLite(input, files[0], flags);
            }
    
            String packageName = null;
            int versionCode = 0;
    
            final ArrayMap<String, ApkLite> apks = new ArrayMap<>();
            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
            try {
                for (File file : files) {
                    if (isApkFile(file)) {
                        final ParseResult<ApkLite> result = parseApkLite(input, file, flags);
                        if (result.isError()) {
                            return input.error(result);
                        }
    
                        final ApkLite lite = result.getResult();
                        // Assert that all package names and version codes are
                        // consistent with the first one we encounter.
                        if (packageName == null) {
                            packageName = lite.getPackageName();
                            versionCode = lite.getVersionCode();
                        } else {
                            if (!packageName.equals(lite.getPackageName())) {
                                return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
                                        "Inconsistent package " + lite.getPackageName() + " in " + file
                                                + "; expected " + packageName);
                            }
                            if (versionCode != lite.getVersionCode()) {
                                return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
                                        "Inconsistent version " + lite.getVersionCode() + " in " + file
                                                + "; expected " + versionCode);
                            }
                        }
    
                        // Assert that each split is defined only oncuses-static-libe
                        if (apks.put(lite.getSplitName(), lite) != null) {
                            return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
                                    "Split name " + lite.getSplitName()
                                            + " defined more than once; most recent was " + file);
                        }
                    }
                }
            } finally {
                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
            }
    
            final ApkLite baseApk = apks.remove(null);
            return composePackageLiteFromApks(input, packageDir, baseApk, apks);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

      如果当前目录下面只有一个目录,会递归调用parseClusterPackageLite(ParseInput input, File packageDir, int flags)方法,并且会将这个目录作为参数传递。
      如果目录下有多个以".apk"结尾的文件,则它为split apk安装形式。首先通过parseApkLite(input, file, flags)解析得到ApkLite对象,这个前面讲过。接下来,可以看到如果是split apk安装形式,基本apk是不用配置"split"属性值,其他apk则是需要配置不同的"split"属性值。并且会将"split"属性值和ApkLite对象放入ArrayMap apks中,接着从apks中取出null key对应的ApkLite对象作为基本对象。
      接下来会调用composePackageLiteFromApks(input, packageDir, baseApk, apks),最后它会调用composePackageLiteFromApks( ParseInput input, File packageDir, ApkLite baseApk, ArrayMap splitApks, boolean apkRenamed)方法来生成PackageLite对象,看下它的实现:

        public static ParseResult<PackageLite> composePackageLiteFromApks(
                ParseInput input, File packageDir, ApkLite baseApk,
                ArrayMap<String, ApkLite> splitApks, boolean apkRenamed) {
            if (baseApk == null) {
                return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
                        "Missing base APK in " + packageDir);
            }
            // Always apply deterministic ordering based on splitName
            final int size = ArrayUtils.size(splitApks);
    
            String[] splitNames = null;
            boolean[] isFeatureSplits = null;
            String[] usesSplitNames = null;
            String[] configForSplits = null;
            String[] splitCodePaths = null;
            int[] splitRevisionCodes = null;
            if (size > 0) {
                splitNames = new String[size];
                isFeatureSplits = new boolean[size];
                usesSplitNames = new String[size];
                configForSplits = new String[size];
                splitCodePaths = new String[size];
                splitRevisionCodes = new int[size];
    
                splitNames = splitApks.keySet().toArray(splitNames);
                Arrays.sort(splitNames, sSplitNameComparator);
    
                for (int i = 0; i < size; i++) {
                    final ApkLite apk = splitApks.get(splitNames[i]);
                    usesSplitNames[i] = apk.getUsesSplitName();
                    isFeatureSplits[i] = apk.isFeatureSplit();
                    configForSplits[i] = apk.getConfigForSplit();
                    splitCodePaths[i] = apkRenamed ? new File(packageDir,
                            splitNameToFileName(apk)).getAbsolutePath() : apk.getPath();
                    splitRevisionCodes[i] = apk.getRevisionCode();
                }
            }
    
            final String codePath = packageDir.getAbsolutePath();
            final String baseCodePath = apkRenamed ? new File(packageDir,
                    splitNameToFileName(baseApk)).getAbsolutePath() : baseApk.getPath();
            return input.success(
                    new PackageLite(codePath, baseCodePath, baseApk, splitNames, isFeatureSplits,
                            usesSplitNames, configForSplits, splitCodePaths, splitRevisionCodes,
                            baseApk.getTargetSdkVersion()));
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

      这里主要是处理split apk的多个文件的情况,这里注意的一点splitCodePath的名字会根据参数apkRenamed 来赋不同的值,如果apkRenamed 为true,则值为目录 + “split_” + splitName + “.apk”;如果为false,则为文件的路径名。
      同样在为baseCodePath 赋值时,apkRenamed 为true,则值为目录 + “base.apk”;如果为false,则为文件的路径名。
      在这里codePath 和 baseCodePath 的值是不同的,codePath为目录名,baseCodePath 为基本apk文件路径。它俩值分别对应PackageLite对象的成员mPath、mBaseApkPath。
      这样就生成了PackageLite对象。

    解析得到解析包对象

      实现在parseBaseApk(ParseInput input, File apkFile, String codePath, SplitAssetLoader assetLoader, int flags)中,该方法在前面说过,在这里不同的是,codePath是目录,不是安装文件。

    解析包ParsingPackageImpl对象

      从前面知道,最终得到的解析包是ParsingPackageImpl对象,它里面包含所有解析出来的信息。看一下它的初始化函数:

        @VisibleForTesting
        public ParsingPackageImpl(@NonNull String packageName, @NonNull String baseApkPath,
                @NonNull String path, @Nullable TypedArray manifestArray) {
            this.packageName = TextUtils.safeIntern(packageName);
            this.mBaseApkPath = baseApkPath;
            this.mPath = path;
    
            if (manifestArray != null) {
                versionCode = manifestArray.getInteger(R.styleable.AndroidManifest_versionCode, 0);
                versionCodeMajor = manifestArray.getInteger(
                        R.styleable.AndroidManifest_versionCodeMajor, 0);
                setBaseRevisionCode(
                        manifestArray.getInteger(R.styleable.AndroidManifest_revisionCode, 0));
                setVersionName(manifestArray.getNonConfigurationString(
                        R.styleable.AndroidManifest_versionName, 0));
    
                setCompileSdkVersion(manifestArray.getInteger(
                        R.styleable.AndroidManifest_compileSdkVersion, 0));
                setCompileSdkVersionCodename(manifestArray.getNonConfigurationString(
                        R.styleable.AndroidManifest_compileSdkVersionCodename, 0));
    
                setIsolatedSplitLoading(manifestArray.getBoolean(
                        R.styleable.AndroidManifest_isolatedSplits, false));
    
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

      在这里,我们知道mBaseApkPath 和mPath 的区别了,在用目录形式的解析时,mPath 是包含apk文件的目录。它里面包含特别多的成员变量和方法。在这里,我们知道它是从以上哪些函数中得到的,等以后使用到,就去这些函数中查找。

    总结

      解析apk包信息时,一种是通过apk文件作为参数解析,另外一种是通过apk文件所在目录作为参数来解析。
      解析出来的类对象是ParsingPackageImpl类型的,它主要解析的是"AndroidManifest.xml"文件,类对象的成员也主要对应配置文件的属性和值。

  • 相关阅读:
    Ubuntu里安装CMake步骤
    【软考软件评测师】第六章节 可靠性测试测试方法
    嵌入式Linux应用开发-基础知识-第十六章GPIO和Pinctrl子系统的使用
    竞赛选题 基于深度学习的动物识别 - 卷积神经网络 机器视觉 图像识别
    【Flutter】Android原生WebView(非Flutter WebView)与FlutterWeb交互
    网络安全(黑客技术)—2024自学手册
    uniapp 显示icon异常
    composer 扩展库。助手库文档
    不要小看一个Redis~ 从头到尾全是精华,阿里Redis速成笔记太香了
    抓到Dubbo异步调用的小BUG,再送你一个贡献开源代码的机会
  • 原文地址:https://blog.csdn.net/q1165328963/article/details/133690581