字节码技术在模块依赖分析中的应用

背景

近年来,随着手机业务的快速发展,为满足手机端用户诉求和业务功能的迅速增长,移动端的技术架构也从单一的大工程应用,逐步向模块化、组件化方向发展。以高德地图为例,Android 端的代码已突破百万行级别,超过100个模块参与最终构建。

试想一下,如果没有一套标准的依赖检测和监控工具,用不了多久,模块的依赖关系就可能会乱成一锅粥。

从模块 Owner 的角度看,为什么依赖分析这么重要?

1.作为模块 Owner,我首先想知道“谁依赖了我?依赖了哪些接口”。唯有如此才能评估本模块改动的影响范围,以及暴露的接口的合理性。

2.我还想知道“我依赖了谁?调用了哪些外部接口”,对所需要的外部能力做到心中有数。

从全局视角看,一个健康的依赖结构,要防止“下层模块”直接依赖“上层模块”,更要杜绝循环依赖。通过分析全局的依赖关系,可以快速定位不合理的依赖,提前暴露业务问题。

因此,依赖分析是研发过程中非常重要的一环。

常见的依赖分析方式

提到 Android 依赖分析,首先浮现在脑海中的可能是以下这些方案:

  • 分析 Gradle 依赖树。
  • 扫描代码中的 import 声明。
  • 使用 Android Studio 自带的分析功能。

我们逐个来分析这几个方案:

1. Gradle 依赖树

使用 ./gradlew :<module>:dependencies --configuration releaseCompileClasspath -q 命令,很容易就可以得到模块的依赖树,如图:

不难发现,这种方式有两个问题:

  • 声明即依赖,即使代码中没有使用的库,也会输出到结果中。
  • 只能分析到模块级别,无法精确到方法级别。

2. 扫描 import 声明

扫描 Java 文件中的 import 语句,可以得到文件(类)之间的调用关系。

因为模块与文件(类)的对应关系非常容易得到(扫描目录)。所以,得到了文件(类)之间的依赖关系,即是得到了模块之间文件(类)级别的依赖关系。

这个方案相比 Gradle 依赖扫描提升了结果维度,可以分析到文件(类)级别。但是它也存在一些缺点:

  • 无法处理 import * 的情况。
  • 扫描“有 import 但未使用对应类”的场景效率太低(需要做源码字符串查找)。

3. 使用 IDE 自带的分析功能

触发 Android Studio 菜单 「Analyze」 -> 「Analyze Dependencies」,可以得到模块间方法级别的依赖关系数据。如图:

Android Studio 能准确分析到模块之间“方法级别”的引用关系,支持在 IDE 中跳转查看,也能扫描到对 Android SDK 的引用。

这个方案比前面两个都优秀,主要是准确。但是它也有几个问题:

  • 耗时较长:全面分析 AMap 全源码,大约需要 10 分钟。
  • 分析结果无法为第三方复用,无法生成可视化的依赖关系图。
  • 分析正向依赖和逆向依赖,需要扫描两次。

总结一下上述三种方案:Gralde 依赖基于工程配置,粒度太粗且结果不准。“Import 扫描方案”能拿到文件级别依赖但数据不全。IDE 扫描虽然结果精准,但是数据复用困难,不便于工程化。

为什么要使用字节码来分析?

参考 Android 构建流程图,所有的 Java 源代码和 aapt 生成的 R.java 文件,都会被编译成 .class 文件,再被编译为 dex 文件,最终通过 apkbuilder 生成到 apk 文件中。图中的 .class 文件即是我们所说的 Java 字节码,它是对 Java 源码的二进制转义。

在 Android 端,常见的字节码应用场景包括:

  • 字节码插桩:用于实现对 UI 、内存、网络等模块的性能监控。
  • 修改 jar 包:针对无源码的库,通过编辑字节码来实现一些简单的逻辑修改。

回到本文的主题,为什么要分析字节码,而不是 Java 代码或者 dex 文件?

不使用 Java 代码是因为有些库以 jar 或者 aar 的方式提供,我们获取不到源码。不使用 dex 文件是因为它没有好用的语法分析工具。所以解析字节码几乎是我们唯一的选择。

