当前位置: 首页 > article >正文

Android 启动时应用的安装解析过程《二》

上一篇内容说到InitAppsHelper这个类的initSystemApps函数,只说了一下几个重要参数的来源还没展开,这里继续,有兴趣的可以看链接: Android 启动时应用的安装解析过程《一》

一、系统应用的扫描安装

	/**
     * Install apps from system dirs.
     */
    @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
    public OverlayConfig initSystemApps(PackageParser2 packageParser,
            WatchedArrayMap<String, PackageSetting> packageSettings,
            int[] userIds, long startTime) {
        // Prepare apex package info before scanning APKs, this information is needed when
        // scanning apk in apex.
        // 扫描系统中apex信息,为扫描APK做准备,apex是系统中一种新的模块化组件
        final List<ApexManager.ScanResult> apexScanResults = scanApexPackagesTraced(packageParser);
        mApexManager.notifyScanResult(apexScanResults);
        // 扫描系统目录中的apk
        scanSystemDirs(packageParser, mExecutorService);
        // Parse overlay configuration files to set default enable state, mutability, and
        // priority of system overlays.
        final ArrayMap<String, File> apkInApexPreInstalledPaths = new ArrayMap<>();
        for (ApexManager.ActiveApexInfo apexInfo : mApexManager.getActiveApexInfos()) {
            final String apexPackageName = mApexManager.getActivePackageNameForApexModuleName(
                    apexInfo.apexModuleName);
            for (String packageName : mApexManager.getApksInApex(apexPackageName)) {
                apkInApexPreInstalledPaths.put(packageName, apexInfo.preInstalledApexPath);
            }
        }
        // 解析系统目录的overlay并把它使能
        final OverlayConfig overlayConfig = OverlayConfig.initializeSystemInstance(
                consumer -> mPm.forEachPackageState(mPm.snapshotComputer(),
                        packageState -> {
                            var pkg = packageState.getPkg();
                            if (pkg != null) {
                                consumer.accept(pkg, packageState.isSystem(),
                                        apkInApexPreInstalledPaths.get(pkg.getPackageName()));
                            }
                        }));

        // do this first before mucking with mPackages for the "expecting better" case
        // 做一些优化,比如禁用一些需要禁用的apk,删除掉已经被删除的应用缓存
        updateStubSystemAppsList(mStubSystemApps);
        mInstallPackageHelper.prepareSystemPackageCleanUp(packageSettings,
                mPossiblyDeletedUpdatedSystemApps, mExpectingBetter, userIds);

        logSystemAppsScanningTime(startTime);
        return overlayConfig;
    }

1.扫描系统目录

private void scanSystemDirs(PackageParser2 packageParser, ExecutorService executorService) {
        File frameworkDir = new File(Environment.getRootDirectory(), "framework");

        // Collect vendor/product/system_ext overlay packages. (Do this before scanning
        // any apps.)
        // For security and version matching reason, only consider overlay packages if they
        // reside in the right directory.
        for (int i = mDirsToScanAsSystem.size() - 1; i >= 0; i--) {
            final ScanPartition partition = mDirsToScanAsSystem.get(i);
            if (partition.getOverlayFolder() == null) {
                continue;
            }
            // 扫描系统分区的overlay 文件夹
            scanDirTracedLI(partition.getOverlayFolder(),
                    mSystemParseFlags, mSystemScanFlags | partition.scanFlag,
                    packageParser, executorService, partition.apexInfo);
        }
        // 扫描system/framework文件夹
        scanDirTracedLI(frameworkDir,
                mSystemParseFlags, mSystemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED,
                packageParser, executorService, null);
        if (!mPm.mPackages.containsKey("android")) {
            throw new IllegalStateException(
                    "Failed to load frameworks package; check log for warnings");
        }

        for (int i = 0, size = mDirsToScanAsSystem.size(); i < size; i++) {
            final ScanPartition partition = mDirsToScanAsSystem.get(i);
            if (partition.getPrivAppFolder() != null) {
                // 扫描系统分区的priv-app文件夹
                scanDirTracedLI(partition.getPrivAppFolder(),
                        mSystemParseFlags,
                        mSystemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag,
                        packageParser, executorService, partition.apexInfo);
            }
            // 扫描系统分区的app文件夹
            scanDirTracedLI(partition.getAppFolder(),
                    mSystemParseFlags, mSystemScanFlags | partition.scanFlag,
                    packageParser, executorService, partition.apexInfo);
        }
    }

