我们都知道,耗时操作不应该在主线程中执行,比如从服务器获取数据然后更新界面。但是,界面更新却只能在主线程中执行。这时,一般都会开启线程获取服务器的数据,然后通过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 @Override 3 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 wake 27 // up the event queue unless there is a barrier at the head of the queue 28 // 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.next 42 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 block 19 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 logger 25 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。
收工。