解决通知关闭Toast失效问题

本文介绍了一种通过Hook系统INotificationManager的方法,使应用程序能够绕过通知权限检查的技术。主要通过将参数pkg固定为android,确保通知逻辑不被校验,从而实现在未开启通知权限的情况下显示Toast。文章提供了具体实现代码,包括如何创建代理对象和修改方法参数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

下图是系统INotificationManager的实现源码 当pkg = “android”时不会校验通知关闭逻辑
所以只需要hook INotificationManager 将参数pkg始终传“android” 就行了 


实现代码: 


/**
 * Created by linjinfa on 2018/2/5.
 * email 331710168@qq.com
 */
public class ToastUtil {

    private static Object iNotificationManagerObj;

    /**
     * @param context
     * @param message
     */
    public static void show(Context context, String message) {
        show(context, message, Toast.LENGTH_SHORT, Gravity.BOTTOM);
    }

    /**
     *
     * @param context
     * @param message
     */
    public static void showCenter(Context context, String message) {
        show(context, message, Toast.LENGTH_SHORT, Gravity.CENTER);
    }

    /**
     * @param context
     * @param message
     */
    public static void show(Context context, String message, int duration, int gravity) {
        if (TextUtils.isEmpty(message)) {
            return;
        }
        //后setText 兼容小米默认会显示app名称的问题
        Toast toast = Toast.makeText(context, null, duration);
        toast.setText(message);
        toast.setGravity(gravity, 0, 0);
        if(isNotificationEnabled(context)){
            toast.show();
        }else{
            showSystemToast(toast);
        }
    }

    /**
     * 业务相关的toast
     * @param context
     * @param message
     */
    public static void showCenterForBusiness(final Context context, final String message) {
        if (context != null) {
            if (context instanceof Activity) {
                ((Activity) context).runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        show(context, message, Toast.LENGTH_SHORT, Gravity.CENTER);
                    }
                });
            } else {
                show(context, message, Toast.LENGTH_SHORT, Gravity.CENTER);
            }
        }
    }

    /**
     * 显示系统Toast
     */
    private static void showSystemToast(Toast toast){
        try{
            Method getServiceMethod = Toast.class.getDeclaredMethod("getService");
            getServiceMethod.setAccessible(true);
            //hook INotificationManager
            if(iNotificationManagerObj == null){
                iNotificationManagerObj = getServiceMethod.invoke(null);

                Class iNotificationManagerCls = Class.forName("android.app.INotificationManager");
                Object iNotificationManagerProxy = Proxy.newProxyInstance(toast.getClass().getClassLoader(), new Class[]{iNotificationManagerCls}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //强制使用系统Toast
                        if("enqueueToast".equals(method.getName())
                                || "enqueueToastEx".equals(method.getName())){  //华为p20 pro上为enqueueToastEx
                            args[0] = "android";
                        }
                        return method.invoke(iNotificationManagerObj, args);
                    }
                });
                Field sServiceFiled = Toast.class.getDeclaredField("sService");
                sServiceFiled.setAccessible(true);
                sServiceFiled.set(null, iNotificationManagerProxy);
            }
            toast.show();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 消息通知是否开启
     * @return
     */
    private static boolean isNotificationEnabled(Context context) {
        NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(context);
        boolean areNotificationsEnabled = notificationManagerCompat.areNotificationsEnabled();
        return areNotificationsEnabled;
    }

}
<think>嗯,用户问的是Android中Toast不工作的原因和解决办法。首先,我得回忆一下Toast的基本用法,可能用户哪里出错了。Toast需要主线程才能显示,如果用户在子线程里直接调用的话,可能会不显示。这时候应该用runOnUiThread或者Handler来切换到主线程。 然后上下文的问题,如果传入的Context不对,比如用了getApplicationContext(),可能在某些情况下Toast无法正确显示。正确的做法应该是使用Activity的Context,或者在Fragment里用getActivity()。 还有应用处于后台的情况,Android 10及以上可能限制了后台应用显示Toast,这时候可能需要用Snackbar代替,或者检查应用是否在前台。 权限问题的话,一般Toast不需要特殊权限,但如果用户修改了系统设置,比如关闭通知权限,也可能导致Toast不显示。这时候需要提示用户检查权限设置。 另外,多次快速调用Toast可能导致队列问题,应该先取消之前的Toast再显示新的。或者考虑用Snackbar替代,因为Snackbar有更好的队列管理。 接着,我需要检查用户提供的引用内容,看是否有相关的信息。引用1提到不同硬件厂商的OpenGL ES驱动导致不一致行为,这可能和Toast无关。引用2关于assets目录的位置,也和Toast无关。引用3讲的是Service的创建,如果Toast在Service里使用,可能需要主线程的问题。引用4是测试框架,可能和测试Toast有关,但用户的问题不是测试中的Toast失效。 所以综合来看,主要原因是主线程、上下文错误、后台限制、权限设置、多次调用以及自定义布局错误。解决办法包括确保主线程调用、使用正确的Context、处理后台情况、检查权限、管理Toast实例和检查自定义布局。 然后生成相关问题的时候,要考虑用户可能接下来会问的,比如主线程和子线程的区别,如何检查Context是否正确,后台限制的具体情况,或者如何自定义Toast布局的正确方法。</think>### Android Toast不工作的原因及解决方法分析 1. **主线程限制问题** - Toast必须在主线程调用才能正常显示,子线程直接调用会导致显示失败 - 解决方法:使用`runOnUiThread()`或`Handler`切换到主线程 ```java new Thread(() -> { runOnUiThread(() -> Toast.makeText(context, "message", LENGTH_SHORT).show()); }).start(); ``` 2. **上下文(Context)错误** - 使用已销毁的Activity上下文会导致Toast无法显示 - 正确做法:在Activity中使用`this`,Fragment中使用`getActivity()` - 特殊情况:在Service中需要添加`FLAG_ACTIVITY_NEW_TASK`标志[^3] 3. **应用处于后台限制(Android 10+)** - Android 10开始限制后台应用显示Toast - 解决方法:检查应用是否在前台,或改用Snackbar进行提示 4. **通知权限被禁用** - 部分厂商系统(如MIUI)需要单独开启通知权限 - 检查路径:设置 > 应用管理 > 通知权限 5. **重复调用导致队列问题** - 快速连续调用可能造成Toast队列异常 - 推荐做法:先调用`cancel()`再显示新Toast ```java Toast previousToast; void showToast(String msg) { if(previousToast != null) previousToast.cancel(); previousToast = Toast.makeText(context, msg, LENGTH_SHORT); previousToast.show(); } ``` 6. **自定义布局错误** - 自定义布局未正确处理View层级会导致显示异常 - 建议使用标准Toast样式,或改用Snackbar实现复杂样式
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值