AddTryCatch插件的作用——编译期加tryCatch,支持向第三方库中加tryCatch
在平时开发过程中,无可避免的会遇到crash,如果是自己写的代码里抛出异常还好,把相关代码逻辑改好即可。但是遇到集成到项目中的第三方库抛出异常,又无法通过自己可修改的代码部分规避问题,就只能依赖第三方库开发者修复问题,再发布新版本了。
而现实中遇到的第三方库,能在提issue后快速修复问题并发布新版本的实在是少之又少。这种情况下,我们就十分被动了。
每当这时候,我就在想:如果能修改第三方库中的代码就好了! 不求能精准修改逻辑,仅仅是能加上try catch就不错了,如果能在catch后捕获到异常,能再调用自定义的代码处理该异常,就更好了!
而这个AddTryCatch,就是为此而生的。
这个插件,可以通过简单的配置,做到在编译期修改字节码的效果。
因为是在编译期在字节码的层面上修改,所以不管是自己写的代码,还是引用的第三方库中的代码,都可以修改。
项目地址:https://github.com/xingchenxuanfeng/AddTryCatchPlugin
AddTryCatch插件的使用
该插件的使用方式很简单,只需要两部:
- AddTryCatch插件发布在了jitpack上,使用时需要先把jitpack加入到buildScript 的repositories中,并在dependencies 中加入
classpath 'com.github.xingchenxuanfeng:AddTryCatchPlugin:1.0.1'
。代码如下:
buildscript {
repositories {
...
maven { url 'https://jitpack.io' }
}
dependencies {
...
classpath 'com.github.xingchenxuanfeng:AddTryCatchPlugin:1.0.1'
}
}
- 在app moudle级别的build.gradle中
apply plugin: 'add-trycatch'
,然后按如下格式进行配置。
apply plugin: 'add-trycatch'
addTryCatch {
hookPoint = [
"com.addtrycatchplugin.TestCrash1" : [
"crashMethod1",
"crashMethod2"
],
"com.addtrycatchplugin.TestCrash2": [
"crashMethod1",
"crashMethod2"
]
]
exceptionHandler = ["com.addtrycatchplugin.ExceptionUtils": "uploadCatchedException"]
}
按照上面的写法配置好后,插件就可以正常工作了。
参数解释:
下面我来解释一下各个参数的具体意义。
hookPoint
hookPoint表示需要注入tryCatch的代码,hookPoint声明的数据结构是Map<String, List<String>>
。
上面的配置表示,在com.addtrycatchplugin.TestCrash1这个类的crashMethod1和crashMethod2两个方法,与com.addtrycatchplugin.TestCrash2这个类的crashMethod1和crashMethod2两个方法,共计四个方法中加入tryCatch。在方法的第一行加入try关键字,在方法最后一行加入catch关键字和catch代码块。
exceptionHandler
exceptionHandler表示在catch到异常后,会执行的异常处理方法(常见的处理策略是上报到统计平台以供分析)。com.addtrycatchplugin.ExceptionUtils是处理方法的类名,uploadCatchedException是方法名。(该参数可以为空,表示catch到异常后,不做任何处理,忽略该异常)
下面是插件效果:
原始代码: 三个类 异常处理类 ExceptionUtils。java,和要被修改的类,TestCrash1.java,TestCrash2.java
ExceptionUtils.java
package com.addtrycatchplugin;
import android.util.Log;
public class ExceptionUtils {
public static void uploadCatchedException(Exception exception) {
if (exception == null) {
return;
}
//demo里没有接入异常上报平台,仅仅打了log来测试是否捕获成功
Log.e("ExceptionUtilsTAG", "uploadCatchedException", exception);
}
}
TestCrash1.java
package com.addtrycatchplugin;
public class TestCrash1 {
public static void crashMethod1() {
int a = 1 / 0;
}
public static void crashMethod2() {
int a = 1 / 0;
}
}
TestCrash2.java
package com.addtrycatchplugin;
class TestCrash2 {
public static void crashMethod1() {
int a = 1 / 0;
}
public static void crashMethod2() {
int a = 1 / 0;
}
}
生成后的代码:TestCrash1.class 和TestCrash2.class
TestCrash1.class
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.addtrycatchplugin;
public class TestCrash1 {
public TestCrash1() {
}
public static void crashMethod1() {
try {
int var0 = 1 / 0;
} catch (Exception var2) {
ExceptionUtils.uploadCatchedException(var2);
}
}
public static void crashMethod2() {
try {
int var0 = 1 / 0;
} catch (Exception var2) {
ExceptionUtils.uploadCatchedException(var2);
}
}
}
TestCrash2.class
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.addtrycatchplugin;
public class TestCrash2 {
public TestCrash2() {
}
public static void crashMethod1() {
try {
int var0 = 1 / 0;
} catch (Exception var2) {
ExceptionUtils.uploadCatchedException(var2);
}
}
public static void crashMethod2() {
try {
int var0 = 1 / 0;
} catch (Exception var2) {
ExceptionUtils.uploadCatchedException(var2);
}
}
}
插件原理:
利用gralde transform api在编译流程中加入自定义的transform任务,然后在自定义的transform任务中,使用asm修改字节码来达到注入try catch代码的目的。同时使用Hunter框架来优化transform任务运行效率,简化代码逻辑。
asm
ASM是一个通用的Java字节码操作和分析框架。它可以用于修改现有类或直接以二进制形式动态生成类。ASM提供了一些常见的字节码转换和分析算法,可以从中构建自定义复杂转换和代码分析工具。ASM提供与其他Java字节码框架类似的功能,但专注于 性能。因为它的设计和实现尽可能小而且快,所以它非常适合在动态系统中使用(当然也可以以静态方式使用,例如在编译器中)。
目前已广泛应用于众多著名项目,如OpenJDK,Groovy编译器,Kotlin编译器,Gradle等。
Gradle transform api
Gradle Transform是Android官方提供给开发者在项目构建阶段中由class到dex转换之前修改class文件的一套api。目前比较经典的应用是字节码插桩、代码注入技术。
Hunter
Hunter: 一个插件框架,在它的基础上可以快速开发一个并发、增量的字节码编译插件,帮助开发人员隐藏了Transform和ASM的绝大部分逻辑,开发者只需写少量的ASM code,就可以开发一款编译插件,修改Android项目的字节码。
插件实现:
具体实现见下期:
一步步实现AddTryCatch插件 —— gradle transform和ASM实践