Android中使用ClassLoader修改自定义异常类继承来使异常捕获失效来坑害你的同事
2017-12-06 18:46
453 查看
原理:使用热修复的原理,用ClassLoader加载同名替换类。根据类的加载机制,一个类只会被加载一次,所以可以使用ClassLoader加载一个同名的、Throwable子类中的非异常类的类,来使异常捕获失效
首先,定义一个自定义异常
然后,在方法中声明抛出和捕获
最后,调用方法
可以看到控制台打印出了Do something for i 1和之后的异常栈,程序正常运行。
下面开始捣乱
1.把FooledYouException复制出来,将继承Exception改为继承Error,并编译成类文件,目录结构和包保持一致
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类中
可以看到一调用方法立刻就崩溃了。这时你的同事会上FooledYouTool中找问题,然而他会看到一个声明捕获Exception公共父类的try-catch语句。
这里就是利用热修复的原理,将加载的类替换成了外部dex中的,导致应用行为改变。
为了验证一下,将异常捕获地方的声明改为捕获对应类,并验证继承关系
可以看到方法正常执行,进入catch块打印异常栈,根据Log输出结果可以看出新建的FooledYouException实际为Error的子类,以Exception声明捕获自然是会漏掉。
这里参考的热修复/类加载方法来自文章
http://blog.csdn.net/sahadev_/article/details/53363052
原文中通过修改默认ClassLoader中的dexElements数组实现,我在使用时遇到了问题,修改后无法找到入口Activity,一启动就会抛出ClassNotFound异常。
根据类加载器的双亲委派机制,子ClassLoader在尝试加载一个类时,首先会让父ClassLoader加载,所以尝试修改默认加载器的parent来实现替换。
原关系为
默认加载器->默认加载器父加载器
修改后变为
默认加载器->我的加载器->默认加载器父加载器
运行结果正常。
首先,定义一个自定义异常
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来实现替换。
原关系为
默认加载器->默认加载器父加载器
修改后变为
默认加载器->我的加载器->默认加载器父加载器
运行结果正常。
相关文章推荐
- Android 使用自定义UncaughtExceptionHandler捕获异常
- Android自定义捕获Application全局异常
- Android自定义捕获Application全局异常
- Android使用UncaughtExceptionHandler捕获全局异常
- android自定义捕获全局异常
- Android使用UncaughtExceptionHandler捕获全局异常
- Android学习札记50:在Android中自定义捕获Application全局异常
- 在Android中自定义捕获Application全局异常,可以替换掉系统的强制退出对话框(很有参考价值与实用价值)
- Android使用UncaughtExceptionHandler捕获全局异常
- Android使用UncaughtExceptionHandler捕获全局异常
- Android使用UncaughtExceptionHandler捕获全局异常
- [转]Android自定义捕获Application全局异常
- java 使用 instanceof 关键字,捕获自定义异常
- Android使用UncaughtExceptionHandler捕获全局异常
- Android使用UncaughtExceptionHandler捕获全局异常
- Android使用UncaughtExceptionHandler捕获全局异常
- 在Android中自定义捕获Application全局异常,可以替换掉系统的强制退出对话框(很有参考价值与实用价值)
- Android使用UncaughtExceptionHandler捕获全局异常
- 继承android.view.View自定义view,使用画笔绘制view示例
- Android自定义捕获Application全局异常