使用Toast的注意项

在Android中Toast的使用频率是很高的,一是系统自带,二是使用方便,三是可以跨页面显示。但是在使用中还是需要注意一些情况,否则可能导致崩溃。

在子线程中使用

子线程中使用Toast是可以的,但是需要先调用Looper.prepare(),然后再show Toast。子线程Toast参考代码。

new Thread() {
     @Override
     public void run() {
        super.run();
        Looper.prepare();
        Toast.makeText(MainActivity.this,"toast",Toast.LENGTH_SHORT).show();
        Looper.loop();
    }
}.start();

连续弹窗

默认情况下连续弹窗的效果是先弹第一个,再弹第二个,直到所有的都弹窗结束,延迟很大效果不好。解决方法有几个,

  1. 如果toast在显示则直接setText,不显示就弹窗。这种方法需要自己加时间戳判断,因为时间有误差,有可能导致某些弹窗显示不了。
  2. 先调用cancel(),再调用show()。因为系统版本的差异,这种方法在有些手机上也会无法显示。
  3. 在第二种的基础上加一步重新创建的过程,但是内存会增加少许,不过不影响。

在Android7.1上的问题

由于谷歌的问题,在android7.1上如果UI界面卡顿,show Toast会崩溃,具体原因可以参考这个链接https://tech.meituan.com/2018/03/29/toast-snackbar-replace.html。解决方法是利用反射设置一个自定义的Handler,用try,catch捕获异常,关键代码如下

    private static void hookN(Toast toast) {
        try {
            Field fieldTN = Toast.class.getDeclaredField("mTN");
            fieldTN.setAccessible(true);
            Object mTN = fieldTN.get(toast);
            Field fieldHandler = fieldTN.getType().getDeclaredField("mHandler");
            fieldHandler.setAccessible(true);
            Handler handler = (Handler) fieldHandler.get(mTN);
            fieldHandler.set(mTN, new SafeHandler(handler));
        } catch (Exception ignored) {
        }
    }

没有通知权限

在某些版本的系统上,如果没有通知权限,Toast也无法显示了。这个问题有两种解决方法,一种是在本页面添加这个Toast页面进行显示,相当于重写一遍Toast的实现方式,但是这个自定义的无法跨页面显示。另外一种方法是利用反射和动态代理方式把Toast的包名替换为系统包名android,如果Toast的PackageName是系统包名不会去检查权限。关于这一块儿的源码在NotificationManagerService中,关键代码如下

private final IBinder mService = new INotificationManager.Stub() {
        @Override
        public void enqueueToast(String pkg, ITransientNotification callback, int duration)
        {
            ……
            final boolean isSystemToast = isCallerSystemOrPhone() || ("android".equals(pkg));
            final boolean isPackageSuspended =
                    isPackageSuspendedForUser(pkg, Binder.getCallingUid());
            // 如果不是系统Toast则检查权限
            if (ENABLE_BLOCKED_TOASTS && !isSystemToast &&
                    (!areNotificationsEnabledForPackage(pkg, Binder.getCallingUid())
                            || isPackageSuspended)) {
                Slog.e(TAG, "Suppressing toast from package " + pkg
                        + (isPackageSuspended
                                ? " due to package suspended by administrator."
                                : " by user request."));
                return;
            }
        ……

       }
    // 省略
    ……
    }

动态修改Toast的包名方法如下

    private static Object iNotificationManagerObj;

    /**
     * hook
     * @param toast toast
     */
    private static void hook(Toast toast) {
        try {
            //hook INotificationManager
            if (iNotificationManagerObj == null) {
                Method getServiceMethod = Toast.class.getDeclaredMethod("getService");
                getServiceMethod.setAccessible(true);
                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 {
                        if ("enqueueToast".equals(method.getName())) {
                            args[0] = "android";
                        }
                        return method.invoke(iNotificationManagerObj, args);
                    }
                });
                Field sServiceFiled = Toast.class.getDeclaredField("sService");
                sServiceFiled.setAccessible(true);
                sServiceFiled.set(null, iNotificationManagerProxy);
            }
        } catch (Exception e) {
            LogUtils.e(e);
        }
    }

详细代码地址https://github.com/jklwan/NoteApplication/blob/master/app/src/main/java/com/chends/note/utils/ToastUtils.java

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spinner 是 Android 中常用的一个下拉列表控件,可以用来展示一个列表并且允许用户选择其中一个选。下面是 Spinner 控件的基本使用步骤: 1. 在 XML 布局文件中添加 Spinner 控件: ```xml <Spinner android:id="@+id/spinner" android:layout_width="wrap_content" android:layout_height="wrap_content" /> ``` 2. 在 Java 代码中获取 Spinner 控件的实例,并为其设置数据源和选择事件监听器: ```java Spinner spinner = findViewById(R.id.spinner); ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.planets_array, android.R.layout.simple_spinner_item); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); spinner.setAdapter(adapter); spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { // 处理选择事件 String selectedItem = (String) parent.getItemAtPosition(position); Toast.makeText(MainActivity.this, "You selected: " + selectedItem, Toast.LENGTH_SHORT).show(); } @Override public void onNothingSelected(AdapterView<?> parent) { // 未选择任何时的处理 } }); ``` 在上述代码中,我们首先通过 `ArrayAdapter.createFromResource()` 方法创建了一个适配器,并将其设置为 Spinner 的数据源。然后,我们为 Spinner 设置了选择事件监听器,当用户选择某一时,会回调 `onItemSelected()` 方法,此时我们可以通过 `parent.getItemAtPosition(position)` 方法获取用户选择的。同时,我们还设置了下拉列表的布局样式。 最后需要注意的是,在 XML 布局文件中的 Spinner 控件可能需要设置一些额外的属性,例如 `android:entries` 可以直接设置数据源,`android:prompt` 可以为 Spinner 设置提示文本等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值