Matrix源码分析系列-如何解析应用安装包(二)

简介: Matrix源码分析系列-如何解析应用安装包

初始化完成后,就直接到了任务的执行,继续往下看

//方法有些长,我们一行一行往下看
    @Override
    public TaskResult call() throws TaskExecuteException {
        try {
            //根据任务类型,结果类型,获取对应的 TaskResult对象,最终是TaskJsonResult或者TaskHtmlResult
            //这里默认
            TaskResult taskResult = TaskResultFactory.factory(getType(), TASK_RESULT_TYPE_JSON, config);
            if (taskResult == null) {
                return null;
            }
            //记录开始时间
            long startTime = System.currentTimeMillis();
            JsonArray jsonArray = new JsonArray();
            //这里就开始循环初始化时加入的dex文件列表,也就是RandomAccessFile随机访问对象
            for (int i = 0; i < dexFileList.size(); i++) {
                RandomAccessFile dexFile = dexFileList.get(i);
                //详见下面函数分析
                countDex(dexFile);
                //关闭文件流,防止泄漏
                dexFile.close();
                //计算内部方法数
                int totalInternalMethods = sumOfValue(classInternalMethod);
                //计算外部方法数
                int totalExternalMethods = sumOfValue(classExternalMethod);
                JsonObject jsonObject = new JsonObject();
                jsonObject.addProperty("dex-file", dexFileNameList.get(i));
        //按配置进行分组
                if (JobConstants.GROUP_CLASS.equals(group)) {
                    List<String> sortList = sortKeyByValue(classInternalMethod);
                    JsonArray classes = new JsonArray();
                    for (String className : sortList) {
                        JsonObject classObj = new JsonObject();
                        classObj.addProperty("name", className);
                        classObj.addProperty("methods", classInternalMethod.get(className));
                        classes.add(classObj);
                    }
                    jsonObject.add("internal-classes", classes);
                } else if (JobConstants.GROUP_PACKAGE.equals(group)) {
                    String packageName;
                    for (Map.Entry<String, Integer> entry : classInternalMethod.entrySet()) {
                        packageName = ApkUtil.getPackageName(entry.getKey());
                        if (!Util.isNullOrNil(packageName)) {
                            if (!pkgInternalRefMethod.containsKey(packageName)) {
                                pkgInternalRefMethod.put(packageName, entry.getValue());
                            } else {
                                pkgInternalRefMethod.put(packageName, pkgInternalRefMethod.get(packageName) + entry.getValue());
                            }
                        }
                    }
                    List<String> sortList = sortKeyByValue(pkgInternalRefMethod);
                    JsonArray packages = new JsonArray();
                    for (String pkgName : sortList) {
                        JsonObject pkgObj = new JsonObject();
                        pkgObj.addProperty("name", pkgName);
                        pkgObj.addProperty("methods", pkgInternalRefMethod.get(pkgName));
                        packages.add(pkgObj);
                    }
                    jsonObject.add("internal-packages", packages);
                }
                jsonObject.addProperty("total-internal-classes", classInternalMethod.size());
                jsonObject.addProperty("total-internal-methods", totalInternalMethods);
                if (JobConstants.GROUP_CLASS.equals(group)) {
                    List<String> sortList = sortKeyByValue(classExternalMethod);
                    JsonArray classes = new JsonArray();
                    for (String className : sortList) {
                        JsonObject classObj = new JsonObject();
                        classObj.addProperty("name", className);
                        classObj.addProperty("methods", classExternalMethod.get(className));
                        classes.add(classObj);
                    }
                    jsonObject.add("external-classes", classes);
                } else if (JobConstants.GROUP_PACKAGE.equals(group)) {
                    String packageName = "";
                    for (Map.Entry<String, Integer> entry : classExternalMethod.entrySet()) {
                        packageName = ApkUtil.getPackageName(entry.getKey());
                        if (!Util.isNullOrNil(packageName)) {
                            if (!pkgExternalMethod.containsKey(packageName)) {
                                pkgExternalMethod.put(packageName, entry.getValue());
                            } else {
                                pkgExternalMethod.put(packageName, pkgExternalMethod.get(packageName) + entry.getValue());
                            }
                        }
                    }
                    List<String> sortList = sortKeyByValue(pkgExternalMethod);
                    JsonArray packages = new JsonArray();
                    for (String pkgName : sortList) {
                        JsonObject pkgObj = new JsonObject();
                        pkgObj.addProperty("name", pkgName);
                        pkgObj.addProperty("methods", pkgExternalMethod.get(pkgName));
                        packages.add(pkgObj);
                    }
                    jsonObject.add("external-packages", packages);
                }
                jsonObject.addProperty("total-external-classes", classExternalMethod.size());
                jsonObject.addProperty("total-external-methods", totalExternalMethods);
                jsonArray.add(jsonObject);
            }
            ((TaskJsonResult) taskResult).add("dex-files", jsonArray);
            taskResult.setStartTime(startTime);
            taskResult.setEndTime(System.currentTimeMillis());
            //返回结果
            return taskResult;
        } catch (Exception e) {
            throw new TaskExecuteException(e.getMessage(), e);
        }
    }

