上一篇博文给出了Android中基于Handler Looper机制实现线程间通信的两个典型实例。本文将对该机制的基本原理进行较深入的研究。个人认为,学好Android编程最好的老师就是Android的源代码,下面将基于Android-19的源码进行分析,重点阐述分析思路。
要分析Handler Looper机制,自然想到去看Handler类和Looper类的源码(分别位于Handler.java和Looper.java两个文件中)。简单阅读两个类的描述后,在Looper类的描述中能找到以下一段示例代码。
* <p>This is a typical example of the implementation of a Looper thread, * using the separation of {@link #prepare} and {@link #loop} to create an * initial Handler to communicate with the Looper. * * <pre> * 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(); * } * }</pre> */
这段代码给出了Handler Looper机制实现进程间通信的三大基本步骤,包括Looper的两个函数prepare()和loop(),以及Handler的handleMessage函数。上一篇博文中实例二模拟子线程向UI主线程传递信息的程序就基本上是直接copy这段示例代码实现的。
先看第一个步骤:调用Looper.prepare()函数,猜测应该是创建Looper对象,做些初始化工作。代码如下:
/** Initialize the current thread as a looper. * This gives you a chance to create handlers that then reference * this looper, before actually starting the loop. Be sure to call * {@link #loop()} after calling this method, and end it by calling * {@link #quit()}. */ public static void prepare() { prepare(true); }
直接调用了重载函数prepare(true);
//ThreadLocal实例为多个线程共享,但保证每个线程的存储空间相互独立 static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); //消息队列 final MessageQueue mQueue; //当前线程引用 final Thread mThread; private static void prepare(boolean quitAllowed) { //保证一个线程最多只能创建一个Looper对象。 if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } //创建Looper对象并存储到当前线程独立的存储空间中 sThreadLocal.set(new Looper(quitAllowed)); } private Looper(boolean quitAllowed) { //创建消息队列(线程间即通过该消息队列实现通信) mQueue = new MessageQueue(quitAllowed); //获得当前线程的引用 mThread = Thread.currentThread(); }
看到这里明白了,Looper的prepare函数实际上创建了Looper对象并把对象保存到当前线程独立的存储空间中。这里,Looper的构造函数是私有的,所以外部无法直接通过new Looper()随意创建Looper对象。而只能通过Looper的prepare()函数创建。这样做能够保证对于某一个线程,最多只会创建一个Looper对象的实例,这实际上就是设计模拟中的单体模式。此外,在Looper的私有构造函数中还创建了消息队列并获得当前线程(即创建Looper的线程)的引用。
先跳过Handler.handleMessage(Message msg),直接看Looper.loop()的实现。
/** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. */ public static void loop() {ui //myLooper()函数通过sThreadLocal.get()判断当前线程是否已经创建了Looper的实例 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 Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } //这里是关键,调用了dispatchMessage函数对从消息队列取出的msg进行分派 msg.target.dispatchMessage(msg); if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } // Make sure that during the course of dispatching the // identity of the thread wasn't corrupted. final long newIdent = Binder.clearCallingIdentity(); if (ident != newIdent) { Log.wtf(TAG, "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what); } //消息使命完成,将其放回消息池 msg.recycle(); } }
到这里,Looper扮演的角色已经明朗了,主要就是通过loop()函数中的那个无限循环不断从消息队列中取出消息,并通过dispatchMessage()方法将消息派送出去。那么消息队列中的消息从哪里来,又会被派送到哪里呢?
先来分析第一个问题,消息从哪里来。上一篇博文的实例中,消息源线程均通过调用Handler的sendMessage()函数来发送消息。进入Handler.java文件看其中的sendMessage()函数。
public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0); }
这里调用了sendMessageDelayed,延时0毫秒。
public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); }
进一步调用了sendMessageAtTime,在当前时刻发出。
public boolean sendMessageAtTime(Message msg, long uptimeMillis) { //获得消息队列的引用 MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); }
再看enqueueMessage函数。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { //设置target handler msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } //将消息插入到消息队列中 return queue.enqueueMessage(msg, uptimeMillis); }
看到这里已经明朗了,消息源线程通过Handler.sendMessage发送消息,实际上就是把消息插入了与之关联的消息队列中。在enqueueMessage函数中有一条关键语句msg.target = this,通过这条语句就把Handler和Looper关联起来了(在Looper.loop()的循环中就是通过msg.target属性找到发送消息的Handler并调用其dispatchMessage()函数派发消息的).
搞清楚了消息从哪里来,接下来分析消息被派发到哪里,接着看dispatchMessage()函数的实现。
/** * Handle system messages here. */ public void dispatchMessage(Message msg) { //若消息对象实现了其中的Runnable接口,调用对应的回调函数,即为message.callback.run()) if (msg.callback != null) { handleCallback(msg); } else { //若实现了Handler类的Callback接口,调用接口的回调函数 if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } //将消息返回Handler的消息处理回调函数(是Handler的成员函数,示例代码中回调的就是该函数) handleMessage(msg); } }
可见,dispatchMessage函数中根据msg,handler对象的设置情况调用相应的消息处理回调函数,我们只需要在这个回调函数中添加代码,就可以进行消息处理。示例代码的第二个步骤中的handleMessage函数就是在这里被回调的。
下面回到示例代码中的第二个步骤:
* mHandler = new Handler() { * public void handleMessage(Message msg) { * // process incoming messages here * } * };
这段代码创建了Handler的对象,并覆盖了其中的HandleMessage方法,用户可以添加自己的消息处理函数。 handleMessage回调函数上面已经分析过了,下面主要看看创建handler对象时都做了哪些事情。转入Handler.java文件,看Handler的构造函数(找不带参数那个)。
/** * Default constructor associates this handler with the {@link Looper} for the * current thread. * * If this thread does not have a looper, this handler won't be able to receive messages * so an exception is thrown. */ public Handler() { this(null, false); }
调用了下面的双参数的构造函数。
final Looper mLooper; final MessageQueue mQueue; public Handler(Callback callback, boolean async) { if (FIND_POTENTIAL_LEAKS) { final Class<? extends Handler> klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&(klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, "The following Handler class should be static or leaks might occur: " +klass.getCanonicalName()); } } //获得当前线程Looper对象的引用 mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } //获得Looper关联的消息队列 mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }
到这里发现Handler的构造函数主要做了两件事:1)获得当前线程Looper对象的应用.2)获得与Looper关联的消息队列的引用。到这里,Handler、Looper、消息队列三者就已经关联起来了。
下面通过一个示意图对上面的分析进行总结。
由图可见,基于Handler Looper机制传递消息主要包括以下几个步骤。
(1)目标线程调用Looper.prepare()创建Looper对象和消息队列。
(2)目标线程通过new Handler()创建handler对象,将Handler,Looper,消息队列三者关联起来。并覆盖其handleMessage函数。
(3)目标线程调用Looper.loop()监听消息队列。
(4)消息源线程调用Handler.sendMessage发送消息。
(5)消息源线程调用MessageQueue.enqueueMessage将待发消息插入消息队列。
(6)目标线程的loop()检测到消息队列有消息插入,将其取出。
(7)目标线程将取出的消息通过Handler.dispatchMessage派发给Handler.handleMessage进行消息处理。
到这里整个Android的Handler Looper机制传递消息原理就分析完毕了。还有一个问题值得一提,回顾一下上一篇博文的示例1模拟从网络上下载数据的程序,UI主线程只通过new Handler()创建了Handler对象的实例并覆盖了其handleMessage函数。在代码中并没有看到调用Looper.prepare和Looper.loop(),那么UI主线程中没有创建Looper对象吗?下面就来分析这个问题,既然是UI主线程,那么自然是在启动应用时候由系统自动创建的,创建过程中是否已经创建了Looper对象并调用loop()进行监听了呢?转到ActivityThread.java,找到其中的main函数,这里即为Android程序的入口。在其中能看到以下两行代码。
Looper.prepareMainLooper(); ...... ...... Looper.loop();
再看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(); } }
在这里调用了prepare创建Looper对象。所以说,对于UI主线程而言,其Looper对象是由系统创建好的,用户就无需自行创建了。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。