最近在做项目时,遇到一个问题,有部手机就是Toast弹不出来。仔细想想可能权限的问题。后面网上搜索给出如下答案: 跟踪Toast的源代码,

make方法省略,做了一些初始化的工作,

show方法

public void show(){
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
INotificationManager service = getService();
String pkg = mContext.getPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
try {
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}
static private INotificationManager getService(){
if (sService != null) {
return sService;
}
sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));
return sService;
}

熟悉binder通讯的同学应该都知道,其实调用了NotificationManagerService.service.enqueueToast方法进入toast队列,进行相应的逻辑处理后回调给Toast中的TN,TN其实就是一个aidl的stub实现,相当于Client端,用来接收Service端发来的消息。看下TN中的show方法

\

public void handleShow(){
...
mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
mWM.removeView(mView);
mWM.addView(mView, mParams);
trySendAccessibilityEvent();
...
}
}

通过WinwodManager添加一个view显示提示消息。

总结来说就是toast的显示过程通过IPC通讯由NotificationManagerService维护一个toast队列,然后通知给Toast中的客户端TN调用WindowManager添加view。

那么,如果关闭通知栏消息权限,会影响NotificationManagerService队列的逻辑处理过程,导致不能通知TN显示出视图。

通过上面的分析,我们可以绕过NotificationManagerService,我们自己维护一个toast队列,处理相关的逻辑,进行显示,定时取消。关键代码


private static void activeQueue(){
BooheeToast toast = mQueue.peek();
if (toast == null) {
mAtomicInteger.decrementAndGet();
} else {
mHanlder.post(toast.mShow);
mHanlder.postDelayed(toast.mHide, toast.mDuration);
mHanlder.postDelayed(mActivite, toast.mDuration);
}
}

mQueue维护了Toast的队列,队列采用FIFO调度,每次调用show()方法触发activeQueue()方法,从队列中取出toast进行显示,然后定时取消。 我本想用SnackBar来替代,可SnackBar有下面几个问题要注意:

1. make()方法的第一个参数的view,不能是有一个ScrollView.

因为SnackBar的实现逻辑是往这个View去addView.而ScrollView我们知道,是只能有一个Child的.否则会Exception.

2. 如果大家在想把Toast替换成SnackBar.需要注意的是,Toast和SnackBar的区别是,前者是悬浮在所有布局之上的包括键盘,而SnackBar是在View上直接addView的.

所以SnackBar.show()的时候,要注意先把Keyboard.hide()了.不然,键盘就会遮住SnackBar.

想想还是自己维护了Toast的队列,那么首先要判断用户是否关闭了消息通知权限,如果没有关闭我还是想用系统提供的队列,网上了很多没有发现可用代码,只能自己看源码了。

com.android.server.notification.NotificationManagerService
private boolean 
noteNotificationOp(String pkg, int uid) {
Slog.v("notifications are disabled by AppOps for " + pkg);
return
false;
}
return
true;

} 看到这基本上知道 通过

noteOpNoThrow(AppOpsManager.uid, pkg) != AppOpsManager.AppOpsManager mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);mAppOps.noteOpNoThrow()
AppOpsManager.OP_POST_NOTIFICATION ??? is hide,不可用。这咱办。只能反射,来调用了。
private static finalString CHECK_OP_NO_THROW= "checkOpNoThrow";private static finalString OP_POST_NOTIFICATION= "OP_POST_NOTIFICATION";
public static booleanisNotificationEnabled(Context context){
AppOpsManager mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);ApplicationInfo appInfo = context.getApplicationInfo();String pkg = context.getApplicationContext().getPackageName();intuid = appInfo.uid;Class appOpsClass = null;/* Context.APP_OPS_MANAGER */try{
appOpsClass = Class.forName(AppOpsManager.class.getName());Method checkOpNoThrowMethod = appOpsClass.getMethod(CHECK_OP_NO_THROW,Integer.TYPE,Integer.TYPE,String.class);Field opPostNotificationValue = appOpsClass.getDeclaredField(OP_POST_NOTIFICATION);intvalue = (int)opPostNotificationValue.get(Integer.class);return((int)checkOpNoThrowMethod.invoke(mAppOps,value,uid,pkg) == AppOpsManager.MODE_ALLOWED);} catch(Exception e) {
e.printStackTrace();}
return true;}

听说到了android24 可以 UseNotificationManagerCompat.areNotificationsEnabled()来判断了。

github 下载。