以下是我看了何红辉大神的《Android开发进阶——从小工到专家》和主席的《Android开发艺术探索》关于Handler做的一些笔记和总结:
为什么只允许在主线程中更新UI?网络等耗时操作要放在子线程?
因为多个线程并发操作UI,可能会产生线程不安全现象,因此规定只能在主线程中更新UI。而网络请求等操作如果放在主线程,考虑到网速等原因,可能会造成ANR(程序未响应),导致主线程被阻塞,所以这类耗时操作应该放在子线程。
Handler消息机制处理流程:
UI线程:即主线程,系统在创建主线程的时候,会在主线程中初始化一个Looper对象,同时也会创建一个相应的MessageQueue消息队列。
MessageQueue:虽然叫消息队列,但是它的内部实现并不是用的队列,实际上它是用单链表的数据结构来存储消息列表。
Handler:发送和处理消息。如果要Handler正常工作,在当前线程中必须要有一个Looper对象。
Looper:每个线程只能有一个Looper对象,在主线程被创建的时候,会在主线程中初始化一个Looper对象,调用Looper的loop方法后,会陷入一个无限的循环中——每当发现MessageQueue中存在一条消息,就会将它取出,传递到Handler的handleMessage方法中。
主线程与Handler、Looper、MessageQueue、ThreadLocal的关系?
我们都知道主线程在创建时,会自动初始化一个Looper对象,并且会创建一个消息队列,同时开启消息循环。主线程的这一系列操作是在ActivityThread.main方法中创建的,该函数是Android应用程序的入口,我们现在来看源代码:
Looper.prepareMainLooper;//1.创建消息循环Looper ActivityThread thread = new ActivityThread; thread.attach(false); if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler; } if (false) { Looper.myLooper.setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); } Looper.loop;//2.执行消息循环 throw new RuntimeException("Main thread loop unexpectedly exited");
从源代码可以看出,主线程通过调用prepareMainLooper方法来创建Looper,然后执行loop方法来开启消息循环。
我们再来看Looper源代码中prepareMainLooper等方法。
public static void prepareMainLooper { prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); }sMainLooper =myLooper; } }private static void prepare(boolean quitAllowed) { if (sThreadLocal.get != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); }public static Looper myLooper {
returnsThreadLocal.get;
}
prepareMainLooper方法中又调用了prepare方法,而prepare方法最后使用了
sThreadLocal.set(new Looper);创建了Looper对象,以及将Looper对象封装到了ThreadLocal线程中。
而消息队列MessageQueue封装在Looper中,Looper的构造函数中会创建一个MessageQueue消息队列。
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread; }
接下来我们来看Handler是如何跟Looper、MessageQueue关联起来的,看Handler源代码中的构造方法。
public Handler(Callback callback, boolean async) { //代码省略 ...... mLooper = Looper.myLooper;//获取Looper if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare"); } mQueue = mLooper.mQueue;//获取消息队列 mCallback = callback; mAsynchronous = async; }
Handler通过调用Looper.myLooper方法来获取Looper对象,通过mLooper.mQueue来获取消息队列,这样当Handler写在主线程中,就跟Looper、MessageQue有关系了。
我们再来看主线程ActivityThread后面又执行的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; //获取消息队列 //代码省略 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 Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } msg.target.dispatchMessage(msg); //处理消息 //代码省略 msg.recycleUnchecked; } }
从源代码中可以看出,其实loop方法就是建立了一个死循环即消息循环,不断的从消息队列中取除消息,然后交给dispatchMessage方法处理消息,而target是Handler对象,因此dispatchMessage是Handler中的方法。
我们再来看dispatchMessage方法是如何处理消息的。
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
dispatchMessage方法实际上就是通过判断callback,当我们使用Handler来sendMessage时通常不会设置callback的值,因此,就相当于再调用主线程中Handler的handleMessage方法。
这样看有点头晕,为了能够更好的理清之间的关系,我又做了关系图,整个过程就是:
处理过程:首先要在主线程中创建一个Handler对象,并重写handleMessage方法,然后当子线程需要进行UI操作时,就会创建一个Message对象,并通过Handler将消息发送出去。之后这条消息就被添加到MessageQueue消息队列中,Looper通过执行loop方法会建立一个死循环即消息循环,通过queue.next方法不断从MessageQueue中取出消息,再调用Handler的dispatchMessage方法处理消息,即Handler的handleMessage方法。
Handler写在主线程中:
由于主线程在创建时,会在主线程中初始化一个Looper对象,所以只要创建Handler对象。
Handler写在子线程中:
我们就要自己创建一个Looper对象
①调用Looper.prepare方法即可为当前线程创建一个Looper对象,并会配置相应的MessageQueue。
②在子线程中创建Handler对象,重写handleMessage方法。
③调用Looper.loop方法启动Looper循环,不断从MessageQueue中取出消息。
④最后注意该子线程必须在主线程中启动,new xxThread.start;
class LooperThread extends Thread { * public Handler mHandler; * * public void run { * Looper.prepare; * * mHandler = new Handler { * public void handleMessage(Message msg) { * // process incoming messages here * } * }; * * Looper.loop; * } * }
Handler是如何获取到当前线程的Looper呢?
通过ThreadLocal,ThreadLocal可以在不同的线程中互不干扰的存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper。
为什么更新UI的Handler必须要在主线程中创建?
因为Handler要与主线程的消息队列关联上,这样handleMessage才会执行在UI线程中,此时更新UI才是线程安全的。