Android关闭通知消息权限无法弹出Toast的解决方案

背景

在之前一段时间里经常有用户反馈无法看到薄荷app弹出的消息提示,导致用户对一些使用场景感到很困惑。猜测可能是由于用户关闭了薄荷app的通知消息的权限导致的问题,测试后发现果真如此。

原因

跟踪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中的TNTN其实就是一个aidlstub实现,相当于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进行显示,然后定时取消。

总结

Google把Toast视为系统级别的消息通知,其实是不合理的,在app前台可见的情况下,有些关键信息需要提供给用户,如果关闭了消息通知权限,那么用户就会看不到这些关键的提示就会很困惑,直接影响用户体验,并且在Android5.0之后在设置中就可以关闭app的消息权限,Toast作为一个系统级别的消息提示设计者真的挺缺心眼的。虽然MD中有SnackBar作为消息提示,其原理就是在当前界面找到根视图,在根视图addView(),达到弹出提示的目的,但是请原谅我的审美不够高,SnackBar真的很丑,并且最近Google又支持BottomSheet,两个叠加到一起,我的天,画面太美,我不敢看。还有就是必须要拿到当前的content,对于用Application的context弹出toast的方案来说适配起来修改比较麻烦。很明显,Toast就应该是app级的消息提示,我们的解决方案也正是这么做的。

参考

本文地址 http://w4lle.github.io/2016/03/27/Android关闭通知消息权限无法弹出Toast的问题解决方案/