浅谈Android PMS解析APP信息流程

前言

前面我们了解了Zygote的启动流程,知道AMSPMS都是由SystemServer进程启动的,我们都知道PMS主要负责App管理工作,这里我们简单从源码角度分析下PMS是如何解析APP解析的;

源码分析(API 30为例)

我们还是从PackageManagerService.main方法出发;

...
  PackageManagerService m = new PackageManagerService(injector, onlyCore, factoryTest);//这里会构建一个PackageManagerService对象
  ...

我们看下PackageManagerService的构造方法

    private static final File sAppInstallDir =
            new File(Environment.getDataDirectory(), "app");
if (!mOnlyCore) {
                EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
                        SystemClock.uptimeMillis());
                scanDirTracedLI(sAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0,
                        packageParser, executorService);

            }

这里会调用scanDirTracedLI扫描/data/app目录下的文件;

scanDirTracedLI又会调用scanDirLI方法;

    private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime,
            PackageParser2 packageParser, ExecutorService executorService) {
        final File[] files = scanDir.listFiles();
        if (ArrayUtils.isEmpty(files)) {  //1.首先会判断文件夹下是否有文件;
            Log.d(TAG, "No files in app dir " + scanDir);
            return;
        }
		//2.构造一个ParallelPackageParser对象;
        ParallelPackageParser parallelPackageParser =
                new ParallelPackageParser(packageParser, executorService);

        int fileCount = 0;
        for (File file : files) {
			//3.判断文件是否是apk文件 && 不是临时文件
            final boolean isPackage = (isApkFile(file) || file.isDirectory())
                    && !PackageInstallerService.isStageName(file.getName());
            if (!isPackage) {
                continue;
            }
            //4.内部实际上是通过线程池提交任务的方式解析file
            parallelPackageParser.submit(file, parseFlags);
            fileCount++;
        }

        for (; fileCount > 0; fileCount--) {
        	//5.获取结果
            ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();
          	...
            }
        }
    }

可以看到scanDirLI方法主要做了以下几件事:

  1. 过滤出apk文件;
  2. 构建parallelPackageParser对象,并向其提交file文件;
  3. 获取结果做一些其他工作;

那我们接下来就重点关注ParallelPackageParser.submit方法;

   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();
           
                mInterruptedInThread = Thread.currentThread().getName();
            }
        });
    }

可以看到只是往线程池中丢入之前扫描到的apk文件,具体解析工作由parsePackage(scanFile, parseFlags);执行;

我们继续跟进

    protected ParsedPackage parsePackage(File scanFile, int parseFlags)
            throws PackageParser.PackageParserException {
        return mPackageParser.parsePackage(scanFile, parseFlags, true);
    }

又交给了mPackageParser.parsePackage方法,这里mPackageParser在API30上是PackageParser2类;

我们在跟踪PackageParser2.parsePackage方法

    public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches)
            throws PackageParserException {
     	...
        ParseInput input = mSharedResult.get().reset();
        ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags);
        ParsedPackage parsed = (ParsedPackage) result.getResult().hideAsParsed();
        ...
        return parsed;
    }

可以看到最终解析任务又交给了ParsingPackageUtils这个类;

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

任务进一步交由parseClusterPackage方法完成;

 private ParseResult<ParsingPackage> parseClusterPackage(ParseInput input, File packageDir,
            int flags) {
            
            ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk,
                    lite.codePath, assets, flags);
    }

任务进一步交给parseBaseApk方法执行;

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

可以看到parseBaseApk五个参数的方法,会去解析AndroidManifest.xml文件得到XmlResourceParser对象,传给六参方法