如何使用字节码分析依赖关系?

要得到模块之间的依赖关系,其实就是要得到“模块间类与类”之间的依赖关系。而要确定类之间的关系,分析类字节码的语句即可。

1. 在什么时机来分析?

了解 Android 构建流程的同学,应该对 transform 这个任务不陌生。它是 Android Gradle 插件提供的一个字节码 Hook 入口。

在 transform 这个任务中,所有的字节码文件(包括三方库) 以 Input 的格式输入。

以JarInput 为例,分析其 file 字段,可得到模块的名称。解析 file 文件,即可得到此模块所有的字节码文件。

有了模块名称和对应路径下的 class 文件,就建立了模块与类的对应关系,这是我们拿到的第一个关键数据。

2. 使用什么工具分析?

解析 Java 字节码的工具,最常用的包括 Javassit,ASM,CGLib。ASM 是一个轻量级的类库,性能较好,但需要直接操作 JVM 指令。CGLib 是对 ASM 的封装,提供了更高级的接口。

相比而言,Javassist 要简单的多,它基于 Java 的 API ,无需操作 JVM 指令,但其性能要差一些(因为 Javassit 增加了一层抽象)。在工程原型阶段,为了快速验证结果,我们优先选择了 Javassit 。

3. 具体方案是怎样的?

先看一个简单的示例,如何分析下面这段代码的调用关系:

1: package com.account;
2: import com.account.B;
3: public class A {
4:     void methodA() {
5:         B b = new B(); // 初始化了 Class B 的实例 b
6:         b.methodB();   // 调用了 b 的 methodB 方法
7:     }
8: }

第1步:初始化环境,加载字节码 A.class,注册语句分析器。

// 初始化 ClassPool,将字节码文件目录注册到 Pool 中。
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath('<class文件所在目录>')
// 加载类A
CtClass cls = pool.get("com.account.A");
// 注册表达式分析器到类A
MyExprEditor editor = new MyExprEditor(ctCls)
ctCls.instrument(editor)

第2步:自定义表达式解析器,分析类A(以解析语句调用为例)。

class MyExprEditor extends ExprEditor {

@Override
void edit(MethodCall m) {// 语句所在类的名称def clsAName = ctCls.name// 语句在哪个方法被调用def where = m.where().methodInfo.getName()// 语句在哪一行被调用def line = m.lineNumber// 被调用类的名称def clsBName = m.className// 被调用的方法def methodBName = m.methodName
}
// 省略其它解析函数 ...

}

ExprEditor 的 edit(MethodCall m) 回调能拦截 Class A 中所有的方法调用(MethodCall)。

除了本例中对 MethodCall 的解析,它还支持解析 new,new Array,ConstructorCall,FieldAccess,InstanceOf,强制类型转换,try-catch 语句。

解析完 Class A,我们得到了 A 对 B 的依赖信息 :


Class1Class2Exprmethod1method2lineNo
com.account.Acom.account.BNewExprmethodA 5
com.account.Acom.account.BmethodCallmethodAmethodB6

------------------- -------------------------------------------------------
简单解释如下:
类 com.account.A 的第5行(methodA方法内),调用了 com.account.B 的构造函数;
类 com.account.A 的第6行(methodA方法内),调用了 com.account.B 的 methodB 函数;
这便是“类和类之间方法级”的依赖数据。结合第1步得到的“模块和类”的对应关系,最终我们便获得了“模块间方法级的依赖数据”。

基于这些基础数据,我们还可以自定义依赖检测规则、生成全局的模块依赖关系图等,本文就不展开了。

小结

本文主要介绍了模块依赖分析在研发过程中的重要性,分析了 Android 常见的依赖分析方案,从 Gradle 依赖树分析, Import 扫描,使用 IDE 分析,到最后的字节码解析,方案逐步递进。越是接近源头的解法,才是越根本的解法。


原文链接
本文为云栖社区原创内容,未经允许不得转载。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

华为发布基于自进化AI的HiSec Insight安全态势感知系统

2020年4月21日&#xff0c;华为举办“安全新视界&#xff0c;AI知未然”主题线上发布会&#xff0c;邀请第三方研究机构、行业客户和合作伙伴共同探讨安全态势感知系统的演进方向&#xff0c;并见证华为HiSec Insight安全态势感知系统的全新面世。华为HiSec Insight安全态势感知…

