zoukankan      html  css  js  c++  java
  • 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 }

    实际的效果图:

    其实也很简单,就是 每间隔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

  • 相关阅读:
    分布式系统笔记
    Paxos算法细节详解(一)
    Java G1学习笔记
    Spring Boot 的 10 个核心模块
    k8s 重点
    毕玄:阿里十年,从分布式到云时代的架构演进之路
    netty原理解析
    JVM调优总结(一):基本概念
    《快学 Go 语言》第 16 课 —— 包管理 GOPATH 和 Vendor
    Oracle 检查表空间使用情况
  • 原文地址:https://www.cnblogs.com/yaoxiaowen/p/6582083.html
Copyright © 2011-2022 走看看