countDex(dexFile) 函数分析

private void countDex(RandomAccessFile dexFile) throws IOException {
      //按类分组的,内部方法Map,清除掉缓存
        classInternalMethod.clear();
      //按类分组的,外部依赖方法Map,清除掉缓存
        classExternalMethod.clear();
      //按包分组的,同上
        pkgInternalRefMethod.clear();
        pkgExternalMethod.clear();
      //使用的com.android.dexdeps 包下的DexData类,
        DexData dexData = new DexData(dexFile);
        dexData.load();
      //获取所有方法索引对象,包括内部方法和外部索引的方法
        MethodRef[] methodRefs = dexData.getMethodRefs();
      //获取所有外部类的索引数据
        ClassRef[] externalClassRefs = dexData.getExternalReferences();
      //获取混淆过的类
        Map<String, String> proguardClassMap = config.getProguardClassMap();
        String className = null;
        for (ClassRef classRef : externalClassRefs) {
            //获取类名
            className = ApkUtil.getNormalClassName(classRef.getName());
            if (proguardClassMap.containsKey(className)) {
                //匹配并赋值为混淆前的类名
                className = proguardClassMap.get(className);
            }
            if (className.indexOf('.') == -1) {
                continue;
            }
            //将类名放入到外部方法Map中,供下面匹配外部方法时使用
            classExternalMethod.put(className, 0);
        }
        //遍历所有方法,找到外部和内部方法,并分别加入到classExternalMethod、classInternalMethod Map中
        for (MethodRef methodRef : methodRefs) {
            //获取该方法的类名
            className = ApkUtil.getNormalClassName(methodRef.getDeclClassName());
            //匹配混淆前的类名
            if (proguardClassMap.containsKey(className)) {
                className = proguardClassMap.get(className);
            }
            if (!Util.isNullOrNil(className)) {
                if (className.indexOf('.') == -1) {
                    continue;
                }
                //根据类名和外部方法存储的类信息匹配,匹配上就说明是外部方法引用。
                if (classExternalMethod.containsKey(className)) {
                    classExternalMethod.put(className, classExternalMethod.get(className) + 1);
                } else if (classInternalMethod.containsKey(className)) {
                    classInternalMethod.put(className, classInternalMethod.get(className) + 1);
                } else {
                    classInternalMethod.put(className, 1);
                }
            }
        }
      //删除没有方法引用的类
        Iterator<String> iterator = classExternalMethod.keySet().iterator();
        while (iterator.hasNext()) {
            if (classExternalMethod.get(iterator.next()) == 0) {
                iterator.remove();
            }
        }
    }

通过源码的分析,其实原理就是使用DexData对象加载dexFile,最后getMethodRefs、getExternalReferences方法获取相关信息,最后通过外部的classExternalMethod 将所有的方法分类成内部方法和外部方法集合。

CountClassTask

经过上面的分析经验,我们再分析CountClassTask就简单了许多,直接找到核心代码如下:

DexData dexData = new DexData(dexFile);
dexData.load();
dexFile.close();
ClassRef[] defClassRefs = dexData.getInternalReferences();
Set<String> classNameSet = new HashSet<>();
for (ClassRef classRef : defClassRefs) {
    String className = ApkUtil.getNormalClassName(classRef.getName());
    if (classProguardMap.containsKey(className)) {
        className = classProguardMap.get(className);
    }
    if (className.indexOf('.') == -1) {
        continue;
    }
    classNameSet.add(className);
}

上面使用了getExternalReferences,而这里直接使用getInternalReferences,获取所有内部类的索引数据,最终加入到一个HashSet中。整个过程简单明了。难道所有的Task的实现都是用到DexData对象处理的吗?并不是,我们来看下一个

UnusedAssetsTask

看类名,简单解释为未被使用的资源任务,其实就是找到assets文件夹下没有被依赖的资源,知道该任务的目的后,我们就来看看,它是如何实现的,先看下init中都做了什么准备

