【问题描述:接口default方法AbstractMethodError】
记录一个Android项目中遇到的问题,我们通过exclude方式重写了一个依赖,改用本地的实现,其中一个接口的default 方法,在运行时报错:AbstractMethodError,也就是没有找到这个default的实现。
【定位原因:exclude导致d8不注入default方法的实现】
查看了exclude之后的apk里本地实现该default接口的class文件,发现Android d8工具实现了对Jave 8语言特性的兼容实现,实现方式是把接口的 default 方法生成了一个$-CC
的合成类,所有继承这个接口的类里注入了一个合成方法,该方法调用了这个$-CC
类里的实现。(编译过程)
而我们exclude之后的apk里lib2实现该default接口的class文件,继承这个接口的类里没有注入这个合成方法,导致直接调用了接口的abstract定义,所以报出AbstractMethodError。
进一步实验发现,只要在依赖 lib2 时exclude了 lib1,即使app里重新依赖lib1,都会导致d8无法实现对 lib2 里default方法的注入。
所以问题的原因进一步需要定位:【为什么exclude之后,d8无法实现对 lib2 里default方法的注入?】
【解决方案:substitute】
笔者还没能分析出【为什么exclude之后,d8无法实现对 lib2 里default方法的注入?】这个问题,但是从另一个思路解决了这个问题,就是既然想把lib1的实现替换为本地的,可以建立一个子模块project(‘:local_lib1’),使用gradle的substitute语法来替换module lib1的依赖为project(‘:local_lib1’):
configurations.all {
resolutionStrategy.dependencySubstitution {
substitute module("com.harold.group:lib1") using project(":local_lib1") because "some reason"
}
}
这正是gradle推荐使用substitute的场景:
One use case for dependency substitution is to use a locally developed version of a module in place of one that is downloaded from an external repository. This could be useful for testing a local, patched version of a dependency.
最后的建议:
- 不要过度使用exclude,仅当你确定代码中真的不需要这个module的时候,否则即使你在其他地方又依赖了进来,也会对代码的编译产生影响,例如本文中的default接口
- 想要移除远程module依赖,替换为本地的实现时,非常适合使用 substitute
- 如果只是想指定module版本,考虑使用自定义依赖解析