在Android中Toast的使用频率是很高的,一是系统自带,二是使用方便,三是可以跨页面显示。但是在使用中还是需要注意一些情况,否则可能导致崩溃。
在子线程中使用
子线程中使用Toast是可以的,但是需要先调用Looper.prepare(),然后再show Toast。子线程Toast参考代码。
new Thread() {
@Override
public void run() {
super.run();
Looper.prepare();
Toast.makeText(MainActivity.this,"toast",Toast.LENGTH_SHORT).show();
Looper.loop();
}
}.start();
连续弹窗
默认情况下连续弹窗的效果是先弹第一个,再弹第二个,直到所有的都弹窗结束,延迟很大效果不好。解决方法有几个,
- 如果toast在显示则直接setText,不显示就弹窗。这种方法需要自己加时间戳判断,因为时间有误差,有可能导致某些弹窗显示不了。
- 先调用cancel(),再调用show()。因为系统版本的差异,这种方法在有些手机上也会无法显示。
- 在第二种的基础上加一步重新创建的过程,但是内存会增加少许,不过不影响。
在Android7.1上的问题
由于谷歌的问题,在android7.1上如果UI界面卡顿,show Toast会崩溃,具体原因可以参考这个链接https://tech.meituan.com/2018/03/29/toast-snackbar-replace.html。解决方法是利用反射设置一个自定义的Handler,用try,catch捕获异常,关键代码如下
private static void hookN(Toast toast) {
try {
Field fieldTN = Toast.class.getDeclaredField("mTN");
fieldTN.setAccessible(true);
Object mTN = fieldTN.get(toast);
Field fieldHandler = fieldTN.getType().getDeclaredField("mHandler");
fieldHandler.setAccessible(true);
Handler handler = (Handler) fieldHandler.get(mTN);
fieldHandler.set(mTN, new SafeHandler(handler));
} catch (Exception ignored) {
}
}
没有通知权限
在某些版本的系统上,如果没有通知权限,Toast也无法显示了。这个问题有两种解决方法,一种是在本页面添加这个Toast页面进行显示,相当于重写一遍Toast的实现方式,但是这个自定义的无法跨页面显示。另外一种方法是利用反射和动态代理方式把Toast的包名替换为系统包名android,如果Toast的PackageName是系统包名不会去检查权限。关于这一块儿的源码在NotificationManagerService中,关键代码如下
private final IBinder mService = new INotificationManager.Stub() {
@Override
public void enqueueToast(String pkg, ITransientNotification callback, int duration)
{
……
final boolean isSystemToast = isCallerSystemOrPhone() || ("android".equals(pkg));
final boolean isPackageSuspended =
isPackageSuspendedForUser(pkg, Binder.getCallingUid());
// 如果不是系统Toast则检查权限
if (ENABLE_BLOCKED_TOASTS && !isSystemToast &&
(!areNotificationsEnabledForPackage(pkg, Binder.getCallingUid())
|| isPackageSuspended)) {
Slog.e(TAG, "Suppressing toast from package " + pkg
+ (isPackageSuspended
? " due to package suspended by administrator."
: " by user request."));
return;
}
……
}
// 省略
……
}
动态修改Toast的包名方法如下
private static Object iNotificationManagerObj;
/**
* hook
* @param toast toast
*/
private static void hook(Toast toast) {
try {
//hook INotificationManager
if (iNotificationManagerObj == null) {
Method getServiceMethod = Toast.class.getDeclaredMethod("getService");
getServiceMethod.setAccessible(true);
iNotificationManagerObj = getServiceMethod.invoke(null);
Class iNotificationManagerCls = Class.forName("android.app.INotificationManager");
Object iNotificationManagerProxy = Proxy.newProxyInstance(toast.getClass().getClassLoader(), new Class[]{iNotificationManagerCls}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("enqueueToast".equals(method.getName())) {
args[0] = "android";
}
return method.invoke(iNotificationManagerObj, args);
}
});
Field sServiceFiled = Toast.class.getDeclaredField("sService");
sServiceFiled.setAccessible(true);
sServiceFiled.set(null, iNotificationManagerProxy);
}
} catch (Exception e) {
LogUtils.e(e);
}
}