parseBaseApk(ParseInput input, String apkPath, String codePath, Resources res, XmlResourceParser parser, int flags)方法

    private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, String apkPath,
            String codePath, Resources res, XmlResourceParser parser, int flags)
            throws XmlPullParserException, IOException, PackageParserException {
   			...
            final ParseResult<ParsingPackage> result =
                    parseBaseApkTags(input, pkg, manifestArray, res, parser, flags);
            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)
            throws XmlPullParserException, IOException {
        //解析得到SharedUserId和SharedUserLabel
        ParseResult<ParsingPackage> sharedUserResult = parseSharedUser(input, pkg, sa);
     	...
        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;
            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;
                    //解析Application标签
                    result = parseBaseApplication(input, pkg, res, parser, flags);
                }
            } else {
                //解析uses-permission、queries等,即非application标签
                result = parseBaseApkTag(tagName, input, pkg, res, parser, flags);
            }

            if (result.isError()) {
                return input.error(result);
            }
        }
		...
        return input.success(pkg);
    }

这里,我们可以看到parseBaseApkTags方法,便是做AndroidManifest.xml具体解析的方法;

  1. 解析得到SharedUserId和SharedUserLabel;
  2. 解析Application标签内容;不用想肯定包含四大组件解析;
  3. 解析uses-permission、queries等,即非application标签;

那我们就重点看parseBaseApplication方法是怎么解析四大组件信息的;

    private ParseResult<ParsingPackage> parseBaseApplication(ParseInput input,
            ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags)
            throws XmlPullParserException, IOException {
        final String pkgName = pkg.getPackageName();
        int targetSdk = pkg.getTargetSdkVersion();

        TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestApplication);
        try {
     		...
            //解析application标签内的一些配置属性,如allowBackup、LargeHeap等
            parseBaseAppBasicFlags(pkg, sa);
			...
            //解析Android任务栈信息
            ParseResult<String> taskAffinityResult = ComponentParseUtils.buildTaskAffinityName(
                    pkgName, pkgName, taskAffinity, input);
            if (taskAffinityResult.isError()) {
                return input.error(taskAffinityResult);
            }

   			...
   			//看着像是处理开进程的情况
            ParseResult<String> processNameResult = ComponentParseUtils.buildProcessName(
                    pkgName, null, pname, flags, mSeparateProcesses, input);
            if (processNameResult.isError()) {
                return input.error(processNameResult);
            }

            String processName = processNameResult.getResult();
            pkg.setProcessName(processName);

       	...
       	//下面表示解析四大组件
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG
                || parser.getDepth() > depth)) {
            if (type != XmlPullParser.START_TAG) {
                continue;
            }

            final ParseResult result;
            String tagName = parser.getName();
            boolean isActivity = false;
            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;
            }

            if (result.isError()) {
                return input.error(result);
            }
        }
        ...
        return input.success(pkg);
    }

至此,完成apk的解析,同时将解析到的信息缓存到ParsingPackage
中,其对应实现类为ParsingPackageImpl,我们简单看下定义的一些关键属性;

public class ParsingPackageImpl implements ParsingPackage, Parcelable {
	//缓存解析到的activity数据
 	 @NonNull
    protected List<ParsedActivity> activities = emptyList();
	//缓存解析到的receivers数据
    @NonNull
    protected List<ParsedActivity> receivers = emptyList();
	//缓存解析到的services数据
    @NonNull
    protected List<ParsedService> services = emptyList();
	//缓存解析到的providers数据
    @NonNull
    protected List<ParsedProvider> providers = emptyList();
	//缓存解析到的permissions数据
    @NonNull
    protected List<ParsedPermission> permissions = emptyList();

}

整体流程图

解析APP信息流程图

结语

