華爲、三星等機型禁用通知權限後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(Git地址)