//初始化方法
  @Override
    public void init() throws TaskInitException {
        super.init();
    //先拿到解压后的apk文件路径
        String inputPath = config.getUnzipPath();
        if (Util.isNullOrNil(inputPath)) {
            throw new TaskInitException(TAG + "---APK-UNZIP-PATH can not be null!");
        }
        inputFile = new File(inputPath);
        //同样的检测文件是否存在和检查文件的属性是否是文件夹
        if (!inputFile.exists()) {
            throw new TaskInitException(TAG + "---APK-UNZIP-PATH '" + inputPath + "' is not exist!");
        } else if (!inputFile.isDirectory()) {
            throw new TaskInitException(TAG + "---APK-UNZIP-PATH '" + inputPath + "' is not directory!");
        }
        //根据配置文件中忽略资源列表,放入到ignoreSet中,为了忽略一些文件的检查,比如确定资源是有用的,就不需要被检查,缩小范围。
        if (params.containsKey(JobConstants.PARAM_IGNORE_ASSETS_LIST) && !Util.isNullOrNil(params.get(JobConstants.PARAM_IGNORE_ASSETS_LIST))) {
            String[] ignoreAssets = params.get(JobConstants.PARAM_IGNORE_ASSETS_LIST).split(",");
            Log.i(TAG, "ignore assets %d", ignoreAssets.length);
            for (String ignore : ignoreAssets) {
                ignoreSet.add(Util.globToRegexp(ignore));
            }
        }
        File[] files = inputFile.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isFile() && file.getName().endsWith(ApkConstants.DEX_FILE_SUFFIX)) {
                    //同样的将dex文件,筛选出来,放入到dexFileNameList中
                    dexFileNameList.add(file.getName());
                }
            }
        }
    }

初始化中,执行了常规的文件检查,params参数的整理,再将dex文件缓存到一个list中,待处理,再来看下call函数,看它如何找到了未被依赖资源。

@Override
    public TaskResult call() throws TaskExecuteException {
        try {
            TaskResult taskResult = TaskResultFactory.factory(type, TaskResultFactory.TASK_RESULT_TYPE_JSON, config);
            long startTime = System.currentTimeMillis();
            //创建assets目录的文件对象
            File assetDir = new File(inputFile, ApkConstants.ASSETS_DIR_NAME);
            //这里递归找到所有的文件,存储到assetsPathSet中,比较简单就不贴代码
            findAssetsFile(assetDir);
            //将忽略的文件,从assetsPathSet中剔除
            generateAssetsSet(assetDir.getAbsolutePath());
            Log.i(TAG, "find all assets count: %d", assetsPathSet.size());
            //核心的实现,解析代码中的assets引用,看下面贴的代码
            decodeCode();
            Log.i(TAG, "find reference assets count: %d", assetRefSet.size());
            assetsPathSet.removeAll(assetRefSet);
            JsonArray jsonArray = new JsonArray();
            for (String name : assetsPathSet) {
                jsonArray.add(name);
            }
            ((TaskJsonResult) taskResult).add("unused-assets", jsonArray);
            taskResult.setStartTime(startTime);
            taskResult.setEndTime(System.currentTimeMillis());
            return taskResult;
        } catch (Exception e) {
            throw new TaskExecuteException(e.getMessage(), e);
        }
    }
  //解析代码中的assets引用
    private void decodeCode() throws IOException {
        for (String dexFileName : dexFileNameList) {
            //这里用到之前 apktool-lib-2.4.0.jar 包中的类,根据dex文件获取DexBackedDexFile对象
            DexBackedDexFile dexFile = DexFileFactory.loadDexFile(new File(inputFile, dexFileName), Opcodes.forApi(15));
            BaksmaliOptions options = new BaksmaliOptions();
            //apktool 的api,拿到排序好的类引用集合
            List<? extends ClassDef> classDefs = Ordering.natural().sortedCopy(dexFile.getClasses());
            for (ClassDef classDef : classDefs) {
                // 按空格将类里面的所有代码组合成一个数组
                String[] lines = ApkUtil.disassembleClass(classDef, options);
                if (lines != null) {
                    //匹配资源文件的引用
                    readSmaliLines(lines);
                }
            }
        }
    }
    private void readSmaliLines(String[] lines) {
        if (lines == null) {
            return;
        }
        for (String line : lines) {
            line = line.trim();
            //找到常量字符
            if (!Util.isNullOrNil(line) && line.startsWith("const-string")) {
                String[] columns = line.split(",");
                if (columns.length == 2) {
                    //拿到常量字符中的资源名字
                    String assetFileName = columns[1].trim();
                    assetFileName = assetFileName.substring(1, assetFileName.length() - 1);
                    if (!Util.isNullOrNil(assetFileName)) {
                        for (String path : assetsPathSet) {
                            //循环匹配,匹配上后就加入到assetRefSet中
                            if (assetFileName.endsWith(path)) {
                                assetRefSet.add(path);
                            }
                        }
                    }
                }
            }
        }
    }

代码分析完,我们已经知道,原理其实就是对类里面的常量字符与assets目录中文件名做了匹配处理,能匹配上说明有被引用到,如果匹配失败,那就是没有被引用。分析到这里,你是不是对其他的Task也有些好奇且有了分析的方法了呢?由于篇幅原因我们就不再详细展开,有问题欢迎评论区提问。

小结