如果以上文章对您有一点点帮助,希望您不要吝啬的点个赞加个关注,您每一次小小的举动都是我坚持写作的不懈动力!ღ( ´・ᴗ・` )

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/19726.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

英文论文润色哪家好用比较好,有值得推荐的吗

英文论文润色 推荐 英文论文润色对于写作者来说是一项十分重要的任务&#xff0c;它可以帮助我们修改文章中的语法、标点和排版等问题&#xff0c;使论文更加准确和易读。在众多的英文润色软件中&#xff0c;147chatgpt改写润色软件是一款值得推荐的全自动批量图文润色、自动纠…

财报解读:Q2业绩指引未达预期,狂奔的爱彼迎要减速了?

全球民宿龙头爱彼迎Airbnb迎来了一个强劲的开端。 美东时间5月9日盘后&#xff0c;爱彼迎发布了2023年第一季度财报。财报显示&#xff0c;爱彼迎一季度营收、净利润、总预订金额都获得了不同程度增长&#xff0c;超出市场预期。美中不足的是&#xff0c;公司预计二季度营收下…

对接银行处理退票的解决方案

什么是退票&#xff1f; 在跨行支付时&#xff0c;付款请求提交汇出行后&#xff0c;由汇出行转交至人民银行支付系统&#xff0c;经人民银行大小额系统处理后会先返回交易成功的结果&#xff0c;再由人民银行转至收款行&#xff0c;收款行在清算过程中会将收款人账户信息、状…

战略投资奥琦玮,微盟冲在餐饮复苏最前线

作者 | 辰纹 来源 | 洞见新研社 好起来了&#xff0c;一切都好起来了。 刚刚过去的五一假期&#xff0c;广州费大厨正佳广场店每天取号1000多桌&#xff0c;餐厅翻台率达到了1200%&#xff1b;长沙文和友单日最高排号超过1万&#xff0c;到店人数近6万&#xff1b;武汉主力龙…

【无标题】c++异常机制的一些总结以及思考

在谈及c处理异常机制的方法之前我们不妨来回顾一下c语言是如何应对这块的。 终止程序&#xff0c;如assert&#xff0c;缺陷&#xff1a;用户难以接受。如发生内存错误&#xff0c;除0错误时就会终止程序。 返回错误码&#xff0c;缺陷&#xff1a;需要程序员自己去查找对应的…

输入url后,到页面展示出来

目录 1、用户在浏览器中输入url地址 2、缓存解析 3、浏览器进行DNS解析域名得到服务器ip地址 4、TCP三次握手建立客户端和服务器的连接 5、客户端发送HTTP请求获取服务器端的静态资源 6、服务器发送HTTP响应报文给客户端&#xff0c;客户端获取到页面静态资源 7、TCP四次…

快速了解 TypeScript

目录 1、简介 2、安装TypeScript 3、编译代码 4、类型注解 5、接口 6、类 7、运行TypeScript Web应用 1、简介 TypeScript是JavaScript类型的超集&#xff0c;它可以编译成纯JavaScript。 TypeScript可以在任何浏览器、任何计算机和任何操作系统上运行&#xff0c;并且…

LeetCode_Day2 | 有意思的数组滑动窗口及螺旋矩阵

LeetCode_数组 977.有序数组的平方1.题目描述2.暴力法3. 双指针法 209.长度最小的子数组1.题目描述2.暴力法3.滑动窗口(双指针法) 59.螺旋矩阵1.题目描述2. 螺旋矩阵解法 977.有序数组的平方 1.题目描述 给你一个按 非递减顺序 排序的整数数组 nums&#xff0c;返回 每个数字…

推荐6个我经常逛的“小网站”,嘿嘿嘿!!!

如今&#xff0c;全球互联网上已经有超过 17 亿个网站。除了全球那些主流网站被大家所熟知外&#xff0c;其实还有很多很多网站&#xff0c;被淹没在了互联网世界中。 每次发现优质的内容都会第一时间给大家分享出来&#xff0c;不管是软件&#xff0c;插件&#xff0c;脚本还…

为什么要做计划跟踪:没有计划,就没有控制

日常工作中&#xff0c;我们每天都被大量的信息和任务填满&#xff0c;常常由于任务繁冗复杂&#xff0c;让人陷入一种无所适从的状态。 我们经常会看到很多如何安排工作计划的教程&#xff0c;比如&#xff1a; 要把大的项目分解为小目标&#xff0c;小目目标再分解为日常任务…

【iOS】—— 实现WebSocket发送消息(SocketRocket第三方库的使用和解析)

文章目录 WebSocketWebSocket特点 SocketRocket导入头文件设置代理SRWebSocket的初始化和建立连接SRWebSocketDelegate 代理方法实现加上简单UI实现两个用户之间简单通信浅看了一点点源码&#xff08;理解的不深&#xff09; 偶然之间了解到了利用WebSocket实现后端和前端的相互…

获取两个日期间时长 (XX天XX时XX分)

使用场景&#xff1a; 发货日期与到货日期 计算运输时长 代码&#xff1a; private String getMinuteTime(String startTime, String endTime) {String minuteTime null;if (StrUtil.isNotBlank(startTime) && StrUtil.isNotBlank(endTime)) {long minute DateUti…

华为OD机试真题 Java 实现【猜字谜】【2023Q1 100分】

一、题目描述 小王设计了一人简单的清字谈游戏&#xff0c;游戏的迷面是一人错误的单词&#xff0c;比如nesw&#xff0c;玩家需要猜出谈底库中正确的单词。猜中的要求如 对于某个谜面和谜底单词&#xff0c;满足下面任一条件都表示猜中&#xff1a; 变换顺序以后一样的&…

寅家科技完成近亿元B1轮融资,加速高阶智能驾驶布局

近日&#xff0c;寅家科技宣布完成近亿元人民币B1轮融资&#xff0c;本轮融资由东方富海、深创投、深圳高新投联合投资&#xff0c;所募资金主要用于公司高阶智能驾驶技术产品的研发迭代&#xff0c;以及智能驾驶产品量产、海外市场开拓&#xff0c;从而进一步提升核心产品的市…

【重新定义matlab强大系列三】MATLAB清洗离群数据(查找、填充或删除离群值)

&#x1f517; 运行环境&#xff1a;matlab &#x1f6a9; 撰写作者&#xff1a;左手の明天 &#x1f947; 精选专栏&#xff1a;《python》 &#x1f525; 推荐专栏&#xff1a;《算法研究》 #### 防伪水印——左手の明天 #### &#x1f497; 大家好&#x1f917;&#x1f91…

应届生如何在职场中提高竞争力?这些方法和策略不容错过!

当前就业形势严峻&#xff0c;对于即将步入职场的应届生来说&#xff0c;提高自己的竞争力显得尤为重要。那么&#xff0c;要如何提高自己的职场竞争力呢&#xff1f;本文将为你分享一些有效的方法和策略&#xff0c;帮助你在职场中获得更好的发展。 一、提高自身素质 职场中&…

关于ADC的笔记1

ADC&#xff0c;全称Anlog-to-Digital Converter&#xff0c;模拟/数字转换器。是指将连续变量的模拟信号转换为离散的数字信号的器件&#xff0c;我们能通过ADC将外界的电压值读入我们的单片机中. 常见的ADC有两种 1.并联比较型&#xff1a; 它的优点是转换速度最快&#x…

VMware 产品下载汇总 2023 持续更新中

本站 VMware 产品下载汇总&#xff1a;vSphere、NSX、Tanzu、Aria、Cloud… 请访问原文链接&#xff1a;https://sysin.org/blog/vmware/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org 本站提供的 VMware 软件全部为 “试用版…

数据分析04——Pandas简介/Series对象/DataFrame对象

1、Pandas简介&#xff1a; Pandas是基于NumPy开发的数据分析三大剑客之一&#xff0c;Python数据分析的核心库提供快速、灵活、明确的数据结构Series对象&#xff1a;一维数组结构&#xff0c;由index和value构成DataFrame对象&#xff1a;二维数组结构&#xff0c;由index、…

106.(cesium篇)cesium椎体旋转

听老人家说:多看美女会长寿 地图之家总目录(订阅之前建议先查看该博客) 文章末尾处提供保证可运行完整代码包,运行如有问题,可“私信”博主。 效果如下所示: 下面献上完整代码,代码重要位置会做相应解释 <html lang="en"> <
最新文章