Android中Handler的消息处理机制以及源码分析

在实际项目当中,一个很常见的需求场景就是在根据子线程当中的数据去更新ui。我们知道,android中ui是单线程模型的,就是只能在UI线程(也称为主线程)中更新ui。而一些耗时操作,比如数据库,网络请求,I/O等都是在其他线程中进行的,那么此时就需要在不同线程中进行通信了,而我们最常用的方式就是Handler。

常见的使用方式如下:

 1 public class HandlerTestActivity extends AppCompatActivity {
 2     public static final String TAG = "HandlerTestActivity";
 3     private TextView displayTv;
 4 
 5     private Handler mHandler = new Handler(){
 6         @Override
 7         public void handleMessage(Message msg) {
 8             if (msg.what == 42){
 9                 displayTv.setText("" + msg.arg1);
10             }
11         }
12     };
13 
14     @Override
15     protected void onCreate(Bundle savedInstanceState) {
16         super.onCreate(savedInstanceState);
17         setContentView(R.layout.activity_handler_test);
18         displayTv = (TextView)findViewById(R.id.handlerDisplayTv);
19 
20         new Thread(new Runnable() {
21             @Override
22             public void run() {
23                 int count = 0;
24 
25                 while (true) {
26 
27                     try {
28                         //模拟耗时操作
29                         TimeUnit.MILLISECONDS.sleep(1000);
30                     }catch (InterruptedException e){
31                     }
32 
33                     Message msg = new Message();
34                     msg.what = 42;
35                     msg.arg1 = count++;
36                     mHandler.sendMessage(msg);
37 
38                 }
39             }
40         }).start();
41     }
42 }

实际的效果图:

Android中Handler的消息处理机制以及源码分析

其实也很简单,就是 每间隔1秒钟,屏幕上的textview显示的数字增加1。

总结一下,Handler,Looper,MessageQueue,Message这些组成了android当中的异步消息处理机制。主要用于不同的线程之间通讯。(虽然我们最常用到的场景就是 发送消息来更新ui)。


而在android的官方sdk文档中,给出的官方demo其实是这样的。

 1 class LooperThread extends Thread {
 2       public Handler mHandler;
 3 
 4       public void run() {
 5           Looper.prepare();
 6 
 7           mHandler = new Handler() {
 8               public void handleMessage(Message msg) {
 9                   // process incoming messages here
10               }
11           };
12 
13           Looper.loop();
14       }
15   }

它的基本原理如下。
1,在某个线程中。首先调用Looper.prepare()方法,来准备一个Looper实例,绑定到了该线程,并维护了一个消息队列(MessageQueue),MessageQueue它里面装的内容就是Message,然后调用 Looper.loop(),这是一个无限循环,它来取出消息队列当中的Message送到Handler进行处理。
2,我们new Handler()过程中,来获得到该线程的Looper。然后在其他线程当中,可以调用Handler实例来发送消息(sendMessage方法等),发送的message被放到了MessageQueue中,然后在Handler所在的线程中,Handler实例再通过handleMessage()等方法来处理 这个message,这样就实现了不同线程当中的通信。

所以问题的关键是 一个Handler实例所在的线程只有唯一的一个Looper来维持无限循环,并且它也维护了唯一的一个消息队列(MessageQueue)来传送Message(导入和取出)。


其实这个原理还是不复杂的,随便一本android入门书上也都会讲到这个问题,(并且肯定比我总结的要通俗易懂和严谨)。不过作为一个程序猿,不仅要知其然,更要知其所以然。深层次的理解了它的原理,不仅便于我们的记忆,也可以让我们使用起来更加得心应手。

所以下面,我就从源码的角度来分析这一过程,从子线程当中的sendMessage()开始,然后又怎么在主线程当中handleMessage()进行接收处理的。

首先来看 Handler.java

 1 /**
 2  * Default constructor associates this handler with the {@link Looper} for the
 3  * current thread.
 4  *
 5  * If this thread does not have a looper, this handler won't be able to receive messages
 6  * so an exception is thrown.
 7  */
 8 public Handler() {
 9     this(null, false);
10 }
11 
12 public Handler(Callback callback, boolean async) {
13     if (FIND_POTENTIAL_LEAKS) {
14         final Class<? extends Handler> klass = getClass();
15         if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
16                 (klass.getModifiers() & Modifier.STATIC) == 0) {
17             Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
18                 klass.getCanonicalName());
19         }
20     }
21 
22     mLooper = Looper.myLooper();
23     if (mLooper == null) {
24         throw new RuntimeException(
25             "Can't create handler inside thread that has not called Looper.prepare()");
26     }
27     mQueue = mLooper.mQueue;
28     mCallback = callback;
29     mAsynchronous = async;
30 }