上面可以看到,这个函数里面将系统分区的overlay,app,还有jar包都扫描了个遍,然后我们看下到底这个scanDirTracedLI函数里面做了哪些事情

private void scanDirTracedLI(File scanDir, int parseFlags, int scanFlags,
                             PackageParser2 packageParser, ExecutorService executorService,
                             @Nullable ApexManager.ActiveApexInfo apexInfo) {
    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
    try {
        if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
            // when scanning apk in apexes, we want to check the maxSdkVersion
            parseFlags |= PARSE_APK_IN_APEX;
        }
        // 调用到了InstallPackageHelper的installPackagesFromDir
        mInstallPackageHelper.installPackagesFromDir(scanDir, parseFlags,
                                                     scanFlags, packageParser, executorService, apexInfo);
    } finally {
        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
    }
}
InstallPackageHelper
public void installPackagesFromDir(File scanDir, int parseFlags,
                                   int scanFlags, PackageParser2 packageParser, ExecutorService executorService,
                                   @Nullable ApexManager.ActiveApexInfo apexInfo) {
    //首先将所有的文件列出来
    final File[] files = scanDir.listFiles();
    if (ArrayUtils.isEmpty(files)) {
        Log.d(TAG, "No files in app dir " + scanDir);
        return;
    }

    if (DEBUG_PACKAGE_SCANNING) {
        Log.d(TAG, "Scanning app dir " + scanDir + " scanFlags=" + scanFlags
              + " flags=0x" + Integer.toHexString(parseFlags));
    }
    // 这个又是个专门解析包的类
    ParallelPackageParser parallelPackageParser =
    new ParallelPackageParser(packageParser, executorService);

    // Submit files for parsing in parallel
    int fileCount = 0;
    for (File file : files) {
        // 这里判断是不是包:是apk文件或者是文件夹并且不是stageName,这个stageName是指
        // vmdl*.tmp,smdl*.tmp,smdl2tmp这些类的文件
        final boolean isPackage = (isApkFile(file) || file.isDirectory())
        && !PackageInstallerService.isStageName(file.getName());
        if (!isPackage) {
            // Ignore entries which are not packages
            continue;
        }
        // 这里如果携带了SCAN_DROP_CACHE的flag会清除缓存
        if ((scanFlags & SCAN_DROP_CACHE) != 0) {
            final PackageCacher cacher = new PackageCacher(mPm.getCacheDir(),
                                                           mPm.mPackageParserCallback);
            Log.w(TAG, "Dropping cache of " + file.getAbsolutePath());
            cacher.cleanCachedResult(file);
        }
        // 这里提交给解析器解析
        parallelPackageParser.submit(file, parseFlags);
        fileCount++;
    }

    // Process results one by one
    for (; fileCount > 0; fileCount--) {
        ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();
        Throwable throwable = parseResult.throwable;
        int errorCode = PackageManager.INSTALL_SUCCEEDED;
        String errorMsg = null;
        // 如果没有异常就表示解析成功另外两个条件属于失败了
        if (throwable == null) {
            try {
                Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "addForInitLI");
                // 这里初始化
                addForInitLI(parseResult.parsedPackage, parseFlags, scanFlags,
                             new UserHandle(UserHandle.USER_SYSTEM), apexInfo);
            } catch (PackageManagerException e) {
                errorCode = e.error;
                errorMsg = "Failed to scan " + parseResult.scanFile + ": " + e.getMessage();
                Slog.w(TAG, errorMsg);
            } finally {
                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
            }
        } else if (throwable instanceof PackageManagerException) {
            PackageManagerException e = (PackageManagerException) throwable;
            errorCode = e.error;
            errorMsg = "Failed to parse " + parseResult.scanFile + ": " + e.getMessage();
            Slog.w(TAG, errorMsg);
        } else {
            throw new IllegalStateException("Unexpected exception occurred while parsing "
                                            + parseResult.scanFile, throwable);
        }
        // apex的安装单独报给ApexManager
        if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0 && errorCode != INSTALL_SUCCEEDED) {
            mApexManager.reportErrorWithApkInApex(scanDir.getAbsolutePath(), errorMsg);
        }

        // Delete invalid userdata apps
        // 没安装成功的会在这里清除
        if ((scanFlags & SCAN_AS_SYSTEM) == 0
            && errorCode != PackageManager.INSTALL_SUCCEEDED) {
            logCriticalInfo(Log.WARN,
                            "Deleting invalid package at " + parseResult.scanFile);
            mRemovePackageHelper.removeCodePath(parseResult.scanFile);
        }
    }
}

