最近在做项目时,遇到一个问题,有部手机就是Toast弹不出来。仔细想想可能权限的问题。后面网上搜索给出如下答案:
跟踪Toast的源代码,make
方法省略,做了一些初始化的工作,
show
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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方法
1 2 3 4 5 6 7 8 9 | 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队列,处理相关的逻辑,进行显示,定时取消。关键代码
1 2 3 4 5 6 7 8 9 10 | 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进行显示,然后定时取消。
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 booleannoteNotificationOp(String pkg, int uid) {
if (mAppOps.noteOpNoThrow(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg) != AppOpsManager.MODE_ALLOWED) {return false;
}
return true;
}
mAppOps.noteOpNoThrow(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg) != AppOpsManager.MODE_ALLOWED 来判断了AppOpsManager mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); mAppOps.noteOpNoThrow()AppOpsManager.OP_POST_NOTIFICATION ??? is hide,不可用。这咱办。只能反射,来调用了。private static final String CHECK_OP_NO_THROW = "checkOpNoThrow"; private static final String OP_POST_NOTIFICATION = "OP_POST_NOTIFICATION";public static boolean isNotificationEnabled(Context context){ AppOpsManager mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); ApplicationInfo appInfo = context.getApplicationInfo(); String pkg = context.getApplicationContext().getPackageName(); int uid = 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); int value = (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 下载。