在 Android 开发中,很多时候,我们可能想要修改第三方库里的某些类。那么我们有两个主流的方式来实现:
第一种方式,优点是方法简单不容易出错,而且 Debug 的时候不会有什么问题。缺点就是操作麻烦,升级的时候需要和上游进行 Rebase。而且当遇到 OkHttp 这种用 Maven 组织的项目,对惯用 Gradle 组织项目的 Android 工程师更是个灾难。
第二种方式,优点是非侵入,不需要下载和引入第三方库的源码。而缺点的话,就是实现相对复杂(涉及到字节码操作),而且因为字节码已被修改,没法和源码对应,所以 Debug 的时候也可能出问题。
相信很多人在打包 Apk 的编译期(Build Time)遇到过「Duplicate Class」的错误。原因是 Dex 构建工具在把所有类打包进同一个 Dex 的时候,如果发现如果有重复的类要打包进 Dex 的话,就会报错。
而在编辑期(Edit Time)的时候,如果我们的所有源码中有重复类的话,IDE 也会提示错误。但是如果我们的源码里有一个和第三方 Jar 包里重复的类,IDE 是不会提示错误的。为什么呢?
因为重复类并不在同一个 Archive 里。Archive 可以看作类的容器,可以是一个 Jar 包,也可以是一个 Dex。当然你某个 Module 中的所有源码,也可以想像成是一个 Archive,所以源码有重复类是会提示错误的。
总结以下,在源码中的类和 Jar 包里的类有重复的情况下,在编辑期是不会提示错误的。只有当到了编译期,构建工具将你源码编译出来的类,和第三方 Jar 里的重复的类,混合到同一个 Archive 里的时候才会报错。
既然,IDE 允许源码中有和 Jar 里的类重复的类。那么,对于开头提到的问题,我们可以提出一种新的解决方案:
只把 Jar 里想要修改的类的对应源码拷贝到项目中修改,然后在最终编译的时候把 Jar 包里这些类给删除掉。保证在打包进 Dex 的时候只有我们拷贝的源码编译出来的类即可。
我们可以使用 JarFilterPlugin 来帮我们在最后打包进 Dex 之前,把所有依赖的 Jar 包里指定的 Class 文件全部过滤掉。
我们先把 Plugin 引入到我们的项目中:
buildscript { repositories { maven { url "https://jitpack.io" } } dependencies { classpath "com.github.nekocode:JarFilterPlugin:1.1" } }
然后我们开始使用插件来过滤 Class 文件了。
假设我们想要修改 Android Support V7 包里的 AppCompatActivity 类。我们只需要把 AppCompatActivity 类的源码拷贝到我们的项目中,然后在 App Module 的 budil.gradle 下添加如下配置:
apply plugin: 'jar-filter' jarFilter { skipFiles = [ 'android/support/v7/app/AppCompatActivity.class', 'android/support/v7/app/AppCompatActivity//$(.*).class' ] }
这样,在执行 Assemble 任务打包出的 Apk 中就只会有我们拷贝的 AppCompatActivity 类。