美文网首页
【Android Framework面试题】在清单文件中配置的r

【Android Framework面试题】在清单文件中配置的r

作者: 小城哇哇 | 来源:发表于2023-09-21 22:03 被阅读0次

    在清单文件中配置的receiver,系统是何时会注册此广播接受者的?

    这道题想考察什么?

    考察同学是否对清单文件的解析熟悉,以及PMS相关的知识的理解。

    考生应该如何回答

    清单文件Manifest 一般被解析的时间有两处:

    1)手机启动的时候,pkms会按照 core app, system app,other app的优先级方案扫码APK,解析AndroidManifest.xml文件,最后得到清单文件中的标签,然后存储到settings对象中并持久化;

    2)在动态部署apk的时候,apk的安装中也必然会调用PackageManagerService来解析apk包中的AndroidManifest文件。所以系统注册广播接收者的时间基本上就是这两个时段,那么具体的细节大家可以参考后面的分析。

    首先apk是由PMS解析的,下面将介绍PMS如何解析APK:

    1)android系统启动之后会解析系统特定目录下的apk文件,并执行解析;
    2)解析Manifest流程:Zygote进程 –> SystemServer进程 –> PackgeManagerService服务 –> scanDirLI方法 –> scanPackageLI方法 –> PackageParser.parserPackage方法;
    3)解析完成Manifest之后会将apk的Manifest信息保存在Settings对象中并持久化,删除软件时,信息会被删除,新安装的apk会重复调用scanDirLi。

    我们一起来分析一下具体的代码的调度流程:

    scanPackageLI()函数说明

    PMS 的构造函数中会通过 scanDirTracedLI() 对各个指定的目录进行扫描:

        private void scanDirTracedLI(File dir, final int parseFlags, int scanFlags, long currentTime) {
            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + dir.getAbsolutePath() + "]");
            try {
                scanDirLI(dir, parseFlags, scanFlags, currentTime);
            } finally {
                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
            }
        }
    
        private void scanDirLI(File dir, 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));
            }
            ParallelPackageParser parallelPackageParser = new ParallelPackageParser(
                    mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir,
                    mParallelPackageParserCallback);
     
            // Submit files for parsing in parallel
            int fileCount = 0;
            for (File file : files) {
                final boolean isPackage = (isApkFile(file) || file.isDirectory()) //判断是否为应用文件
                        && !PackageInstallerService.isStageName(file.getName());
                if (!isPackage) {
                    // Ignore entries which are not packages
                    continue; //忽略非应用文件
                }
     
                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;
     
                if (throwable == null) {
                    // Static shared libraries have synthetic package names
                    if (parseResult.pkg.applicationInfo.isStaticSharedLibrary()) {
                        renameStaticSharedLibraryPackage(parseResult.pkg);
                    }
                    try {
                        if (errorCode == PackageManager.INSTALL_SUCCEEDED) {
                            scanPackageLI(parseResult.pkg, parseResult.scanFile, parseFlags, scanFlags,
                                    currentTime, null); //最终会调用到这里
                        }
                    } catch (PackageManagerException e) {
                        errorCode = e.error;
                        Slog.w(TAG, "Failed to scan " + parseResult.scanFile + ": " + e.getMessage());
                    }
                } else if (throwable instanceof PackageParser.PackageParserException) {
                    PackageParser.PackageParserException e = (PackageParser.PackageParserException)
                            throwable;
                    errorCode = e.error;
                    Slog.w(TAG, "Failed to parse " + parseResult.scanFile + ": " + e.getMessage());
                } else {
                    throw new IllegalStateException("Unexpected exception occurred while parsing "
                            + parseResult.scanFile, throwable);
                }
     
                // Delete invalid userdata apps
                if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
                        errorCode == PackageManager.INSTALL_FAILED_INVALID_APK) {
                    logCriticalInfo(Log.WARN,
                            "Deleting invalid package at " + parseResult.scanFile);
                    removeCodePathLI(parseResult.scanFile);
                }
            }
            parallelPackageParser.close();
        }
    

    最后会执行到scanPackageLI(),当然有些不同的android api版本,代码是有不同的,但是整体大方向是一致的,那么我们一起来看一下scanPackageLI的执行代码:

    private AndroidPackage scanPackageLI(File scanFile, int parseFlags, int scanFlags,
                long currentTime, UserHandle user) throws PackageManagerException {
            if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanFile);
    
            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
            final ParsedPackage parsedPackage;
            try (PackageParser2 pp = new PackageParser2(mSeparateProcesses, mOnlyCore, mMetrics, null,
                    mPackageParserCallback)) {
                parsedPackage = pp.parsePackage(scanFile, parseFlags, false);//代码1
            } catch (PackageParserException e) {
                throw PackageManagerException.from(e);
            } finally {
                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
            }
    
            // Static shared libraries have synthetic package names
            if (parsedPackage.isStaticSharedLibrary()) {
                renameStaticSharedLibraryPackage(parsedPackage);
            }
    
            return addForInitLI(parsedPackage, parseFlags, scanFlags, currentTime, user);
        }
    

    上面的代码会将主要的操作转交给PackageParser2#parsePackage 进行,具体的逻辑如下:

    parsePackage()函数说明

    code 路径:frameworks/base/core/java/android/content/pm/PackageParser2.java

     public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches)
                throws PackageParserException {
            if (useCaches && mCacher != null) {
                ParsedPackage parsed = mCacher.getCachedResult(packageFile, flags);
                if (parsed != null) {
                    return parsed;
                }
            }
    
            long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
            ParseInput input = mSharedResult.get().reset();
            ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags);  //code 1
            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;
            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;
        }
    

    上面的代码会调用带code 1处,然后在code1处执行的代码就非常简单了,如下所示:

    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);
            }
    }
    

    上面代码中,如果参数是packageFile 是一个目录,就会执行parseClusterPackage(),否则执行parseMonolithicPackage() 来处理。对于关联的多个apk 会放到一个目录下,来查看parseClusterPackage():

    private ParseResult<ParsingPackage> parseClusterPackage(ParseInput input, File packageDir,
                int flags) {
    //获取应用目录的PackageLite 对象,这个对象中分开保存了目录下的核心应用名称以及其他非核心应用的名称
        ParseResult<PackageParser.PackageLite> liteResult =
                ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, 0);
        if (liteResult.isError()) {
            return input.error(liteResult);
        }
    
        final PackageParser.PackageLite lite = liteResult.getResult();
        if (mOnlyCoreApps && !lite.coreApp) {
            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.isolatedSplits && !ArrayUtils.isEmpty(lite.splitNames)) {
            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 {
            //需要AssetManager 对象
            final AssetManager assets = assetLoader.getBaseAssetManager();
            final File baseApk = new File(lite.baseCodePath);
            ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk,
                    lite.codePath, assets, flags);
            if (result.isError()) {
                return input.error(result);
            }
            //对于apk 进行分析,得到Package 对象
            ParsingPackage pkg = result.getResult();
            if (!ArrayUtils.isEmpty(lite.splitNames)) {
                pkg.asSplit(
                        lite.splitNames,
                        lite.splitCodePaths,
                        lite.splitRevisionCodes,
                        splitDependencies
                );
                final int num = lite.splitNames.length;
    
                for (int i = 0; i < num; i++) {
                    final AssetManager splitAssets = assetLoader.getSplitAssetManager(i);
                    parseSplitApk(input, pkg, i, splitAssets, flags);
                }
            }
    
            pkg.setUse32BitAbi(lite.use32bitAbi);
            return input.success(pkg);
        } catch (PackageParserException e) {
            return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                    "Failed to load assets: " + lite.baseCodePath, e);
        } finally {
            IoUtils.closeQuietly(assetLoader);
        }
    }
    

    parseClusterPackage() 方法中先执行parseClusterPackageLite() 方法对目录下的apk 文件进行初步分析,主要是区分出核心应用和非核心应用。核心应用只有一个,非核心应用可以没有或者多个,非核心应用的作用是用来保存资源和代码。
    接下来调用parseBaseApk() 方法对核心应用进行分析,并生成Package 对象,对非核心的应用调用parseSpliteApk() 方法来分析,分析的结果会放到前面的 Package 对象中。
    parseBaseApk() 方法实际上是主要是分析AndroidManifest.xml 文件:

    private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, File apkFile,
            String codePath, AssetManager assets, int flags) {
        final String apkPath = apkFile.getAbsolutePath();
    
        ...
    
        final int cookie = assets.findCookieForPath(apkPath);
        ...
        try (XmlResourceParser parser = assets.openXmlResourceParser(cookie,
                PackageParser.ANDROID_MANIFEST_FILENAME)) { // 1
            final Resources res = new Resources(assets, mDisplayMetrics, null);
    
            ParseResult<ParsingPackage> result = parseBaseApk(input, apkPath, codePath, res,
                    parser, flags);
            ...
            final ParsingPackage pkg = result.getResult();
            ..
            pkg.setVolumeUuid(volumeUuid);
            ...
            return input.success(pkg);
        } catch (Exception e) {
            return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                    "Failed to read manifest from " + apkPath, e);
        }
    }
    

    在代码1处主要分析XmlResourceParser 对象,也就是获得一个 XML 资源解析对象,该对象解析的是 APK 中的 AndroidManifest.xml 文件

    parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
    

    其中这个地方的ANDROID_MANIFEST_FILENAME 为:

    private static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";
    

    继续分析,最终会执行到

    final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError);
    
    private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, String apkPath,
            String codePath, Resources res, XmlResourceParser parser, int flags)
            throws XmlPullParserException, IOException, PackageParserException {
        final String splitName;
        final String pkgName;
    
        ParseResult<Pair<String, String>> packageSplitResult =
                ApkLiteParseUtils.parsePackageSplitNames(input, parser, 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);//1
            if (result.isError()) {
                return result;
            }
    
            return input.success(pkg);
        } finally {
            manifestArray.recycle();
        }
    }
    

    上面的代码1 处会执行parseBaseApkTags 函数。

    private ParseResult<ParsingPackage> parseBaseApkTags(ParseInput input, ParsingPackage pkg,
            TypedArray sa, Resources res, XmlResourceParser parser, int flags)
            throws XmlPullParserException, IOException {
        ...
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG
                || parser.getDepth() > depth)) {
            ...
    
            // TODO(b/135203078): Convert to instance methods to share variables
            // <application> has special logic, so it's handled outside the general method
            if (PackageParser.TAG_APPLICATION.equals(tagName)) {
                if (foundApp) {
                    if (PackageParser.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);//1
                }
            } else {
                result = parseBaseApkTag(tagName, input, pkg, res, parser, flags);//2
            }
    
            if (result.isError()) {
                return input.error(result);
            }
        }
        ...
        convertNewPermissions(pkg);
    
        convertSplitPermissions(pkg);
    
        ...
    
        return input.success(pkg);
    }
    

    到这个地方就找到了我们需要的内容,广播就是在这个里面解析的,其他的内容就暂时不分析了,重点分析代码1 和代码2处的逻辑。

    parseBaseApplication()

    大家看parseBaseApplication中的下面的代码块,大家不难发现,正在解析我们要的receiver属性,解析出来后会讲这个属性存于pkg信息里面进行保存。

    switch (tagName) {
        case "activity":
            isActivity = true;
            // fall-through
        case "receiver":
            ParseResult<ParsedActivity> activityResult =
                    ParsedActivityUtils.parseActivityOrReceiver(mSeparateProcesses, pkg,
                            res, parser, flags, PackageParser.sUseRoundIcon, input);
    
            if (activityResult.isSuccess()) {
                ParsedActivity activity = activityResult.getResult();
                if (isActivity) {
                    hasActivityOrder |= (activity.getOrder() != 0);
                    pkg.addActivity(activity);
                } else {
                    hasReceiverOrder |= (activity.getOrder() != 0);
                    pkg.addReceiver(activity);
                }
            }
    
            result = activityResult;
            break;
        case "service":
            ParseResult<ParsedService> serviceResult =
                    ParsedServiceUtils.parseService(mSeparateProcesses, pkg, res, parser,
                            flags, PackageParser.sUseRoundIcon, input);
            if (serviceResult.isSuccess()) {
                ParsedService service = serviceResult.getResult();
                hasServiceOrder |= (service.getOrder() != 0);
                pkg.addService(service);
            }
    
            result = serviceResult;
            break;
        case "provider":
            ParseResult<ParsedProvider> providerResult =
                    ParsedProviderUtils.parseProvider(mSeparateProcesses, pkg, res, parser,
                            flags, PackageParser.sUseRoundIcon, input);
            if (providerResult.isSuccess()) {
                pkg.addProvider(providerResult.getResult());
            }
    
            result = providerResult;
            break;
        case "activity-alias":
            activityResult = ParsedActivityUtils.parseActivityAlias(pkg, res,
                    parser, PackageParser.sUseRoundIcon, input);
            if (activityResult.isSuccess()) {
                ParsedActivity activity = activityResult.getResult();
                hasActivityOrder |= (activity.getOrder() != 0);
                pkg.addActivity(activity);
            }
    
            result = activityResult;
            break;
        default:
            result = parseBaseAppChildTag(input, tagName, pkg, res, parser, flags);
            break;
    }
    

    receiver的注册如下:

               else if (tagName.equals("receiver")) {
                    Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs,
                            true, false);
                    if (a == null) {
                        mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                        return false;
                    }
                    owner.receivers.add(a);
                } 
    

    到这个地方为止整个广播的静态注册流程我们就分析完了。

    总结

    清单文件的解析过程,一般是由PKMS来完成,触发PKMS的执行的分为两个部分:1)在系统启动的过程中,会启动SystemServer 进程,而SystemServer进程会启动各种服务,这些服务包括PKMS,在启动PKMS的时候就会扫码apk安装路径下面的apk,然后解析AndroidManifest文件,并做持久化存储;

    2)app 安装的过程中,也会触发PKMS对apk进行检测,调用类似的流程解析 AndroidManifest文件。Receiver的解析,都只是整个AndroidManifest解析过程中的一个环节。

    最后

    有需要面试题的朋友可以关注一下哇哇,以上都可以分享!!!

    相关文章

      网友评论

          本文标题:【Android Framework面试题】在清单文件中配置的r

          本文链接:https://www.haomeiwen.com/subject/qlgzvdtx.html