Toast不是乱弹的

一  前言

Toast在android里面弹的很多了,但是在分线程里面弹会怎么样呢? 有些同学会说,那还不是和平时一样的弹?!其实,也不尽然,不信?请继续往下面看。


二 分线程弹Toast引发的问题

如果在分线程里面直接弹Toast,比如下面这样,button点击的时候启动一个线程,弹出一个Toast:


那么就会得到一个应用程序异常终止的错误弹窗。

原因如下:
复制内容到剪贴板
代码:
E/AndroidRuntime( 8978): FATAL EXCEPTION: Thread-2221

E/AndroidRuntime( 8978): Process: com.example.androidtest, PID: 8978

E/AndroidRuntime( 8978): java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

E/AndroidRuntime( 8978):         at android.os.Handler.<init>(Handler.java:200)

E/AndroidRuntime( 8978):         at android.os.Handler.<init>(Handler.java:114)

E/AndroidRuntime( 8978):         at android.widget.Toast$TN.<init>(Toast.java:347)

E/AndroidRuntime( 8978):         at android.widget.Toast.<init>(Toast.java:100)

E/AndroidRuntime( 8978):         at android.widget.Toast.makeText(Toast.java:254)

E/AndroidRuntime( 8978):         at com.example.androidtest.MainActivity$1$1.run(MainActivity.java:40)

E/AndroidRuntime( 8978):         at java.lang.Thread.run(Thread.java:818)

D/ActivityManager(  939): addErrorToDropBox processName = com.example.androidtest
意思是如果没有调用Looper.prepare(),就不能初始化Handler对象。看错误堆栈,是Toast.makeText()的时候会初始化Handler对象。

这个调用过程如下:

1. 从Toast.makeText(...)开始
1 public static Toast makeText(Context context, CharSequence text, int duration) {
2         Toast result = new Toast(context);
3  
4         ...
5  
6         return result;
7     }
它会初始化一个Toast对象,继续看Toast的构造函数:

2.
1 public Toast(Context context) {
2         mContext = context;
3         mTN = new TN();
4         ...
5     }
在Toast的构造函数里面,会初始化一个TN对象,这个TN对象是什么东西呢?

3.
1 private static class TN extends ITransientNotification.Stub {
2 final Handler mHandler = new Handler();   
3 ...
4 }
这个TN对象是Binder对象的子类,显而易见,它是跨进程通信用的。其实它就是和系统Toast服务通信用的,应用程序需要把自己的Toast请求发送给系统Toast服务,告诉它,我需要弹一个什么样的Toast。
所以需要TN这个东东。

而TN这个东东需要初始化Handler成员变量,所以就会调用到Handler初始化这边。

4.  Handler初始化
01 public Handler(Callback callback, boolean async) {
02          
03  
04         mLooper = Looper.myLooper();
05         if (mLooper == null) {
06             throw new RuntimeException(
07                 "Can't create handler inside thread that has not called Looper.prepare()");
08         }
09          
10     }
Handler初始化的时候会拿出与当前线程相关的Looper对象,如果不存在,也就是没有调用过Looper.prepare()方法的话,就会报错。

这就是上面错误的来源啦!

三 如何解决?

知道原因后,就比较好解决了,于是就出现了下面的代码:




在分线程弹出Toast之前把Looper准备好,实验表明,这样确实可以达到效果。可以把Toast弹出来。

但是这样有个问题,就是会导致分线程永远不会退出。这个原理大家看看Looper.loop()是怎么实现的就知道了,它里面是一个死循环。

这样的话,会导致进程里面的线程越来越多,这个本人是不推荐的。

四 另外一种解决方法

除了第三种方法之外,我们还有种方法,就是把Toast丢到主线程去弹出来。

也很简单,主要初始化一个主线程的Handler,然后调用Handler.postRunnable()方法就可以了。,如下:




在开发中遇到的“`Toast`提示不是封闭类”错误,通常出现在尝试对`Toast`类进行某些不符合其设计意图的操作时,例如尝试继承或修改其内部行为。`Toast`类在Android中是一个封装好的非开放类(即非`open`类),这意味着它不允许被继承或重写其方法,这是为了确保系统的稳定性和安全性[^1]。 ### 解决方案 #### 1. **避免继承 `Toast` 类** 由于`Toast`不是开放类,直接继承`Toast`并尝试重写其方法会导致编译错误。正确的做法是使用`Toast`的现有API来创建和显示提示信息,而不是尝试修改其行为。例如: ```kotlin Toast.makeText(context, "这是一个Toast提示", Toast.LENGTH_SHORT).show() ``` #### 2. **使用封装函数替代继承** 如果需要对`Toast`进行统一的样式或行为管理,可以通过封装一个工具类或函数来实现,而不是继承`Toast`。例如: ```kotlin object CustomToast { fun show(context: Context, message: String) { val toast = Toast.makeText(context, message, Toast.LENGTH_SHORT) // 可以在此处统一设置样式或位置 toast.setGravity(Gravity.CENTER, 0, 0) toast.show() } } ``` 然后在代码中调用: ```kotlin CustomToast.show(context, "自定义Toast提示") ``` #### 3. **使用 `Snackbar` 或其他替代方案** 如果发现`Toast`在某些场景下无法满足需求(例如需要交互性或更复杂的UI),可以考虑使用`Snackbar`或其他自定义的视图组件来替代。`Snackbar`提供了更丰富的用户反馈方式,并且支持点击操作[^1]。 ```kotlin Snackbar.make(view, "这是一个Snackbar提示", Snackbar.LENGTH_LONG) .setAction("确定") { // 点击后的操作 } .show() ``` #### 4. **检查 Kotlin 编译器设置(仅限 Kotlin)** 如果使用的是 Kotlin 编写代码,并且错误提示中提到“不是密封类”或“不是封闭类”,请检查是否启用了 Kotlin 的密封类(sealed class)或封闭类(final class)相关特性。默认情况下,Kotlin 中的类是封闭的,不能被继承,除非显式使用 `open` 关键字。`Toast`本身是 Java 类,但在 Kotlin 中使用时也应遵循这一规则。 ### 总结 “`Toast`提示不是封闭类”错误的根本原因在于尝试对一个非开放类进行继承或重写。解决方案包括:避免继承`Toast`、使用封装函数统一管理提示逻辑、考虑使用`Snackbar`等替代组件,以及检查 Kotlin 的类继承规则。通过这些方式,可以有效避免此类编译错误并提升用户体验。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值