您的位置:首页 > 移动开发 > Android开发

Android中使用ClassLoader修改自定义异常类继承来使异常捕获失效来坑害你的同事

2017-12-06 18:46 375 查看
原理:使用热修复的原理,用ClassLoader加载同名替换类。根据类的加载机制,一个类只会被加载一次,所以可以使用ClassLoader加载一个同名的、Throwable子类中的非异常类的类,来使异常捕获失效

首先,定义一个自定义异常

public class FooledYouException extends Exception {
public FooledYouException() {
super("Surprise!");
}
}


然后,在方法中声明抛出和捕获

public class FooledYouTool {

public static void fooledYou(int i){
try{
System.out.println("Do something for i "+i);
throwExceptionMethod();
}catch (Exception e){
e.printStackTrace();
}
}

private static void throwExceptionMethod() throws FooledYouException{
throw new FooledYouException();
}
}
可以看到方法中声明的位所有异常类的父类,不应该有异常被抛出

最后,调用方法

public class MainActivity2 extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FooledYouTool.fooledYou(1);
}
}


可以看到控制台打印出了Do something for i 1和之后的异常栈,程序正常运行。

下面开始捣乱

1.把FooledYouException复制出来,将继承Exception改为继承Error,并编译成类文件,目录结构和包保持一致

public class FooledYouException extends Error {
public FooledYouException() {
super("Surprise!");
}
}


2.用jar工具打成jar包

jar cvf FooledYou.jar alpacaplayground/FooledYouException.class

3.用dx工具转成dex

dx工具在sdk的built-tools目录中,以mac为例,目录为

~/Library/Android/sdk/build-tools

会有多个buildtools版本,我选择了app使用的26版本。

执行命令

~/Library/Android/sdk/build-tools/26.0.2/dx --dex --output=FoolYouDex.jar FooledYou.jar

再将转好的dex文件复制到手机里可被App访问的地方,比如SD卡中。我为了省事放到了Assets目录中,在App启动后复制到App缓存目录下

由于要在其他人第一次使用这个类之前完成替换,将替换代码写在Application类中

public class MyApplication extends Application {

@Override
public void onCreate() {
super.onCreate();
try {
/*
这里是将dexjar文件复制到缓存目录中来给ClassLoader加载
所有的耗时操作都应该放在工作线程中进行,这里是为了演示省事,
直接在主线程中进行IO操作,请勿效仿
不要阻塞主线程,不要阻塞主线程,不要阻塞主线程,重要的事情说三遍
*/
InputStream im = getAssets().open("FoolYouDex.jar");
File foolYou = new File(getCacheDir(), "foolYou.jar");
if (foolYou.exists()) {
foolYou.delete();
}
byte[] buf = new byte[4096];
FileOutputStream fos = new FileOutputStream(foolYou);
int length;
while ((length = im.read(buf)) > 0) {
fos.write(buf, 0, length);
}
//获得默认的ClassLoader
ClassLoader pathClassLoader = getClassLoader();
try {
Field field = ClassLoader.class.getDeclaredField("parent");
field.setAccessible(true);
//创建自定义ClassLoader,并以默认ClassLoader的parent作为自己的parent
DexClassLoader dexClassLoader = new DexClassLoader(foolYou.getAbsolutePath(), getDir("dex", 0).getAbsolutePath(),
null, (ClassLoader) field.get(pathClassLoader));
//将默认ClassLoader的parent设置为自定义ClassLoader
field.set(pathClassLoader, dexClassLoader);
} catch (Throwable e) {
e.printStackTrace();
}

} catch (Exception e) {
e.printStackTrace();
}
}

}
修改manifest将Application的name指定为自定义的,再运行



可以看到一调用方法立刻就崩溃了。这时你的同事会上FooledYouTool中找问题,然而他会看到一个声明捕获Exception公共父类的try-catch语句。

这里就是利用热修复的原理,将加载的类替换成了外部dex中的,导致应用行为改变。

为了验证一下,将异常捕获地方的声明改为捕获对应类,并验证继承关系

public static void fooledYou(int i){
try{
System.out.println("Do something for i "+i);
throwExceptionMethod();
}catch (FooledYouException e){
e.printStackTrace();
Log.i("FooledYouException","is Throwable "+(Throwable.class.isInstance(e)));
Log.i("FooledYouException","is Exception "+(Exception.class.isInstance(e)));
Log.i("FooledYouException","is Error "+(Error.class.isInstance(e)));
}
}
运行App,结果如下



可以看到方法正常执行,进入catch块打印异常栈,根据Log输出结果可以看出新建的FooledYouException实际为Error的子类,以Exception声明捕获自然是会漏掉。

这里参考的热修复/类加载方法来自文章
http://blog.csdn.net/sahadev_/article/details/53363052
原文中通过修改默认ClassLoader中的dexElements数组实现,我在使用时遇到了问题,修改后无法找到入口Activity,一启动就会抛出ClassNotFound异常。

根据类加载器的双亲委派机制,子ClassLoader在尝试加载一个类时,首先会让父ClassLoader加载,所以尝试修改默认加载器的parent来实现替换。

原关系为

默认加载器->默认加载器父加载器

修改后变为

默认加载器->我的加载器->默认加载器父加载器

运行结果正常。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息