从上面可以看出主要就做了两件事情,把文件夹下面的包一一解析,并做初始化,看看如何解析的先:
ParallelPackageParser submit之后

2.开始解析

/*---------------------------ParallelPackageParser start ---------------------------*/
public void submit(File scanFile, int parseFlags) {
    // 直接开线程开始解析
    mExecutorService.submit(() -> {
        ParseResult pr = new ParseResult();
        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parallel parsePackage [" + scanFile + "]");
        try {
            pr.scanFile = scanFile;
            // 解析
            pr.parsedPackage = parsePackage(scanFile, parseFlags);
        } catch (Throwable e) {
            pr.throwable = e;
        } finally {
            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
        }
        try {
            mQueue.put(pr);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            // Propagate result to callers of take().
            // This is helpful to prevent main thread from getting stuck waiting on
            // ParallelPackageParser to finish in case of interruption
            mInterruptedInThread = Thread.currentThread().getName();
        }
    });
}

protected ParsedPackage parsePackage(File scanFile, int parseFlags)
throws PackageManagerException {
    try {
        // 发现这里又交给PackageParser2去做了
        return mPackageParser.parsePackage(scanFile, parseFlags, true);
    } catch (PackageParserException e) {
        throw new PackageManagerException(e.error, e.getMessage(), e);
    }
}
/*---------------------------ParallelPackageParser end---------------------------*/
/*---------------------------PackageParser2 start---------------------------*/
public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches)
throws PackageParserException {
    // 列出所有的文件
    var files = packageFile.listFiles();
    // Apk directory is directly nested under the current directory
    if (ArrayUtils.size(files) == 1 && files[0].isDirectory()) {
        packageFile = files[0];
    }

    if (useCaches && mCacher != null) {
        // 这里会去检查有没有缓存,缓存是存在data/system/目录下如果有缓存则不再解析
        ParsedPackage parsed = mCacher.getCachedResult(packageFile, flags);
        if (parsed != null) {
            return parsed;
        }
    }

    long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
    ParseInput input = mSharedResult.get().reset();
    // 这里又交给了ParsingPackageUtils这个类去干活了
    ParseResult<ParsingPackage> result = mParsingUtils.parsePackage(input, packageFile, flags);
    if (result.isError()) {
        throw new PackageParserException(result.getErrorCode(), result.getErrorMessage(),
                                         result.getException());
    }

    ParsedPackage parsed = (ParsedPackage) result.getResult().hideAsParsed();

    long cacheTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
    // 这里解析好的结果保存到缓存,也就是只要APK被解析过一次就会存在缓存了
    if (mCacher != null) {
        mCacher.cacheResult(packageFile, flags, parsed);
    }
    if (LOG_PARSE_TIMINGS) {
        parseTime = cacheTime - parseTime;
        cacheTime = SystemClock.uptimeMillis() - cacheTime;
        if (parseTime + cacheTime > LOG_PARSE_TIMINGS_THRESHOLD_MS) {
            Slog.i(TAG, "Parse times for '" + packageFile + "': parse=" + parseTime
                   + "ms, update_cache=" + cacheTime + " ms");
        }
    }

    return parsed;
}

/*---------------------------PackageParser2 end---------------------------*/
/*---------------------------ParsingPackageUtils start---------------------------*/
public ParseResult<ParsingPackage> parsePackage(ParseInput input, File packageFile, int flags) {
        // 如果是文件夹则走if 文件则走else,这里为什么会这样分,那/system/app目录来说,
        // 系统在预置APP的时候可能是先在该目录下新建一层目录再放APK,也有可能直接放置一个APK在该目录下
        if (packageFile.isDirectory()) {
            return parseClusterPackage(input, packageFile,  flags);
        } else {
            return parseMonolithicPackage(input, packageFile, flags);
        }
}

