1.问题抛出
在android 7.1.1上 如果同时展示两个windowmanager.LayoutParams.type = type_toast,那么必现下面的报错信息,测试用例的代码 也在下面贴出:
测试用例代码:
WindowManager mw = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
TextView tv = new TextView(this);
tv.setLayoutParams(new WindowManager.LayoutParams(-2,-2));
tv.setText("windowmanager 添加悬浮窗");
WindowManager.LayoutParams params =new WindowManager.LayoutParams();
params.type = WindowManager.LayoutParams.TYPE_TOAST;
mw.addView(tv,params);
Toast toast = new Toast(this);
toast.makeText(this,"API25 type_toast",Toast.LENGTH_LONG).show();
必现报错信息
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.tmall.android/com.tmall.android.MainActivity}: android.view.WindowManager$BadTokenException: Unable to add window -- window android.view.ViewRootImpl$W@3afb76 has already been added
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2665)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2726)
at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:4519)
at android.app.ActivityThread.-wrap19(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1483)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6119)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
虽然报错的异常类型是WindowManager$BadTokenException
但是仔细看后面window android.view.ViewRootImpl$W@3afb76 has already been added,给出的是一个windowToken重复添加view的解释,因为在android 7.1.1上谷歌对type_toast类型的弹框做了限制,同一时刻 对于一个app 不能展示两个type_toast类型的弹框,因为该类型可以覆盖到其他程序上,造成不好的体验。
2.Toast弹出流程图
为了明白它是怎么将toast的视图添加到窗口中展示到我们眼前,以及明白这个BadTokenException异常是如果被抛出的,画了一张简要流程图,远程服务与本地通信都写在了图中,文字不在铺张开来
image.png3. 7.1.1与其他版本发起Toast的差异
android 7.1.1(Api25)上Toast请求windowmanager添加视图
image.png
android 8.0(Api27)上Toast 请求windowmanager添加视图
image.png
在更高的版本中 尽管还会抛出错误,但是谷歌在内部捕获了异常 ,不会致使程序Crash了,机智的谷歌。
4.问题解决
如果你对源码够了解看[2]的流程图就会知道着手点还是Toast,了解的不深也可以从[3]中看到 Toast的视图添加是在Toast&NT&handleShow方法中被发起的,,而且谷歌也在更高版本中在该方法中添加了异常捕获。那么我们怎么在7.1.1上解决这个问题呢,反射代理Toast&NT$Handler
package com.tmall.android.toast;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.StringRes;
import android.widget.Toast;
import java.lang.reflect.Field;
public class SafeToast {
private static Field sField_TN;
private static Field sField_TN_Handler;
private static Toast mToast;
static {
try {
sField_TN = Toast.class.getDeclaredField("mTN");
sField_TN.setAccessible(true);
sField_TN_Handler = sField_TN.getType().getDeclaredField("mHandler");
sField_TN_Handler.setAccessible(true);
} catch (Exception e) {
}
}
private SafeToast() {
}
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) {
try {
Object tn = sField_TN.get(toast);
Handler preHandler = (Handler) sField_TN_Handler.get(tn);
sField_TN_Handler.set(tn, new SafeHandler(preHandler));
} catch (Exception e) {
}
}
private static class SafeHandler extends Handler {
private Handler impl;
public SafeHandler(Handler impl) {
this.impl = impl;
}
@Override
public void dispatchMessage(Message msg) {
try {
super.dispatchMessage(msg);
} catch (Exception e) {
}
}
@Override
public void handleMessage(Message msg) {
impl.handleMessage(msg);//需要委托给原Handler执行
}
}
}
5.再次验证
不会崩溃,但是同时显示两个type_toast类型的悬浮窗异常依旧会抛出,但是被我们捕获了。
WindowManager mw = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
TextView tv = new TextView(this);
tv.setLayoutParams(new WindowManager.LayoutParams(50,50));
tv.setBackgroundColor(Color.WHITE);
tv.setText("windowmanager 添加悬浮窗");
WindowManager.LayoutParams params =new WindowManager.LayoutParams();
params.width=50;
params.height=50;
params.type = WindowManager.LayoutParams.TYPE_TOAST;
mw.addView(tv,params);
//Toast toast = new Toast(this);
// toast.makeText(this,"API25 type_toast",Toast.LENGTH_LONG).show();
SafeToast.show(this,"safe_toast on android 7.1.1", Toast.LENGTH_LONG);
网友评论