当然,Handler的构造方法有好几个。不过我们先沿着主线去分析。
在这个构造方法中,最重要的是这两句。 

1  mLooper = Looper.myLooper();
2  mQueue = mLooper.mQueue; 

这两句话,说明我们在new Handler时,该线程必须准备好了一个 Looper以及MessageQueue。
然后我们再来看一下Looper.java当中的代码。 

 1 // sThreadLocal.get() will return null unless you've called prepare().
 2 static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
 3 
 4 final MessageQueue mQueue;
 5 final Thread mThread;
 6 
 7 private Looper(boolean quitAllowed) {
 8         mQueue = new MessageQueue(quitAllowed);
 9         mThread = Thread.currentThread();
10     }
11 
12 public static @Nullable Looper myLooper() {
13     return sThreadLocal.get();
14 }
15 
16 private static void prepare(boolean quitAllowed) {
17     if (sThreadLocal.get() != null) {
18         throw new RuntimeException("Only one Looper may be created per thread");
19     }
20     sThreadLocal.set(new Looper(quitAllowed));
21 }

首先代码中可以得知 Looper的唯一一个构造方法是private的,所以我们要想得到Looper,那就只能通过prepare()方法了,而在prepare()方法中,注意那句“throw new RuntimeException("Only one Looper may be created per thread")”,这就保证了我们的Looper是唯一的,而通过Handler的构造方法的 “throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()")”。我们也能保证在使用 Handler之前,必须已经调用了 Looper.prepare()来初始化这个唯一的Looper。

而关于这个存储Looper的ThreadLocal对象,其实是一个线程内部的数据存储类,它最大的特点是作用域是限定在该线程内的,也就是说,当它存储数据后(set方法)。只能在该线程内才能获取到数据。 其他线程调用get方法得到的其实会是null(也就是无法得到对应数据)。当然,它内部有一套相应的机制和算法来保证它的作用域是线程内的。

那么从这些代码当中,我们已经明白了,当我们在new Handler()时,必须已经 初始化了唯一的一个Looper和MessageQueue了。

这里插一个问题。我们一直在口口声声的说,在new Handler之前,我们要调用 Looper.prepare()来执行必要的初始化,可是在我们的第一个HandlerTestActivity demo中,根本没调用 这个方法啊。这个原因是因为在UI线程启动时,在ActivityThread中已经帮我们调用过 Looper.prepareMainLooper()方法了(ActivityThread也调用了Looper.loop())。

 1  /**
 2      * Initialize the current thread as a looper, marking it as an
 3      * application's main looper. The main looper for your application
 4      * is created by the Android environment, so you should never need
 5      * to call this function yourself.  See also: {@link #prepare()}
 6      */
 7     public static void prepareMainLooper() {
 8         prepare(false);
 9         synchronized (Looper.class) {
10             if (sMainLooper != null) {
11                 throw new IllegalStateException("The main Looper has already been prepared.");
12             }
13             sMainLooper = myLooper();
14         }
15     }

所以我们在 HandlerTestActivity中,不需要调用Looper.prepare()方法了。而在官方的LooperThread demo中,因为不是在ui线程。那就只要你自己来调用 Looper.prepare()和Looper.loop()了.


既然Handler,Looper和MessageQueue我们已经都得到实例对象了,那么下面看看它的发送消息的过程。

 1 /**
 2  * Pushes a message onto the end of the message queue after all pending messages
 3  * before the current time. It will be received in {@link #handleMessage},
 4  * in the thread attached to this handler.
 5  *  
 6  */
 7 public final boolean sendMessage(Message msg)
 8 {
 9     return sendMessageDelayed(msg, 0);
10 }
11 
12 /**
13  * Enqueue a message into the message queue after all pending messages
14  * before (current time + delayMillis). You will receive it in
15  * {@link #handleMessage}, in the thread attached to this handler.
16  */
17 public final boolean sendMessageDelayed(Message msg, long delayMillis)
18 {
19     if (delayMillis < 0) {
20         delayMillis = 0;
21     }
22     return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
23 }
24 
25   /**
26  * Enqueue a message into the message queue after all pending messages
27  * before the absolute time (in milliseconds) <var>uptimeMillis</var>.
28  * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
29  * Time spent in deep sleep will add an additional delay to execution.
30  * You will receive it in {@link #handleMessage}, in the thread attached
31  * to this handler.
32  * 
33 
34  */
35 public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
36     MessageQueue queue = mQueue;
37     if (queue == null) {
38         RuntimeException e = new RuntimeException(
39                 this + " sendMessageAtTime() called with no mQueue");
40         Log.w("Looper", e.getMessage(), e);
41         return false;
42     }
43     return enqueueMessage(queue, msg, uptimeMillis);
44 }