本期就先到这里,后面再补充详细的实战示例,毕竟实践才能出真理,由于时间的原因,后面还有8篇,当然我们肯定会在后续持续更新的,敬请谅解。

目录
打赏
0
0
0
0
22
分享
相关文章
安全监控系统:技术架构与应用解析
该系统采用模块化设计,集成了行为识别、视频监控、人脸识别、危险区域检测、异常事件检测、日志追溯及消息推送等功能,并可选配OCR识别模块。基于深度学习与开源技术栈(如TensorFlow、OpenCV),系统具备高精度、低延迟特点,支持实时分析儿童行为、监测危险区域、识别异常事件,并将结果推送给教师或家长。同时兼容主流硬件,支持本地化推理与分布式处理,确保可靠性与扩展性,为幼儿园安全管理提供全面解决方案。
162 3
HarmonyOS Next~鸿蒙应用框架开发实战:Ability Kit与Accessibility Kit深度解析
本书深入解析HarmonyOS应用框架开发,聚焦Ability Kit与Accessibility Kit两大核心组件。Ability Kit通过FA/PA双引擎架构实现跨设备协同,支持分布式能力开发;Accessibility Kit提供无障碍服务构建方案,优化用户体验。内容涵盖设计理念、实践案例、调试优化及未来演进方向,助力开发者打造高效、包容的分布式应用,体现HarmonyOS生态价值。
193 27
深入探索 BPMN、CMMN 和 DMN:从定义到应用的全方位解析
在当今快速变化的商业环境中,对象管理组织(OMG)推出了三种强大的建模标准:BPMN(业务流程模型和符号)、CMMN(案例管理模型和符号)和DMN(决策模型和符号)。它们分别适用于结构化流程管理、动态案例处理和规则驱动的决策制定,并能相互协作,覆盖更广泛的业务场景。BPMN通过直观符号绘制固定流程;CMMN灵活管理不确定的案例;DMN以表格形式定义清晰的决策规则。三者结合可优化企业效率与灵活性。 [阅读更多](https://example.com/blog)
深入探索 BPMN、CMMN 和 DMN:从定义到应用的全方位解析
阿里云服务器ECS通用型规格族解析:实例规格、性能基准与场景化应用指南
作为ECS产品矩阵中的核心序列,通用型规格族以均衡的计算、内存、网络和存储性能著称,覆盖从基础应用到高性能计算的广泛场景。通用型规格族属于独享型云服务器,实例采用固定CPU调度模式,实例的每个CPU绑定到一个物理CPU超线程,实例间无CPU资源争抢,实例计算性能稳定且有严格的SLA保证,在性能上会更加稳定,高负载情况下也不会出现资源争夺现象。本文将深度解析阿里云ECS通用型规格族的技术架构、实例规格特性、最新价格政策及典型应用场景,为云计算选型提供参考。
可穿戴设备如何重塑医疗健康:技术解析与应用实战
可穿戴设备如何重塑医疗健康:技术解析与应用实战
156 4
DeepSeek大模型在客服系统中的应用场景解析
在数字化浪潮下,客户服务领域正经历深刻变革,AI技术成为提升服务效能与体验的关键。DeepSeek大模型凭借自然语言处理、语音交互及多模态技术,显著优化客服流程,提升用户满意度。它通过智能问答、多轮对话引导、多模态语音客服和情绪监测等功能,革新服务模式,实现高效应答与精准分析,推动人机协作,为企业和客户创造更大价值。
437 5
淘宝拍立淘按图搜索API接口系列的应用与数据解析
淘宝拍立淘按图搜索API接口是阿里巴巴旗下淘宝平台提供的一项基于图像识别技术的创新服务。以下是对该接口系列的应用与数据解析的详细分析
DeepSeek 实践应用解析:合力亿捷智能客服迈向 “真智能” 时代
DeepSeek作为人工智能领域的创新翘楚,凭借领先的技术实力,在智能客服领域掀起变革。通过全渠道智能辅助、精准对话管理、多语言交互、智能工单处理、个性化推荐、情绪分析及反馈监控等功能,大幅提升客户服务效率和质量,助力企业实现卓越升级,推动智能化服务发展。
193 1
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
淘宝商品评论API接口系列的应用与数据解析
在电商平台中,用户评论是了解商品质量、服务水平和用户满意度的重要数据来源。淘宝作为中国最大的电商平台,提供了商品评论API接口,帮助开发者获取和分析用户评价数据。本文将介绍淘宝商品评论API接口系列的作用、使用方法,并通过示例展示如何调用API并解析返回的JSON数据。

热门文章

最新文章

推荐镜像

更多
  • DNS
  • AI助理

    你好,我是AI助理

    可以解答问题、推荐解决方案等

    登录插画

    登录以查看您的控制台资源

    管理云资源
    状态一览
    快捷访问