// 先看下解析目录parseClusterPackage
private ParseResult<ParsingPackage> parseClusterPackage(ParseInput input, File packageDir,
            int flags) {
        int liteParseFlags = 0;
        // 这里判断是不是在解析APEX的时候解析APK
        if ((flags & PARSE_APK_IN_APEX) != 0) {
            liteParseFlags |= PARSE_APK_IN_APEX;
        }
        // 轻量解析APK,这里会解析APK的签名信息,AndroidManifest.xml中除了Activity,Service,ContentProvider及权限以外的信息,
        // 顺便会做一些校验比如SDK版本,签名校验,系统目录的APK 可以跳过签名校验(签名校验从v4到v1逐一校验),这一部分设计是为了节省时间
        final ParseResult<PackageLite> liteResult =
                ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, liteParseFlags);
        // 没有错误说明各种校验通过了
        if (liteResult.isError()) {
            return input.error(liteResult);
        }

        final PackageLite lite = liteResult.getResult();
        // Build the split dependency tree.
        SparseArray<int[]> splitDependencies = null;
        final SplitAssetLoader assetLoader;
        // 如果是APKS 格式的这里会创建资源依赖树
        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());
            boolean shouldSkipComponents = lite.isIsSdkLibrary() && disallowSdkLibsToBeApps();
            // 这里解析APK
            final ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk,
                    lite.getPath(), assetLoader, flags, shouldSkipComponents);
            if (result.isError()) {
                return input.error(result);
            }

            ParsingPackage pkg = result.getResult();
            // 如果是APKS格式进行另外的解析
            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);
                    final ParseResult<ParsingPackage> split =
                            parseSplitApk(input, pkg, i, splitAssets, flags);
                    if (split.isError()) {
                        return input.error(split);
                    }
                }
            }

            pkg.set32BitAbiPreferred(lite.isUse32bitAbi());
            return input.success(pkg);
        } catch (IllegalArgumentException e) {
            return input.error(e.getCause() instanceof IOException ? INSTALL_FAILED_INVALID_APK
                    : INSTALL_PARSE_FAILED_NOT_APK, e.getMessage(), e);
        } finally {
            IoUtils.closeQuietly(assetLoader);
        }
}
/*******parseBaseApk******/

 private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, File apkFile,
            String codePath, SplitAssetLoader assetLoader, int flags,
            boolean shouldSkipComponents) {
        ......
        // 又开始解析AndroidManifest.xml
        try (XmlResourceParser parser = assets.openXmlResourceParser(cookie,
                ANDROID_MANIFEST_FILENAME)) {
            final Resources res = new Resources(assets, mDisplayMetrics, null);
            // 这里又一个重载解析APK
            ParseResult<ParsingPackage> result = parseBaseApk(input, apkPath, codePath, res,
                    parser, flags, shouldSkipComponents);
            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 {
                // 查询该应用是否支持overlay,一般定义了相应的overlayable
                definesOverlayable = apkAssets.definesOverlayable();
            } catch (IOException ignored) {
                // Will fail if there's no packages in the ApkAssets, which can be treated as false
            }
            // 如果可以这里会查找到可以被overlay的域
            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) {
                final ParseResult<SigningDetails> ret =
                        getSigningDetails(input, pkg, false /*skipVerify*/);
                if (ret.isError()) {
                    return input.error(ret);
                }
                pkg.setSigningDetails(ret.getResult());
            } else {
                pkg.setSigningDetails(SigningDetails.UNKNOWN);
            }

            if (Flags.aslInApkAppMetadataSource()) {
                try (InputStream in = assets.open(APP_METADATA_FILE_NAME)) {
                    pkg.setAppMetadataFileInApk(true);
                } catch (Exception e) { }
            }

            return input.success(pkg);
        } catch (Exception e) {
            return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                    "Failed to read manifest from " + apkPath, e);
        }
    }

/*******parseBaseApk 重载******/
private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, String apkPath,
            String codePath, Resources res, XmlResourceParser parser, int flags,
            boolean shouldSkipComponents)
            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 {
            // 是否是core app ,类似系统设置
            final boolean isCoreApp = parser.getAttributeBooleanValue(null /*namespace*/,
                    "coreApp", false);
            final ParsingPackage pkg = mCallback.startParsingPackage(
                    pkgName, apkPath, codePath, manifestArray, isCoreApp);
            // 解析AndroidManinfest里面的各个TAG,真正解析的开始
            final ParseResult<ParsingPackage> result =
                    parseBaseApkTags(input, pkg, manifestArray, res, parser, flags,
                            shouldSkipComponents);
            if (result.isError()) {
                return result;
            }

            return input.success(pkg);
        } finally {
            manifestArray.recycle();
        }
    }