通过代码可以看出,几经周折,其实调用的是 enqueueMessage(queue, msg, uptimeMillis)方法,这个方法就是重中之重了。(Handler的sendMessage的方法有几个,但是归根到底,最后还是 调用enqueueMessage方法来进行入队操作)。

1 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
2     msg.target = this;
3     if (mAsynchronous) {
4         msg.setAsynchronous(true);
5     }
6     return queue.enqueueMessage(msg, uptimeMillis);
7 }

通过代码可以看出来,Looper的enqueueMessage方法最后调用的还是 queue.enqueueMessage方法,也就是MessageQueue本身来进行入队操作。不过在这个方法中,我们还有一句要注意的代码,那就是
msg.target = this;通过这句代码,来把message的 target对象来变成 Handler本身了。这句代码在后面的处理流程中,是有重要作用的。

那么现在,我们就看看 MessageQueue的 enqueueMessage()方法。

 1 boolean enqueueMessage(Message msg, long when) {
 2     if (msg.target == null) {
 3         throw new IllegalArgumentException("Message must have a target.");
 4     }
 5     if (msg.isInUse()) {
 6         throw new IllegalStateException(msg + " This message is already in use.");
 7     }
 8 
 9     synchronized (this) {
10         if (mQuitting) {
11             IllegalStateException e = new IllegalStateException(
12                     msg.target + " sending message to a Handler on a dead thread");
13             Log.w(TAG, e.getMessage(), e);
14             msg.recycle();
15             return false;
16         }
17 
18         msg.markInUse();
19         msg.when = when;
20         Message p = mMessages;
21         boolean needWake;
22         if (p == null || when == 0 || when < p.when) {
23             // New head, wake up the event queue if blocked.
24             msg.next = p;
25             mMessages = msg;
26             needWake = mBlocked;
27         } else {
28             // Inserted within the middle of the queue.  Usually we don't have to wake
29             // up the event queue unless there is a barrier at the head of the queue
30             // and the message is the earliest asynchronous message in the queue.
31             needWake = mBlocked && p.target == null && msg.isAsynchronous();
32             Message prev;
33             for (;;) {
34                 prev = p;
35                 p = p.next;
36                 if (p == null || when < p.when) {
37                     break;
38                 }
39                 if (needWake && p.isAsynchronous()) {
40                     needWake = false;
41                 }
42             }
43             msg.next = p; // invariant: p == prev.next
44             prev.next = msg;
45         }
46 
47         // We can assume mPtr != 0 because mQuitting is false.
48         if (needWake) {
49             nativeWake(mPtr);
50         }
51     }
52     return true;
53 }

这个方法内容比较多。但是主要作用就是一个,把message按照时间戳的顺序,插入到这个队列中,值得注意的是,MessageQueue队列的方式是通过链表来实现的。
至于为什么插入的顺序是按照时间来的,因为我们在使用Handler来发送message时,有几个方法本来就是有个延迟时间的。(比如sendMessageDelayed(Message, long)等,默认的发送时间就是当前时间),并且在Message对象中,也有when属性来保存这个时间的。
不过这里的这个时间戳取的是 手机自开机之后的时间,而不是我们经常说的linux那个源自1970年的那个时间。

我们的代码看到这里,其实就应该告一段落了,那就是 我们通过Handler来发送的Message,已经被送入到MessageQuequ了。消息已经在消息队列了。那么怎么取出来进行处理呢。


我们在最初分析原理的时候就说过,Looper.loop()中维持了一个无限循环,
看一下代码:

 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          //省略一些其他代码
25         try {
26             msg.target.dispatchMessage(msg);
27         } finally {
28             if (traceTag != 0) {
29                 Trace.traceEnd(traceTag);
30             }
31         }
32 
33         if (logging != null) {
34             logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
35         }
36 
37        //省略一些其他代码
38 
39         msg.recycleUnchecked();
40     }
41 } 

在这个代码当中,可以看到的确是维持了一个死循环,而这个死循环当中, 就是通过 queue.next() 从MessageQueue中取出message,并且如果没有数据的话,就会被阻塞。MessageQueue对象的next()方法这里就不贴出来了,就是一个出队操作(根据message的when属性,当时间到了,就返回对象,并从队列中删除),。

