华为、三星等机型禁用通知权限后Toast不弹出
原因
查看Toast
源码后发现,Toast
显示要通过INotificationManager
类来实现,而当通知禁用后,调用此类会返回异常,所以导致通知不显示,源码如下:
public void show() {
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
try {
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// 权限禁用后走这里,这里是空方法,所以会发生既不crash又无响应的情况
}
}
这是一个google
的bug
,部分小米手机重写了Toast
代码,所以可以正常执行,我们可以通过反射的方式来暴力绕过,也就有了如下解决方式:
解决方法
public class ToastUtils {
private static Object iNotificationManagerObj;
/**
* @param context
* @param message
*/
public static void show(Context context, String message) {
show(context.getApplicationContext(), message, Toast.LENGTH_SHORT);
}
/**
* @param context
* @param message
*/
public static void show(Context context, String message, int duration) {
if (TextUtils.isEmpty(message)) {
return;
}
//后setText 兼容小米默认会显示app名称的问题
Toast toast = Toast.makeText(context, null, duration);
toast.setText(message);
if (isNotificationEnabled(context)) {
toast.show();
} else {
showSystemToast(toast);
}
}
/**
* 显示系统Toast
*/
private static void showSystemToast(Toast toast) {
try {
Method getServiceMethod = Toast.class.getDeclaredMethod("getService");
getServiceMethod.setAccessible(true);
//hook INotificationManager
if (iNotificationManagerObj == null) {
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 {
//强制使用系统Toast
if ("enqueueToast".equals(method.getName())
|| "enqueueToastEx".equals(method.getName())) { //华为p20 pro上为enqueueToastEx
args[0] = "android";
}
return method.invoke(iNotificationManagerObj, args);
}
});
Field sServiceFiled = Toast.class.getDeclaredField("sService");
sServiceFiled.setAccessible(true);
sServiceFiled.set(null, iNotificationManagerProxy);
}
toast.show();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 消息通知是否开启
*
* @return
*/
private static boolean isNotificationEnabled(Context context) {
NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(context);
boolean areNotificationsEnabled = notificationManagerCompat.areNotificationsEnabled();
return areNotificationsEnabled;
}
}
内容相同Toast短时间不能重复弹出
原因
当我们重复点击Toast
时候,会连续弹出很多Toast
,视觉体验不好,于是网上流传着这些解决方法:
Toast mToast;
public void showToast(String text) {
if (mToast == null) {
mToast = Toast.makeText(MainActivity.this, text, Toast.LENGTH_SHORT);
} else {
mToast.setText(text);
mToast.setDuration(Toast.LENGTH_SHORT);
}
mToast.show();
}
这个方法在旧版本android上没有问题,新版本当短时间显示同一个Toast时,会显示不出来。
文字相同且当前Toast
正在显示时,系统会认为是误触操作,从而屏蔽当前显示Toast
请求。
出现这个问题据说是为了防止某些流氓app
一直弹出一个模仿系统界面的Toast
从而导致系统瘫痪。
解决方法
这是系统的限制,想要绕过这个限制只能自定义Toast
了,这里我推荐git上的大神自定义版Toast
——XToast