首页 > 代码库 > Handler、Looper、Message分析
Handler、Looper、Message分析
我们都知道,耗时操作不应该在主线程中执行,比如从服务器获取数据然后更新界面。但是,界面更新却只能在主线程中执行。这时,一般都会开启线程获取服务器的数据,然后通过Handler将数据发送到主线程,在主线程中进行界面更新。一般来说我们的做法都是这样:
1 new Thread(new Runnable() { 2 @Override 3 public void run() { 4 Looper.prepare(); 5 mHandler = new MyHandler(); 6 Message msg = new Message(); 7 msg.obj = "Text"; 8 mHandler.sendMessage(msg); 9 Looper.loop();10 }11 }).start();
MyHandler继承Handler,并且复写了handleMessage(Message msg)方法,代码如下:
1 class MyHandler extends Handler{2 @Override3 public void handleMessage(Message msg) {4 String text = (String)msg.obj;5 mTextView.setText(text);6 }7 }
在handleMessage方法中,就可以处理从线程中发送过来的数据并更新控件(mTextView)了。知道怎么用,有个蛋用啊,得知道其原理啊(衰!!!)。那就来看源码吧。
1)来看看Looper.prepare做了什么?
prepare()方法中调用了其重载方法,并传入了参数true。
1 private static void prepare(boolean quitAllowed) { 2 if (sThreadLocal.get() != null) {3 throw new RuntimeException("Only one Looper may be created per thread");4 } 5 sThreadLocal.set(new Looper(quitAllowed));6 }
sThreadLocal是ThreadLoacl是对象,关于ThreadLocal,只需要知道ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。这里的sThreadLocal保存的是Looper对象。一个线程中最多只能有一个Looper,并且只能在prepare方法中创建。所以一个线程中最多只能调用一次Looper.prepare,否则就会抛出RuntimeException("Only one Looper may be created per thread")。
sThreadLocal.set(new Looper(quitAllowed)); new出了一个Looper并且将其添加进sThreadLocal中。
2)Looper的构造方法中做了什么?
1 private Looper(boolean quitAllowed) { 2 mQueue = new MessageQueue(quitAllowed);3 mRun = true;4 mThread = Thread.currentThread();5 }
MessageQueue是一个先进先出的消息队列,我们通过handler发送的消息就是由其。
3)发送消息
现在MessageQueue已经有了,就等消息发送过来了。通过handler.sendMessage方法发送的消息,最终都会进入到下面这个方法中:
1 public boolean sendMessageAtTime(Message msg, long uptimeMillis) { 2 MessageQueue queue = mQueue; 3 if (queue == null) { 4 RuntimeException e = new RuntimeException( 5 this + " sendMessageAtTime() called with no mQueue"); 6 Log.w("Looper", e.getMessage(), e); 7 return false; 8 } 9 return enqueueMessage(queue, msg, uptimeMillis);10 }
mQueue是在构造方法中通过获得当前线程的Looper来获取的。enqueueMessage,最终其实现是调用MessageQueue.enqueueMessage来实现,就是将消息添加到队列中,来是怎么实现的。
1 final boolean enqueueMessage(Message msg, long when) { 2 if (msg.isInUse()) { 3 throw new AndroidRuntimeException(msg + " This message is already in use."); 4 } 5 if (msg.target == null) { 6 throw new AndroidRuntimeException("Message must have a target."); 7 } 8 9 boolean needWake;10 synchronized (this) {11 if (mQuiting) {12 RuntimeException e = new RuntimeException(13 msg.target + " sending message to a Handler on a dead thread");14 Log.w("MessageQueue", e.getMessage(), e); 15 return false;16 }17 18 msg.when = when; 19 Message p = mMessages;20 if (p == null || when == 0 || when < p.when) {21 // New head, wake up the event queue if blocked.22 msg.next = p;23 mMessages = msg;24 needWake = mBlocked;25 } else {26 // Inserted within the middle of the queue. Usually we don‘t have to wake27 // up the event queue unless there is a barrier at the head of the queue28 // and the message is the earliest asynchronous message in the queue.29 needWake = mBlocked && p.target == null && msg.isAsynchronous();30 Message prev;31 for (;;) {32 prev = p;33 p = p.next;34 if (p == null || when < p.when) {35 break;36 }37 if (needWake && p.isAsynchronous()) {38 needWake = false;39 }40 }41 msg.next = p; // invariant: p == prev.next42 prev.next = msg;43 }44 }45 if (needWake) {46 nativeWake(mPtr);47 }48 return true;49 }
方法有点长,挑重点看。消息的添加,其实就是在if else中这一段代码中实现的。Message,是琏表。知道了这一点,其实上面的重点代码也就不难理解了。当前队列为空,或者when(从开机到现在的毫秒数,加上delay)为0,或者当前的消息的时间比前一个消息的时间小,都会被判断为当前队列中没有消息。代码会进入到if片段。当队列中有消息,进入到else,通过琏表添加元素的方式,把消息添加到队列中。
OK,发送消息,把消息添加进队列的都已经完成了,那消息是如何从队列中取出来,并交给handler处理的呢?
4)Looper.loop()
从队列中取出消息,并交给handler处理,都在这里面了。
1 /** 2 * Run the message queue in this thread. Be sure to call 3 * {@link #quit()} to end the loop. 4 */ 5 public static void loop() { 6 final Looper me = myLooper(); 7 if (me == null) { 8 throw new RuntimeException("No Looper; Looper.prepare() wasn‘t called on this thread."); 9 }10 final MessageQueue queue = me.mQueue;11 12 // Make sure the identity of this thread is that of the local process,13 // and keep track of what that identity token actually is.14 Binder.clearCallingIdentity();15 final long ident = Binder.clearCallingIdentity();16 17 for (;;) {18 Message msg = queue.next(); // might block19 if (msg == null) {20 // No message indicates that the message queue is quitting.21 return;22 }23 24 // This must be in a local variable, in case a UI event sets the logger25 Printer logging = me.mLogging;26 if (logging != null) {27 logging.println(">>>>> Dispatching to " + msg.target + " " +28 msg.callback + ": " + msg.what);29 }30 31 msg.target.dispatchMessage(msg);32 33 if (logging != null) {34 logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
额,也相当长,同样,看重点。Message msg = queue.next();这个,用脚趾头想也是从队列中取出消息。不看了。 msg.target.dispatchMessage(msg);msg.target,就是handler。
handler.dispatchMessage如下:
1 public void dispatchMessage(Message msg) { 2 if (msg.callback != null) { 3 handleCallback(msg); 4 } else { 5 if (mCallback != null) { 6 if (mCallback.handleMessage(msg)) { 7 return; 8 } 9 } 10 handleMessage(msg);11 } 12 }
这个,看到了我们熟悉的handleMessage。
收工。
Handler、Looper、Message分析