字节码引用检测实践
目的/作用:解决组件之间相互依赖缺失问题(A依赖于B,B修改,A发现引用的B中的【类/方法/字段】不存在,则无法打包)。
一、原理
假设以下场景:
APP版本开发过程中,由于A组件没有业务修改,所以继续使用B组件上个版本的1.0.0版本(版本开发过程中一般只会重新拉取需要修改的仓库,无需修改的仓库会继续使用老版本),但B组件仓有代码修改,所以有了新的1.0.1版本,并修改了相关代码,调整(增、删、改)了类/属性/方法,如下:
请思考:以上场景项目编译是否会有问题?
答:build success!
- APP主仓依赖的是1.0.0版本的A组件仓编译后的AAR文件,这个AAR文件早在1.0.0版本就编好没动,所以A组件没有编译问题;
- APP主依赖的是1.0.1版本的B组件,A依赖的是1.0.0版本的B组件,最终编译会取B组件的高版本1.0.1版本参与APP工程编译,App仓没有使用被调整的(类/属性/方法),也不存在编译问题。
再思考:以上场景项目编译完成后运行过程中是否会有问题?
答:run 失败!
- 在APP运行到A组件调用了B组件中被调整(类/属性/方法)的情况下就会出现运行时崩溃。
- 因为最终参与APP工程编译的是1.0.1版本的B组件,该版本已经调整了(类/属性/方法),所以会出现运行时错误。
二、问题复现:
1、project下
- 一个A module (依赖B中的method或者field或class)
- 一个B module
2、A,B发布到本地maven中。
3、新建app工程,implementation方式导入A、B。然后调查编译和运行状态。
三、调研
1、可行性 --- 不可做-> 替代方案
可行,在apk编译是进行字节码依赖校验( .class 转换成 .dex 之间校验)。
2、改动范围、影响范围
对业务代码无影响,主要是implementation依赖包
3、集成复杂度
- Gradle自定义插件,只需要在主工程中引入。
project A B
待实现:
模块内验证
模块间验证
1、api不改动,正常编译,正常运行
2、B api改动,正常编译,不能正常运行(没加插件)
3、B api改动,无法编译(插件生效)
四、解决方案
1、自己实现:
学习:Gradle自定义插件开发 + Transform API + Java ASM(字节码操作框架)
备注:
- Gradle自定义插件:独立于app项目存在,只需以插件形式引入,松耦合,集成简单。
- Transform API:Gradle Transform 是 Android 官方提供给开发者在项目构建阶段,即由 .class 到 .dex 转换期间修改 .class 文件的一套 API。目前比较经典的应用是字节码插桩、代码注入技术。
- Java ASM: ASM是一个操作Java字节码的类库【ASM provides a simple API for decomposing(将一个整体拆分成多个部分), modifying(修改某一部分的信息), and recomposing(将多个部分重新组织成一个整体) binary Java classes (i.e. ByteCode).】
计划:模块内校验 --> 模块间校验
其中:模块内校验已经实现。
2、在完成【模块内校验】后,转向看【模块间检验】
1、引用的类、方法、属性定位问题:之前我都是在本地的demo,即同一个peoject下的代码,定位相对容易。但是真实场景是通过arr包引用,定位无从下手,暂时无解决办法.
2、直到发现了bytedance开源的一个插件组ByteX(github.com/bytedance/B…)
3、ByteX.refer-check-plugin 开源插件
refer-check-plugin(github.com/bytedance/B…)
功能
检查所有字节码指令,看是否存在以下非法引用:
- 调用了不存在的方法;
- 所调用的方法没有访问权限(比如,调用了其它类的private方法,或在static方法内调用非static方法);
- 访问了不存在的字段;
- 所访问的字段没有访问权限(比如,访问了其它类的private字段,或在static方法内调用非static字段);
1、测试
- 正常 build✅ run✅
Method
- 方法缺失 build✅ run✅
- 方法名修改 build✅ run✅
- 方法返回值类型修改 build✅ run✅
- 方法修饰符修改 build✅ run✅
Method var
- 方法参数数量更改 build✅ run✅
- 方法参数数据类型修改 build✅ run✅
Field
- 属性缺失 build✅ run✅
- 属性名名修改 build✅ run✅
- 属性修饰符修改 build✅ run✅
- 属性数据类型修改 build✅ run✅
Class
- 类缺失 build✅ run✅
- 类名修改 build✅ run✅
- 类修饰符修改 build✅ run✅
最后
至此,字节码校验插件就完工了,大家按需引入app工程即可(具体步骤可以参考:juejin.cn/post/714509… ),
大家共勉!!!