/**** parseBaseApkTags ****/
private ParseResult<ParsingPackage> parseBaseApkTags(ParseInput input, ParsingPackage pkg,
            TypedArray sa, Resources res, XmlResourceParser parser, int flags,
            boolean shouldSkipComponents) throws XmlPullParserException, IOException {
        // 解析共享uid
        ParseResult<ParsingPackage> sharedUserResult = parseSharedUser(input, pkg, sa);
        if (sharedUserResult.isError()) {
            return sharedUserResult;
        }
        // 用于标记系统为可更新,通常和动态系统分区及增量更新机制有关。
        final boolean updatableSystem = parser.getAttributeBooleanValue(null /*namespace*/,
                "updatableSystem", true);
        // 用于设备恢复模式下的紧急安装或恢复。
        final String emergencyInstaller = parser.getAttributeValue(null /*namespace*/,
                "emergencyInstaller");

        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)
                .setUpdatableSystem(updatableSystem)
                .setEmergencyInstaller(emergencyInstaller);

        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;
            }
            if (sAconfigFlags.skipCurrentElement(parser)) {
                continue;
            }

            String tagName = parser.getName();
            final ParseResult result;

            // <application> has special logic, so it's handled outside the general method
            // 解析Application TAG配置及四大组件信息
            if (TAG_APPLICATION.equals(tagName)) {
                if (foundApp) {
                    if (RIGID_PARSER) {
                        result = input.error("<manifest> has more than one <application>");
                    } else {
                        Slog.w(TAG, "<manifest> has more than one <application>");
                        result = input.success(null);
                    }
                } else {
                    foundApp = true;
                    result = parseBaseApplication(input, pkg, res, parser, flags,
                            shouldSkipComponents);
                }
            } else {
                // Application 以外的TAG信息
                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(
                    "<manifest> does not contain an <application> or <instrumentation>",
                    DeferredError.MISSING_APP_TAG);
            if (deferResult.isError()) {
                return input.error(deferResult);
            }
        }

        return validateBaseApkTags(input, pkg, flags);
    }

后面都是一些解析细节了,不在追述,值得注意的是大部分APK 只会解析一次会将APK 的信息存储在/data/system/package_caches,下面除非apk出现了更新否则不会再重新解析


http://www.kler.cn/a/387918.html

相关文章:

  • C++ QT 工具日志异步分批保存
  • 【电力系统】永磁同步电机调速系统带有扰动观测器
  • docker基础:搭建centos7(详见B站泷羽sec)
  • 11 go语言(golang) - 数据类型:结构体
  • 《硬件架构的艺术》笔记(一):亚稳态
  • 晨控RFID技术助力半导体制造业革新之路
  • MATLAB实现智能水滴算法(Intelligent Water Drops Algorithm, IWDA)
  • oracle 带有小数点的0.几数据在转化为字符串的时候丢失前面的0
  • C# 用于将一个DataTable转换为Users对象的列表
  • fastapi_socketio连接vue的socktio.client
  • prompt资料收集
  • 网络编程示例之网络基础知识
  • PVE纵览-备份与快照指南
  • uniapp+vue加油服务系统 微信小程序
  • 在一个项目的完整开发中,会涉及到多个 Git 命令
  • kafka面试夺命30问
  • 6:arm condition code flags详细的讲解
  • P5019 [NOIP2018 提高组] 铺设道路
  • volta多版本node管理工具
  • 「Mac畅玩鸿蒙与硬件30」UI互动应用篇7 - 简易计步器
  • 共享汽车管理:SpringBoot技术实现指南
  • ubuntu 22.04 如何调整进程启动后能打开的文件数限制
  • 基于Spring Boot+Vue的养老院管理系统【原创】
  • ElasticSearch备考 -- 集群配置常见问题
  • FPN(Feature Pyramid Network)
  • pytorch3d报错:RuntimeError: Not compiled with GPU support.