不再烦恼依赖管理:使用Gradle自定义插件进行字节码引用分析

1,769 阅读4分钟

字节码引用检测实践

目的/作用:解决组件之间相互依赖缺失问题(A依赖于B,B修改,A发现引用的B中的【类/方法/字段】不存在,则无法打包)。

一、原理

假设以下场景:

APP版本开发过程中,由于A组件没有业务修改,所以继续使用B组件上个版本的1.0.0版本(版本开发过程中一般只会重新拉取需要修改的仓库,无需修改的仓库会继续使用老版本),但B组件仓有代码修改,所以有了新的1.0.1版本,并修改了相关代码,调整(增、删、改)了类/属性/方法,如下:

image-20220930152732737.png

请思考:以上场景项目编译是否会有问题?

答: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仓没有使用被调整的(类/属性/方法),也不存在编译问题。

截屏2022-09-30 下午3.17.20.png

再思考:以上场景项目编译完成后运行过程中是否会有问题?

答:run 失败!

  • 在APP运行到A组件调用了B组件中被调整(类/属性/方法)的情况下就会出现运行时崩溃。
  • 因为最终参与APP工程编译的是1.0.1版本的B组件,该版本已经调整了(类/属性/方法),所以会出现运行时错误。

截屏2022-09-30 下午3.24.13.png

二、问题复现:

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、自己实现:

参考文章:juejin.cn/post/703877…

学习: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).】

计划:模块内校验 --> 模块间校验

其中:模块内校验已经实现。

截屏2022-09-30 下午3.31.43.png

截屏2022-09-30 下午3.31.34.png

截屏2022-09-30 下午3.31.59.png

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✅

1669176937674.png

  • Field

    • 属性缺失 build✅ run✅
    • 属性名名修改 build✅ run✅
    • 属性修饰符修改 build✅ run✅
    • 属性数据类型修改 build✅ run✅

image-20220930150445286.png

  • Class

    • 类缺失 build✅ run✅
    • 类名修改 build✅ run✅
    • 类修饰符修改 build✅ run✅

截屏2022-09-30 下午3.34.02.png

最后

至此,字节码校验插件就完工了,大家按需引入app工程即可(具体步骤可以参考:juejin.cn/post/714509… ),

大家共勉!!!