栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Toast兼容

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Toast兼容

背景

部分机型在用户通知权限关闭之后,toast弹不出来,导致app变成哑巴应用,在需要toast提示用户的地方,没有任何展示。
使用三星S8(android9.0)在关闭通知权限之后可以复现该场景。
我们不能要求用户开启通知权限,那么需要兼容用户关闭通知权限的场景。

具体分析
我们看一下toast的show()方法做了什么:

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) {
        // Empty    }
     }

调用了INotificationManager的enqueueToast方法,可以看出toast使用了notification的相关api,需要通知权限。
对于一些定制的系统,应该是实现了自己的aidl代理类,重写了enqueueToast方法,可以做到不受通知栏权限的限制。比如三星s8的该方法名称就是enqueueToastLog。

看下系统的INotificationManager代理类NotificationManagerService:

@Override 
public void enqueueToast(String pkg, ITransientNotification callback, int duration)
{
if (DBG) {
Slog.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback
+ " duration=" + duration);
}       
       
if (pkg == null || callback == null) {
      Slog.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback);
      return ;
     }
final boolean isSystemToast = isCallerSystemOrPhone() || ("android".equals(pkg));
    ...}

通过isSystemToast这个变量判断是不是系统的toast,如果是系统toast那么就不受通知权限的影响了。
尝试hook enqueueToast方法,修改packgae名称为android,让isSystemToast这个参数为true。

Method getServiceMethd = Toast.class.getDeclaredMethod("getService");
            getServiceMethd.setAccessible(true);

            final Object iNotificationManager = getServiceMethd.invoke(null);
            Class iNmCls = Class.forName("android.app.INotificationManager");
            Object proxy = Proxy.newProxyInstance(toast.getClass().getClassLoader(), new Class[]{iNmCls}, new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    if("enqueueToast".equals(method.getName()) || "enqueueToastEx".equals(method.getName()) || "enqueueToastLog".equals(method.getName())){
                        args[0] = "android";
                    }
                    return method.invoke(iNotificationManager,args);
                }
            });
            Field sServiceField = Toast.class.getDeclaredField("sService");
            sServiceField.setAccessible(true);
            sServiceField.set(null,proxy);

但是Android10上报错,如下:

看来Android10.0上对uid也做了限制,不能做到兼容全部版本。

我们知道Toast是使用WindowManager创建的,那么我们也可以手动创建一个toast,展示在当前的activity中,通过定时任务关闭它,实现toast的效果。
优点是在用户通知权限关闭之后也能展示toast。
因windowmanager是跟当前activity绑定,那么跟系统的toast比起来,在当前activity关闭之后就不再继续展示了。

实现方式
在用户通知权限开启的时候,使用原生toast,未开启时使用windowmanager添加toast。
在baseApplication中调用init:

    public static void init(Application application){

        if (mToast != null) {
            mToast.cancel();
        }
        if(DuToastCompat.isNotificationEnabled(application)){
            if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1) {
                mToast = new SafeToast(application);
            }else{
                mToast = new DuToast(application);
            }
        }else{
            mToast = new SupportNotificationToast(application);
        }
    
        if (sStrategy == null) {
            setToastHandler(new ToastStrategy());
        }
    }

对应的类 SafeToast:兼容7.1版本的主线程阻塞问题
NotificationSupportToast:通知权限未开启时候使用
DuToast:封装原生toast
ToastStrategy:实际是个handler,结合队列控制toast的show和cancel时机。
可实现最多三个toast依次展示,不会因后续toast的弹出导致之前toast显示时间过短。

展示在底部的toast沿用之前的调用方式ToastUtil.show(),其他样式可直接调用DuToastUtils中的方法。

showToastInCenter //显示在中间位置的toast
showToastWithIconInCenter //显示在中间位置的toast,带图片,一行文字
showToastIconWithTwoTextInCenter //显示在中间位置的toast,一张图片,两行文字

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/340489.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号