Android 无须权限的Toast解决方案 - 本仁笔记

4,088 阅读2分钟
原文链接: ie8384.com

问题:

在5.0以上系统中被用户关闭消息通知后,系统的Toast也一起无法显示了。

原因:Toast通过NotificationManagerService 维护一个toast队列,然后通知调用 WindowManager 添加view,那么当用户关闭通知权限后,NotificationManagerService受到影响,导致Toast无法显示。

可以考虑的解决方案:

1、仿照系统的Toast然后用Handler+Queue自己的消息队列来维护,让其不受NotificationManagerService影响,显示还是使用WindowManager来addview

通过WindowManager addview 来实现,虽然Type选的TYPE_TOAST,在MIUI 8上,要是没有开权限,还是显示不了

2、通过Dialog、PopupWindow来编写一个自定义通知。

跟原来toast的初衷不一致,toast是不会获取焦点的,但是dialog和popupwindow会获取焦点,会盖住下面的内容。

3、通过直接去当前页面最外层content布局来添加View。

需要传activity,才可以获取最外层的content;而且上面覆盖dialogFragment时,通过activity的content添加的view会被dialogfragment覆盖。

本文采用的方法

本文采用了第三种方法,获取当前最外层ViewGroup,通过ViewGroup AddView来显示Toast, 并解决了上述提到的问题。

1、使用方法 需要先初始化:

SHToast.init(new SHToast.ToastListener() {
@Override
public Activity getCurrentActivity() {
return ActivityLifecycleCallbacks.getInstance().getResumeActivity();
}
});

然后可以像Toast那样使用,

 SHToast.makeText(context, "默认Toast样式",
SHToast.LENGTH_SHORT).show();

或者 直接调用

SHToast.toast("测试测试");

如果没有初始化,可以调用

SHToast.toast(activity, "测试测试");

源码见:SHToast

2、实现原理

  • 首先,消息队列还是采用Handler+Queue来维护.
  • 传入activity,需要在初始化时调用:
    SHToast.init(new SHToast.ToastListener() {
    @Override
    public Activity getCurrentActivity() {
    return ActivityLifecycleCallbacks.getInstance().getResumeActivity();
    }
    });

    我在ActivityLifecycleCallbacks里维护了一个activity列表,可以获取当前resume的activity。

    ActivityLifecycleCallbacks实现了 Application.ActivityLifecycleCallbacks

这样每次toast 都会调用目前resume的activity,如果没有activity resume,就不显示toast了。

  • 解决dialogFragment覆盖的问题,关键过程就是如何获取最外层viewgroup。
    • 首先判断是否有android.app.Fragment appFragment,如果有,循环直到获取最外层ViewGroup,然后获取content元素
      container = rootView.findViewById(android.R.id.content);
      如果没有获取到,就到下一步;
    • 然后判断是否有可见的android.support.v4.app.Fragment,同理,如果有,循环直到获取最外层ViewGroup,然后获取content元素
      container = rootView.findViewById(android.R.id.content);
      如果没有获取到,就到下一步;
    • 获取activity的最外层viewgroup,
      container = (ViewGroup) activity.findViewById(android.R.id.content);

    关键代码如下:

private static boolean initToastView(Activity activity, ToastMsg msg) {
if (null == activity) {
return false;
}
FragmentActivity fragmentActivity;
android.support.v4.app.Fragment visibleFragment = null;
android.app.Fragment appFragment = null;
Bundle bundle = new Bundle();
bundle.putInt("key", 0);
try {
appFragment = activity.getFragmentManager().getFragment(bundle, "key");
} catch (Exception e) {

}
if (null != appFragment) {
View rootView = appFragment.getView();
ViewParent viewParent = null;
while (null != rootView.getParent()) {
viewParent = rootView.getParent();
if (null != viewParent && viewParent instanceof ViewGroup) {
rootView = (ViewGroup) viewParent;
} else {
break;
}
}
container = (ViewGroup) rootView.findViewById(android.R.id.content);
}

if (null == container) {
if (activity instanceof FragmentActivity) {
fragmentActivity = (FragmentActivity) activity;
List<android.support.v4.app.Fragment> fragments = fragmentActivity.getSupportFragmentManager().getFragments();
if (null != fragments) {
for (android.support.v4.app.Fragment fragment : fragments) {
if (fragment != null && fragment.getUserVisibleHint()) {
visibleFragment = fragment;
break;
}
}
}
}
if (null != visibleFragment) {
View rootView = visibleFragment.getView();
container = (ViewGroup) rootView.findViewById(android.R.id.content);
}
}

if (null == container) {
container = (ViewGroup) activity.findViewById(android.R.id.content);
}

if (null == container) {
return false;
}

……
return true;
}