What are Hooks?
The hook technology is also called the hook function. Before the system calls the function, the hook program first captures the message, and the hook function first obtains the control right. At this time, the hook function can process (change) the execution behavior of the function, and can also be forced to end message delivery. To put it simply, it is to pull out the program of the system and turn it into a code fragment that we execute ourselves.
Implementation of Hook?
To implement hooks, we must know java's reflection and dynamic proxy.
Detailed Explanation of Java Reflection Mechanism
Case: Toast WindowManager$BadTokenException
Tips: We mainly focus on Android7.x in this small section of source code.
I believe that Android friends should have encountered token failure when they usually develop.
According to the normal process, this abnormality will not occur. But because in some cases, Android
a message of a certain UI thread of the process is blocked. The method TN
that causes the 0 (display) message to come out is located after the message and has not been executed for a long time. At this time, the timeout detection of the service is over, and the record in the service is deleted . That is, as shown, delete happens before the process method. This leads to our exception above.show
post
NotificationManager
WMS
token
token
Android
show
The entire toast display principle and analysis You can take a complete look at the analysis of the QQ music technology team, (the source of the above picture is also the same) [ Android] Toast problem analysis (1) - Tencent Cloud Developer Community - Tencent Cloud
The token is valid is not reproduced in the sleep method, but after reading the Toast source code, the BadTokenException can be reproduced in another way:
val mw = getSystemService(WINDOW_SERVICE) as WindowManager
val tv = TextView(this)
tv.layoutParams = WindowManager.LayoutParams(1, 1)
tv.text = "模拟toast悬浮窗"
val params = WindowManager.LayoutParams()
params.type = WindowManager.LayoutParams.TYPE_TOAST
mw.addView(tv, params)
Toast.makeText(this, "xxxx", Toast.LENGTH_SHORT).show()
Because toasts of type==TYPE_TOAST cannot be added repeatedly, a BadTokenException will also be reported. Next, we will pass this demo and use the hook solution to solve this exception.
Reading the source code, we found that the handleShow method in Android 7.0 Toast.java:
and on Android 8.0 Toast.java:
The comparison found that in Android 8.0, 8.0 has a layer of try catch protection when WindowManager performs addView, but it does not on 7.0. Then we can refer to the method of 8.0 and catch this exception directly.
Find the hook point
Try to hook static variables and singleton objects
Try to hook public objects and methods
Looking at the call chain, a message is sent in mHandler, and handlerShow is called after processing the message displayed by the toast in handlerMessage. mHandler is a variable in the TN class, and there is a variable mTN (TN class) in Toast. So it is very simple, we hook the point to locate this mTN, then reflect and replace the internal member variable mHandler of TN, and add a try-catch to the handleMessage method to protect it.
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;
}
@Override
public void handleMessage(Message msg) {
try{
tnHandler.handleMessage(msg);
}catch (Exception e){
e.printStackTrace();
}
}
}
}
Call HookToastUtil.show(this,"111",1000) to replace the previous Toast.makeText and no error will be reported