華為、三星等機(jī)型禁用通知權(quán)限后Toast不彈出
原因
查看Toast源碼后發(fā)現(xiàn),Toast顯示要通過INotificationManager類來實(shí)現(xiàn),而當(dāng)通知禁用后,調(diào)用此類會(huì)返回異常,所以導(dǎo)致通知不顯示,源碼如下:
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) {
// 權(quán)限禁用后走這里,這里是空方法,所以會(huì)發(fā)生既不crash又無響應(yīng)的情況
}
}
這是一個(gè)google的bug,部分小米手機(jī)重寫了Toast代碼,所以可以正常執(zhí)行,我們可以通過反射的方式來暴力繞過,也就有了如下解決方式:
解決方法
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 兼容小米默認(rèn)會(huì)顯示app名稱的問題
Toast toast = Toast.makeText(context, null, duration);
toast.setText(message);
if (isNotificationEnabled(context)) {
toast.show();
} else {
showSystemToast(toast);
}
}
/**
* 顯示系統(tǒng)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 {
//強(qiáng)制使用系統(tǒng)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;
}
}
內(nèi)容相同Toast短時(shí)間不能重復(fù)彈出
原因
當(dāng)我們重復(fù)點(diǎn)擊Toast時(shí)候,會(huì)連續(xù)彈出很多Toast,視覺體驗(yàn)不好,于是網(wǎng)上流傳著這些解決方法:
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();
}
這個(gè)方法在舊版本android上沒有問題,新版本當(dāng)短時(shí)間顯示同一個(gè)Toast時(shí),會(huì)顯示不出來。
文字相同且當(dāng)前Toast正在顯示時(shí),系統(tǒng)會(huì)認(rèn)為是誤觸操作,從而屏蔽當(dāng)前顯示Toast請(qǐng)求。
出現(xiàn)這個(gè)問題據(jù)說是為了防止某些流氓app一直彈出一個(gè)模仿系統(tǒng)界面的Toast從而導(dǎo)致系統(tǒng)癱瘓。
解決方法
這是系統(tǒng)的限制,想要繞過這個(gè)限制只能自定義Toast了,這里我推薦git上的大神自定義版Toast——XToast