1.概述
在android中Toast经常会被用到,在主线程中直接使用Toast没任何问题,消息显示正常。一旦将Toast放入子线程中的时候,消息不会有任何响应。网上查了解决办法,直接将写在子线程中的Toast前后加上Looper.prepare(),其后加上Looper.loop(),就可以正常显示了。例:
new Thread() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(getApplicationContext(), "发生未知错误!", Toast.LENGTH_SHORT).show();
Looper.loop();
}
}.start();
原来一直是只知其然不知其所以然,现在就看下内在原理,彻底了解为什么。(不建议这么使用,会造成新的问题。当前子线程因为looper的存在,导致一直处于未销毁状态,而占有内存。建议直接将消息发送到UI线程中进行显示。)
2.Looper
首先,来了解下Looper。
看看关于looper这个类的描述说明。
* Class used to run a message loop for a thread. Threads by default do
* not have a message loop associated with them; to create one, call
* {@link #prepare} in the thread that is to run the loop, and then
* {@link #loop} to have it process messages until the loop is stopped.
*
* <p>Most interaction with a message loop is through the
* {@link Handler} class.
(大意就是,looper这个类是用来给线程thread运行消息循环的。线程们在默认情况下并没有一个消息循环loop与它们相关联;用looper类中的方法prepare可以在线程中创建一个looper对象用来执行loop用的,然后调用loop方法时就会让之前创建的looper对象来处理循环中的消息,直到循环停止。
Looper与消息循环的大部分交互是通过Handler类。)
看到这,就可以知道looper主要的方法就prepare()和loop()。
1.Looper.prepare()
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {//第5行
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
从第5行可以看出Looper.prepare()方法中,首先会判断sThreadLocal中是否已经有Looper存在,如果有就抛异常,没有就实例化一个looper set进去。从这就可以看出每个线程中只可能存在一个Looper。
实例化Looper时,会创建一个MessageQueue,同时将当前线程和Looper绑定。
2.Looper.loop()
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mL