“做好大数据测试,我是认真的!”

阿里妹导读&#xff1a;大数据已然是当下的重要课题&#xff0c;大大小小的企业在重视大数据的同时&#xff0c;也渐渐重视大数据质量的问题。阿里巴巴测试开发专家小郅&#xff0c;今天会分享他对数据测试的系统性思考。文章内容架构清晰&#xff0c;内容较长&#xff0c;建议…

从安全到镜像流水线,Docker 最佳实践与反模式一览

作者 | Timothy Mugayi译者 | 弯月&#xff0c;责编 | 夕颜封图 | CSDN付费下载自视觉中国出品 | CSDN&#xff08;ID:CSDNnews&#xff09;在使用Docker的大部分时间里&#xff0c;我们并不关心其内部的工作原理。仅凭启动一个Docker容器并且让应用程序运行良好&#xff0c;并…

ChaosBlade 发布对 C++ 应用混沌实验的支持

前言 为满足 C 应用系统故障演练&#xff0c;阿里妈妈安全生产团队开源了 C 混沌实验执行器&#xff0c;填补了 C 应用混沌工程实验的空白&#xff0c;其遵循《混沌实验模型》&#xff0c;可通过 ChaosBlade 工具直接执行。项目详情点击这里&#xff01; 。 本文重点介绍该执…

TortoiseGitPlink提示输入密码解决方法

文章目录一、现象二 、解决方法2.1. 打开TortoiseGit 下的puttygen工具2.2. 点击load&#xff0c;加载私钥2.3. 生成一个新的私钥2.4. 项目拉取2.5. 配置新的私钥一、现象 二 、解决方法 2.1. 打开TortoiseGit 下的puttygen工具 双击D:\software\TortoiseGit\bin下面的puttyg…

引领高并发直播场景进入毫秒时代,阿里云发布超低延时直播服务

近日&#xff0c;阿里云上线超低延时直播服务RTS&#xff08;Real-time Streaming&#xff09;&#xff0c;该服务在视频直播产品的基础上&#xff0c;进行全链路延时监控、传输协议改造等底层技术优化&#xff0c;支持千万级并发场景下的毫秒级延迟直播能力&#xff0c;保障低…

JavaScript-面向对象 class 继承

class继承 class 关键字是在ES6引入的 ES6之前的写法&#xff1a; function Student(name) {this.name name } // 给Student新增一个方法 Student.prototype.hello function () {alert(Hello) }ES6的写法&#xff1a; // 定义一个 学生的 类 class Student1{constructor(…

我26岁,月薪一万,刚实现“黄焖鸡自由”(苦笑)

今天是CSDN微信公众号千万粉丝达成的日子&#xff0c;因此&#xff0c;“千万粉丝狂欢节”来了&#xff01;第一弹超值福利来袭&#xff0c;前方高能&#xff1a;「粉丝节限定版一卡通」重磅上线&#xff01;可看该大牛老师全部课程&#xff01;课程涵盖热门的Java、Python和AI…

Android侧滑原来可以这么优雅

前言 侧滑手势在Android App应用得非常广泛&#xff0c;常见的使用场景包括&#xff1a;滑动抽屉、侧滑删除、侧滑返回、下拉刷新以及侧滑封面等。由于这些使用场景实在是太通用了&#xff0c;各路大神们八仙过海各显神通&#xff0c;每种侧滑场景都开源出了很多非常实用的框架…

TortoiseGit状态图标不能正常显示的解决办法

文章目录一. 运行环境一、方案11.1. 右键点击桌面空白处&#xff0c;打开TortoiseGit的Settings1.2. 修改Icon Overlays的Status cache1.3. 重启电脑&#xff0c;你就会发现你的小乌龟箭头出来了。二、方案2一. 运行环境 版本说明Windows 10 64bit操作系统TortoiseGit-2.12.0.…

平头哥广发英雄帖,公开首款CPU“玄铁”仿真代码

