Android Toast与Looper的深入研究,Toast是否属于修改UI界面


偶然看到csdn的一篇帖子非常有趣

public void onClick(View view) {
new Thread() {
@Override
public void run() {

Looper.prepare();
Toast.makeText(MainActivity.this, "Hello Toast!!", Toast.LENGTH_SHORT).show();
Looper.loop();

}
}.start();
}
大致就是说,如上代码中,Toast可以在子线程中正常显示,但是为什么将显示吐司的代码替换为textView.setText()就不行了?

Looper.prepare()和Looper.loop()的疑问

我们先要理解为什么如上代码可以在子线程中显示Toast:
            Looper.prepare();
Toast.makeText(MainActivity.this, "Hello Toast!!", Toast.LENGTH_SHORT).show();
Looper.loop();

首先解释下这个流程,点击按钮时,启动了一个子线程,线程中
先是调用了Looper.prepare()方法,这个方法作用是在当前线程创建一个Looper对象
之后执行了显示吐司的代码
然后调用了Looper.Loop()方法,这个方法是开启循环

这样就可以正常显示吐司
看到这里就非常好奇,这三句代码必须按照这样的顺序吗?可不可以调换位置呢?

那么就来试试:
            Looper.prepare();
Looper.loop();
Toast.makeText(MainActivity.this, "Hello Toast!!", Toast.LENGTH_SHORT).show();
这样一来,运行发现Toast不能显示了,而且没有任何报错?这又是为什么?
因为Looper.loop()方法是一个死循环,它之后的代码不会被执行。除非调用looper对象的quit方法退出循环

好的,那么换一种顺序:
            Toast.makeText(MainActivity.this, "Hello Toast!!", Toast.LENGTH_SHORT).show();
Looper.prepare();
Looper.loop();
那么。。这个就不用试了,相当于在子线程中直接就去显示Toast,第一行就报异常了

为什么子线程中,Toast可以借助Looper来显示?

其实这个问题,在子线程中直接显示吐司,认真分析出现的异常,就可以找到答案
(从错误日志看系统源码的时候,建议使当前项目的sdk编译版本,目标版本和运行的安卓设备的安卓版本,三者保持一致,这样可以直接根据行号找到对应源码)

如下就是子线程中显示吐司出现的异常:

03-11 09:41:50.097 2163-2231/com.demo E/AndroidRuntime: FATAL EXCEPTION: Thread-113
                                                               Process: com.demo, PID: 2163
                                                               java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
                                                                   at android.os.Handler.<init>(Handler.java:200)
                                                                   at android.os.Handler.<init>(Handler.java:114)
                                                                   at android.widget.Toast$TN.<init>(Toast.java:327)
                                                                   at android.widget.Toast.<init>(Toast.java:92)
                                                                   at android.widget.Toast.makeText(Toast.java:241)
                                                                   at com.demo.MainActivity$1.run(MainActivity.java:22)

我们可以按照这个错误信息逐步跟踪:

at com.demo.MainActivity$1.run(MainActivity.java:22)
Toast.makeText(MainActivity.this, "Hello Toast!!", Toast.LENGTH_SHORT).show();
这一行不用解释

at android.widget.Toast.makeText(Toast.java:241)
public static Toast makeText(Context context, CharSequence text, int duration) {
Toast result = new Toast(context);
Toast的makeText方法中,创建了Toast对象,等于调用了Toast的构造方法

at android.widget.Toast.<init>(Toast.java:92)
public Toast(Context context) {
mContext = context;
mTN = new TN();
Toast的构造方法中又创建了TN对象,调用了TN的构造方法

at android.widget.Toast$TN.<init>(Toast.java:327)
private static class TN extends ITransientNotification.Stub {
final Runnable mShow = new Runnable() {
@Override
public void run() {
handleShow();
}
};

final Runnable mHide = new Runnable() {
@Override
public void run() {
handleHide();
// Don't do this in handleHide() because it is also invoked by handleShow()
mNextView = null;
}
};

private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
final Handler mHandler = new Handler(); // 327行
TN,很明显是一个AIDL进程间通讯,很容易想到是在调用系统服务。在它的327行,发现他创建了一个Handler对象

通过查看Handler的构造方法
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
最终发现,Handler的构造方法会获取当前线程的Looper,如果当前线程没有Looper,则抛出异常

那么此时终于明白了,为什么主线程中可以直接显示吐司,而子线程中就必须调用Looper.prepare()和Looper.loop(),才能显示吐司?

原因就是:
Toast的内部会创建Handler,而Handler又依赖于Looper。
安卓中,主线程会默认创建一个Looper,并且开启循环;而子线程并不会自动创建Looper,需要我们手动调用prepar和loop方法,才能创建并开启循环。

也就是说,我们使用Looper.prepare()和Looper.loop(),显示的Toast,是在子线程中借助Handler消息队列执行的。

得出结论:
Toast可能是属于修改UI界面,但是它没有被ViewRootImpl进行线程检测。它依赖于Handler机制、Looper、AIDL进程通讯,系统服务的调用。
textView.setText()则被ViewRootImpl.checkThread()检测线程,必须在主线程中进行。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值