可是取出来的message数据,这里怎么做处理呢。关键是这一句 msg.target.dispatchMessage(msg),
别忘记了我们前面分析时,在message入队之前的一句代码 msg.target = this。所以 这里的 msg.target.dispatchMessage(msg)代码说白了还是在这个Handler的dispatchMessage()来分发处理的message。

 1 /**
 2  * Handle system messages here.
 3  */
 4 public void dispatchMessage(Message msg) {
 5     if (msg.callback != null) {
 6         handleCallback(msg);
 7     } else {
 8         if (mCallback != null) {
 9             if (mCallback.handleMessage(msg)) {
10                 return;
11             }
12         }
13         handleMessage(msg);
14     }
15 }
16 
17 /**
18  * Subclasses must implement this to receive messages.
19  */
20 public void handleMessage(Message msg) {
21 }

在dispatchMessage方法中,经过两个if判断,最终message的处理,落到了 handleMessage()方法,至于handleMessage()方法,是个空方法,看看方法前面的注释,这个时候就应该恍然大悟了。
我们在 最初的 demo中的处理方式,不就是重写的 handleMessage方法嘛。。。

所以在这里,这个过程算是比较清晰了,Handler对象(在其他线程中)发送的message,被放入了 MessageQueue中,然后通过Looper的无限循环,最后又被取出到Handler所处的线程中进行了处理。


另外,除了我们传统Handler的sendMessage()方法外,还有一种方法来使用Handler。

public class HandlerTest_2_Activity extends AppCompatActivity {
    public static final String TAG = "HandlerTest_2_Activity";
    private TextView displayTv;

    private Handler mHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler_test);
        displayTv = (TextView) findViewById(R.id.handlerDisplayTv);

        mHandler = new Handler();

        new MyThread("子线程").start();

    }

    class MyThread extends Thread{

        public MyThread(String name) {
            super(name);
        }

        @Override
        public void run() {
            super.run();
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                //操作UI
                    displayTv.setText("operation from " + Thread.currentThread().getName());

                }
            });
        }
    }
}

我们在 MyThread子线程当中,可以直接使用 mHandler.post方法来传递任务到ui线程中进行相关操作。(Thread才是线程,Runnable只是任务,如果不明白这一点的,可以看一下我前面写的几篇多线程的文章)。

我们也分析一下相关的代码:

 1 /**
 2  * Causes the Runnable r to be added to the message queue.
 3  * The runnable will be run on the thread to which this handler is 
 4  * attached. 
 5  */
 6 public final boolean post(Runnable r)
 7 {
 8    return  sendMessageDelayed(getPostMessage(r), 0);
 9 }
10 
11 private static Message getPostMessage(Runnable r) {
12     Message m = Message.obtain();
13     m.callback = r;
14     return m;
15 }

看到 sendMessageDelayed()方法,我们就很熟悉了,其实 还是老一套,将message发送到 队列中,不过 getPostMessage()方法是什么呢?
代码就几行,将Runnable包装成Message。并且m.callback = r; 而关于Message,我们可以new,也可以使用obtain方法。后者效率更好一些,因为系统会维护一个Message池进行复用。

不要忘记了我们的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 }
13 
14  private static void handleCallback(Message message) {
15     message.callback.run();
16 }

此时再读读if判断的第一句,msg.callback是什么,不就是post中传递进去的 Runnable嘛,而这里回调的 handleCallback()方法,转了一圈,最后还是回调到了 Runnable的run()方法了。
所以使用Handler.post(Runnable)算是一种简写了。

不过在dispatchMessage()方法中,还有这样一句判断,mCallback != null ,其实是这样的。

 1 /**
 2  * Callback interface you can use when instantiating a Handler to avoid
 3  * having to implement your own subclass of Handler.
 4  *
 5  */
 6 public interface Callback {
 7     public boolean handleMessage(Message msg);
 8 }
 9 public Handler(Callback callback) {
10     this(callback, false);
11 }

所以说,处理Message的方式不只一种。


写完这些,Handler这种消息处理机制已经从源码角度分析ok了。理解了原理,今后使用起来才更加的得心应手。原理其实不算复杂,但是真正的细节问题其实也有很多。这篇文章呢,也是我写的第一篇关于分析源码的文章,在这里呢,也给自己立个flag,希望今后能写更多的分析源码的文章。

---

作者: www.yaoxiaowen.com

github: https://github.com/yaowen369