“在自研芯片的路上&#xff0c;阿里走出了万里长征的第一步。” 7月25日&#xff0c;阿里云峰会上海站&#xff0c;压轴出场的平头哥&#xff0c;交出了时隔10月的首份答卷&#xff1a;玄铁910&#xff08;XuanTie910&#xff09;&#xff0c;目前业界性能最强的RISC-V处理器…

JavaScript-操作BOM对象

BOM&#xff08;Browser Object Model&#xff09;: 浏览器对象模型 浏览器介绍 JavaScript 和 浏览器关系&#xff1f; JavaScript 诞生就是为了能够让他在浏览器中运行&#xff01; 内核&#xff1a; IE 6~11ChromeSafariFireFox 第三方浏览器&#xff08;可以换上面的内核…

一文带你认识keepalived,再带你通关LVS+Keepalived!

来源 | 故事凌责编 | Carol封图 | CSDN 下载于视觉中国昨天我们讲到《那些你不知道的 LVS 秘密》&#xff0c;今天我们就继续一起来进行 LVS 更深的探索&#xff0c;来一起通关 LVSKeepalived吧&#xff01;keepalivedkeepalive的学习参考网站&#xff1a;https://www.keepali…

手把手教你D2C,走向前端智能化

这几年来 AI 来势汹汹&#xff0c;在部分领域应用也逐渐成熟。前端发展至今&#xff0c;我们也有许多成熟的前端解决方案代码&#xff0c;有大量的设计稿&#xff0c;我们是否能够结合智能化的能力将一个 Design 变成一个 Code &#xff08;设计转代码&#xff0c;简称 D2C&…

TortoiseGit 冲突和解决方案_入门试炼_07

文章目录一、张三操作1. 张三新增hello.java2. 提交本地仓库3. 更新项目4. 将本地仓库变动文件提交远程二、李四操作2.1. 编辑Hello.java2.2. 提交三、张三操作23.1. 在hello.java中添加内容3.2. 提交本地仓库3.3. 更新项目至最新版本3.4. 更新最版本异常&#xff08;冲突&…

如何低成本实现Flutter富文本,看这一篇就够了!

作者&#xff1a;闲鱼技术-玄川 背景 闲鱼是国内最早使用Flutter 的团队&#xff0c;作为一个电商App商品详情页是非常重要场景&#xff0c;其中最主要的技术能力是文字混排。 我们面对文本类的需求是复杂而且多变&#xff0c;然而Flutter历史的几个版本&#xff0c;Text只能…

赋能零售成长型企业营销增长,云徙「数盈·新营销中台」发布

巨石崩裂时&#xff0c;有人看见了恐惧&#xff0c;有人看见了光。 因为有光&#xff0c;万物生长。 疫情给每个企业的影响都是巨大的。但在疫情冲击之下&#xff0c;由中台技术推动企业数字化转型&#xff0c;又给企业带来了希望。 从2016年创业至今&#xff0c;云徙科技的每…

TortoiseGit 下载、安装、配置_入门试炼_01

TortoiseGit 简介: TortoiseGit 简称 tgit&#xff0c; 中文名海龟Git。TortoiseGit是一个开放的GIT版本控制系统的源客户端。 文章目录一、软件下载1.1. 下载安装Git1.2. Tortoisegit二、安装流程2.1. 双击安装程序2.2. 直接点击下一步(Next)2.3. Next2.4. 选择安装目录2.5. 点…

ArchSummit分享 | 高德地图App架构演化与实践

讲师介绍 郝仁杰&#xff0c;高德地图无线开发专家。在7月13日落幕的2019年ArchSummit峰会上就高德地图近几年的App架构演化和实践进行了分享。 背景概述 高德是国内领先的数字地图内容、导航和位置服务解决方案提供商&#xff0c;端上分手机和车机两条主线。近年来&#xf…

工程师的灵魂拷问:你的密钥安全吗?

阿里妹导读&#xff1a;密钥管理是密码学应用的核心问题之一。任何涉及加密/签名的应用&#xff0c;无论算法本身机制多么安全&#xff0c;最终都会受到灵魂拷问&#xff1a;你密钥存在哪儿&#xff1f;本文实现了一种安全的密钥管理方案&#xff0c;基于安全多方计算技术&…