> 文章列表 > android hook(Toast BadTokenException案例)

android hook(Toast BadTokenException案例)

android hook(Toast BadTokenException案例)

什么是Hook?

 hook 技术又叫做钩子函数,在系统没有调用该函数之前,钩子程序先捕捉该消息,钩子函数先得到控制权,这时钩子函数即可以加工处理(改变)该函数的执行行为,还可以强制结束消息的传递。简单来说,就是把系统的程序拉出来,变成我们自己执行的代码片段。

Hook的实现?

实现hook我们必须要知道java的反射和动态代理。

​​​​​​Java反射机制详解_贺兰猪的博客-CSDN博Java动态代理_贺兰猪的博客-CSDN博客Java反射机制详解_贺兰猪的博客-CSDN博客

案例:Toast WindowManager$BadTokenException

tips:这一小段源码层面我们主要针对于Android7.x。

相信Android朋友们平时开发的时候应该都遇到过token失效。

按照正常的流程,是不会出现这种异常。但是由于在某些情况下, Android 进程某个 UI 线程的某个消息阻塞。导致 TNshow 方法 post 出来 0 (显示) 消息位于该消息之后,迟迟没有执行。这时候,NotificationManager 的超时检测结束,删除了 WMS 服务中的 token 记录。也就是如图所示,删除 token 发生在 Android 进程 show 方法之前。这就导致了我们上面的异常。

整个toast显示原理及分析大家可以完整的看下QQ音乐技术团队的分析,(上图来源也是那)[Android] Toast问题深度剖析(一) - 腾讯云开发者社区-腾讯云

用sleep的方式并没有复现出来这个token is valid,但阅读Toast源代码后可以用另一种方式来复现这个BadTokenException:

val mw = getSystemService(WINDOW_SERVICE) as WindowManagerval tv = TextView(this)tv.layoutParams = WindowManager.LayoutParams(1, 1)tv.text = "模拟toast悬浮窗"val params = WindowManager.LayoutParams()params.type = WindowManager.LayoutParams.TYPE_TOASTmw.addView(tv, params)Toast.makeText(this, "xxxx", Toast.LENGTH_SHORT).show()

因为type==TYPE_TOAST的类型的toast不能重复添加,所以这样也会报一个BadTokenException,接下来我们就要通过这个demo,用hook的解决方案来解决这个异常。

阅读源码我们发现,在Android 7.0 Toast.java  handleShow方法:

和在Android 8.0 Toast.java上:

对比发现在Android 8.0中,在WindowManager进行addView的时候8.0进行了一层try catch保护,而在7.0上并没有。那么我们就可以参考8.0的方法,直接catch住这个异常。

寻找hook点

尽量hook静态变量和单例对象

尽量hook public的对象和方法

查看调用链,mHandler中发了一个消息,在handlerMessage中处理这个toast显示的消息后调用handlerShow,mHandler是TN类中的变量,Toast 里面有一个变量mTN(TN类)。所以就很简单了,我们hook点就定位这个mTN,然后反射替换TN的内部成员变量mHandler,对handleMessage方法添加try-catch做到保护即可。

public class HookToastUtil {private static Field sField_N;private static Field sField_TN_Handler;private static Toast mToast;private HookToastUtil() {}public static void show(Context context, CharSequence message, int duration) {if (mToast == null) {mToast = Toast.makeText(context.getApplicationContext(), message, duration);hook(mToast);} else {mToast.setDuration(duration);mToast.setText(message);mToast.show();}}public static void show(Context context, @StringRes int resId, int duration) {if (mToast == null) {mToast = Toast.makeText(context.getApplicationContext(), resId, duration);hook(mToast);} else {mToast.setDuration(duration);mToast.setText(context.getString(resId));}mToast.show();}private static void hook(Toast toast) {if (Build.VERSION.SDK_INT != Build.VERSION_CODES.N_MR1) {return;}try {Class<?> cls = Class.forName("android.widget.Toast");sField_N = cls.getDeclaredField("mTN");sField_N.setAccessible(true);sField_TN_Handler = sField_N.getType().getDeclaredField("mHandler");sField_TN_Handler.setAccessible(true);Object tn = sField_N.get(toast);Handler handler = (Handler) sField_TN_Handler.get(tn);sField_TN_Handler.set(tn, new ReplaceHandler(handler));} catch (Exception e) {e.printStackTrace();}}private static class ReplaceHandler extends Handler {private Handler tnHandler;public ReplaceHandler(Handler handler) {this.tnHandler = handler;}@Overridepublic void handleMessage(Message msg) {try{tnHandler.handleMessage(msg);}catch (Exception e){e.printStackTrace();}}}
}

 调用HookToastUtil.show(this,"111",1000)  替换之前的Toast.makeText就不会报错了