zoukankan      html  css  js  c++  java
  • 深入源代码解析Android中的Handler,Message,MessageQueue,Looper

    本文主要是对Handler和消息循环的实现原理进行源代码分析。假设不熟悉Handler能够參见博文《 Android中Handler的使用》,里面对Android为何以引入Handler机制以及怎样使用Handler做了解说。

    概括来说。Handler是Android中引入的一种让开发人员參与处理线程中消息循环的机制。我们在使用Handler的时候与Message打交道最多,Message是Hanlder机制向开发人员暴露出来的相关类,能够通过Message类完毕大部分操作Handler的功能。但作为程序猿,我不能仅仅知道怎么用Handler,还要知道其内部怎样实现的。Handler的内部实现主要涉及到例如以下几个类: Thread、MessageQueue和Looper。

    这几类之间的关系能够用例如以下的图来简单说明:

    这里写图片描写叙述

    Thread是最基础的。Looper和MessageQueue都构建在Thread之上,Handler又构建在Looper和MessageQueue之上。我们通过Handler间接地与以下这几个相对底层一点的类打交道。


    MessageQueue

    MessageQueue源代码链接

    最基础最底层的是Thread。每一个线程内部都维护了一个消息队列——MessageQueue。

    消息队列MessageQueue,顾名思义,就是存放消息的队列(好像是废话…)。那队列中存储的消息是什么呢?假设我们在UI界面上单击了某个button。而此时程序又恰好收到了某个广播事件。那我们怎样处理这两件事呢? 由于一个线程在某一时刻仅仅能处理一件事情。不能同一时候处理多件事情,所以我们不能同一时候处理button的单击事件和广播事件,我们仅仅能挨个对其进行处理,仅仅要挨个处理就要有处理的先后顺序。 为此Android把UI界面上单击button的事件封装成了一个Message。将其放入到MessageQueue里面去。即将单击button事件的Message入栈到消息队列中,然后再将广播事件的封装成以Message,也将其入栈到消息队列中。也就是说一个Message对象表示的是线程须要处理的一件事情。消息队列就是一堆须要处理的Message的池。线程Thread会依次取出消息队列中的消息,依次对其进行处理。

    MessageQueue中有两个比較重要的方法。一个是enqueueMessage方法,一个是next方法。

    enqueueMessage方法用于将一个Message放入到消息队列MessageQueue中。next方法是从消息队列MessageQueue中堵塞式地取出一个Message。

    在Android中。消息队列负责管理着顶级程序对象(Activity、BroadcastReceiver等)以及由其创建的全部窗体。

    须要注意的是,消息队列不是Android平台特有的。其它的平台框架也会用到消息队列,比方微软的MFC框架等。


    Looper

    Looper源代码链接

    消息队列MessageQueue仅仅是存储Message的地方,真正让消息队列循环起来的是Looper,这就好比消息队列MessageQueue是个水车。那么Looper就是让水车转动起来的河水。假设没有河水,那么水车就是个精巧的摆设,没有不论什么用处,Looper让MessageQueue动了起来。有了活力。

    Looper是用来使线程中的消息循环起来的。默认情况下当我们创建一个新的线程的时候,这个线程里面是没有消息队列MessageQueue的。为了能够让线程能够绑定一个消息队列。我们须要借助于Looper:首先我们要调用Looper的prepare方法,然后调用Looper的Loop方法。典型的代码例如以下所看到的:

    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();
          }
      }

    须要注意的是Looper.prepare()和Looper.loop()都是在新线程的run方法内调用的,这两个方法都是静态方法。我们通过查看Looper的源代码能够发现。Looper的构造函数是private的,也就是在该类的外部不能用new Looper()的形式得到一个Looper对象。依据我们上面的描写叙述,我们知道线程Thread和Looper是一对一绑定的,也就是一个线程中最多仅仅有一个Looper对象,这也就能解释Looper的构造函数为什么是private的了,我们仅仅能通过工厂方法Looper.myLooper()这个静态方法获取当前线程所绑定的Looper。

    Looper通过例如以下代码保存了对当前线程的引用:

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    所以在Looper对象中通过sThreadLocal就能够找到其绑定的线程。ThreadLocal中有个set方法和get方法,能够通过set方法向ThreadLocal中存入一个对象,然后能够通过get方法取出存入的对象。ThreadLocal在new的时候使用了泛型,从上面的代码中我们能够看到此处的泛型类型是Looper,也就是我们通过ThreadLocal的set和get方法仅仅能写入和读取Looper对象类型,假设我们调用其ThreadLocal的set方法传入一个Looper,将该Looper绑定给了该线程,相应的get就能获得该线程所绑定的Looper对象。

    我们再来看一下Looper.prepare()。该方法是让Looper做好准备,仅仅有Looper准备好了之后才干调用Looper.loop()方法。Looper.prepare()的代码例如以下:

    private static void prepare(boolean quitAllowed) {
            if (sThreadLocal.get() != null) {
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            sThreadLocal.set(new Looper(quitAllowed));
    }

    上面的代码首先通过sThreadLocal.get()拿到线程sThreadLocal所绑定的Looper对象,由于初始情况下sThreadLocal并没有绑定Looper,所以第一次调用prepare方法时,sThreadLocal.get()返回null。不会抛出异常。重点是以下的代码sThreadLocal.set(new Looper(quitAllowed))。首先通过私有的构造函数创建了一个Looper对象的实例。然后通过sThreadLocal的set方法将该Looper绑定到sThreadLocal中。
    这样就完毕了线程sThreadLocal与Looper的双向绑定:
    a. 在Looper内通过sThreadLocal能够获取Looper所绑定的线程;
    b.线程sThreadLocal通过sThreadLocal.get()方法能够获取该线程所绑定的Looper对象。

    上面的代码运行了Looper的构造函数,我们看一下其代码:

    private Looper(boolean quitAllowed) {
            mQueue = new MessageQueue(quitAllowed);
            mThread = Thread.currentThread();
    }

    我们能够看到在其构造函数中实例化一个消息队列MessageQueue,并将其赋值给其成员字段mQueue,这样Looper也就与MessageQueue通过成员字段mQueue进行了关联。

    在运行完了Looper.prepare()之后。我们就能够在外部通过调用Looper.myLooper()获取当前线程绑定的Looper对象。
    myLooper的代码例如以下所看到的:

    public static Looper myLooper() {
            return sThreadLocal.get();
    }

    须要注意的是,在一个线程中。仅仅能调用一次Looper.prepare()。由于在第一次调用了Looper.prepare()之后,当前线程就已经绑定了Looper,在该线程内第二次调用Looper.prepare()方法的时候。sThreadLocal.get()会返回第一次调用prepare的时候绑定的Looper。不是null。这样就会走的以下的代码throw new RuntimeException(“Only one Looper may be created per thread”)。从而抛出异常,告诉开发人员一个线程仅仅能绑定一个Looper对象。

    在调用了Looper.prepare()方法之后,当前线程和Looper就进行了双向的绑定,这时候我们就能够调用Looper.loop()方法让消息队列循环起来了。
    须要注意的是Looper.loop()应该在该Looper所绑定的线程中运行。

    Looper.loop()的代码例如以下:

    public static void loop() {
            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);
                }
    
                //注意以下这行
                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.recycleUnchecked();
            }
    }

    上面有几行代码是关键代码:
    1. final MessageQueue queue = me.mQueue;
    变量me是通过静态方法myLooper()获得的当前线程所绑定的Looper。me.mQueue是当前线程所关联的消息队列。


    2. for (;;)
    我们发现for循环没有设置循环终止的条件,所以这个for循环是个死循环。
    3. Message msg = queue.next(); // might block
    我们通过消息队列MessageQueue的next方法从消息队列中取出一条消息,假设此时消息队列中有Message。那么next方法会马上返回该Message,假设此时消息队列中没有Message,那么next方法就会堵塞式地等待获取Message。
    4. msg.target.dispatchMessage(msg);
    msg的target属性是Handler,该代码的意思是让Message所关联的Handler通过dispatchMessage方法让Handler处理该Message,关于Handler的dispatchMessage方法将会在以下具体介绍。


    Handler

    Handler源代码链接

    Handler是暴露给开发人员最顶层的一个类,其构建在Thread、Looper与MessageQueue之上。
    Handler具有多个构造函数,签名分别例如以下所看到的:
    1. publicHandler()
    2. publicHandler(Callbackcallback)
    3. publicHandler(Looperlooper)
    4. publicHandler(Looperlooper, Callbackcallback)
    第1个和第2个构造函数都没有传递Looper。这两个构造函数都将通过调用Looper.myLooper()获取当前线程绑定的Looper对象,然后将该Looper对象保存到名为mLooper的成员字段中。


    第3个和第4个构造函数传递了Looper对象。这两个构造函数会将该Looper保存到名为mLooper的成员字段中。
    第2个和第4个构造函数还传递了Callback对象。Callback是Handler中的内部接口。须要实现其内部的handleMessage方法,Callback代码例如以下:

    public interface Callback {
            public boolean handleMessage(Message msg);
    }

    Handler.Callback是用来处理Message的一种手段。假设没有传递该參数,那么就应该重写Handler的handleMessage方法。也就是说为了使得Handler能够处理Message,我们有两种办法:
    1. 向Hanlder的构造函数传入一个Handler.Callback对象。并实现Handler.Callback的handleMessage方法
    2. 无需向Hanlder的构造函数传入Handler.Callback对象。可是须要重写Handler本身的handleMessage方法
    也就是说不管哪种方式。我们都得通过某种方式实现handleMessage方法,这点与Java中对Thread的设计有异曲同工之处。
    在Java中,假设我们想使用多线程,有两种办法:
    1. 向Thread的构造函数传入一个Runnable对象,并实现Runnable的run方法
    2. 无需向Thread的构造函数传入Runnable对象,可是要重写Thread本身的run方法
    所以仅仅要用过多线程Thread,应该就对Hanlder这样的须要实现handleMessage的两种方式了然于心了。

    我们知道通过sendMessageXXX系列方法能够向消息队列中加入消息,我们通过源代码能够看出这些方法的调用顺序。
    sendMessage调用了sendMessageDelayed,sendMessageDelayed又调用了sendMessageAtTime。
    Handler中还有一系列的sendEmptyMessageXXX方法,而这些sendEmptyMessageXXX方法在其内部又分别调用了其相应的sendMessageXXX方法。

    通过以下调用关系图我们能够看的更清楚些:
    这里写图片描写叙述
    由此可见全部的sendMessageXXX方法和sendEmptyMessageXXX终于都调用了sendMessageAtTime方法。

    我们再来看看postXXX方法。会发现postXXX方法在其内部又调用了相应的sendMessageXXX方法。我们能够查看下sendMessage的源代码:

    public final boolean post(Runnable r)
    {
           return  sendMessageDelayed(getPostMessage(r), 0);
    }

    能够看到内部调用了getPostMessage方法,该方法传入一个Runnable对象,得到一个Message对象,getPostMessage的源代码例如以下:

    private static Message getPostMessage(Runnable r) {
            Message m = Message.obtain();
            m.callback = r;
            return m;
     }

    通过上面的代码我们能够看到在getPostMessage方法中,我们创建了一个Message对象,并将传入的Runnable对象赋值给Message的callback成员字段,然后返回该Message,然后在post方法中该携带有Runnable信息的Message传入到sendMessageDelayed方法中。

    由此我们能够看到全部的postXXX方法内部都须要借助sendMessageXXX方法来实现,所以postXXX与sendMessageXXX并非对立关系。而是postXXX依赖sendMessageXXX,所以postXXX方法能够通过sendMessageXXX方法向消息队列中传入消息,仅仅只是通过postXXX方法向消息队列中传入的消息都携带有Runnable对象(Message.callback)。

    我们能够通过例如以下关系图看清楚postXXX系列方法与sendMessageXXX方法之间的调用关系:
    这里写图片描写叙述

    通过分别分析sendEmptyMessageXXX、postXXX方法与sendMessageXXX方法之间的关系,我们能够看到在Handler中全部能够直接或间接向消息队列发送Message的方法终于都调用了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) {
            //注意以下这行代码
            msg.target = this;
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            //注意以下这行代码
            return queue.enqueueMessage(msg, uptimeMillis);
    }

    在该方法中有两件事须要注意:
    1. msg.target = this
    该代码将Message的target绑定为当前的Handler
    2. queue.enqueueMessage
    变量queue表示的是Handler所绑定的消息队列MessageQueue。通过调用queue.enqueueMessage(msg, uptimeMillis)我们将Message放入到消息队列中。

    所以我们通过下图能够看到完整的方法调用顺序:
    这里写图片描写叙述

    我们在分析Looper.loop()的源代码时发现,Looper一直在不断的从消息队列中通过MessageQueue的next方法获取Message,然后通过代码msg.target.dispatchMessage(msg)让该msg所绑定的Handler(Message.target)运行dispatchMessage方法以实现对Message的处理。
    Handler的dispatchMessage的源代码例如以下:

    public void dispatchMessage(Message msg) {
            //注意以下这行代码
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
                 //注意以下这行代码
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                 //注意以下这行代码
                handleMessage(msg);
            }
    }

    我们来分析下这段代码:

    1.首先会推断msg.callback存不存在,msg.callback是Runnable类型,假设msg.callback存在,那么说明该Message是通过运行Handler的postXXX系列方法将Message放入到消息队列中的,这样的情况下会运行handleCallback(msg), handleCallback源代码例如以下:

    private static void handleCallback(Message message) {
            message.callback.run();
    }

    这样我们我们就清楚地看到我们运行了msg.callback的run方法,也就是运行了postXXX所传递的Runnable对象的run方法。

    2.假设我们不是通过postXXX系列方法将Message放入到消息队列中的,那么msg.callback就是null,代码继续往下运行,接着我们会推断Handler的成员字段mCallback存不存在。mCallback是Hanlder.Callback类型的,我们在上面提到过。在Handler的构造函数中我们能够传递Hanlder.Callback类型的对象,该对象须要实现handleMessage方法,假设我们在构造函数中传递了该Callback对象,那么我们就会让Callback的handleMessage方法来处理Message。

    3.假设我们在构造函数中没有传入Callback类型的对象。那么mCallback就为null,那么我们会调用Handler自身的hanldeMessage方法,该方法默认是个空方法,我们须要自己是重写实现该方法。

    综上。我们能够看到Handler提供了三种途径处理Message,并且处理有前后优先级之分:首先尝试让postXXX中传递的Runnable运行,其次尝试让Handler构造函数中传入的Callback的handleMessage方法处理,最后才是让Handler自身的handleMessage方法处理Message。


    一图胜千言

    我们在本文讨论了Thread、MessageQueue、Looper以及Hanlder的之间的关系,我们能够通过例如以下一张传送带的图来更形象的理解他们之间的关系。

    这里写图片描写叙述

    在现实生活的生产生活中,存在着各种各样的传送带,传送带上面洒满了各种货物,传送带在发动机滚轮的带动下一直在向前滚动,不断有新的货物放置在传送带的一端,货物在传送带的带动下送到还有一端进行收集处理。

    我们能够把传送带上的货物看做是一个个的Message,而承载这些货物的传送带就是装载Message的消息队列MessageQueue。传送带是靠发送机滚轮带动起来转动的,我们能够把发送机滚轮看做是Looper,而发动机的转动是须要电源的,我们能够把电源看做是线程Thread,全部的消息循环的一切操作都是基于某个线程的。一切准备就绪,我们仅仅须要按下电源开关发动机就会转动起来,这个开关就是Looper的loop方法,当我们按下开关的时候。我们就相当于运行了Looper的loop方法,此时Looper就会驱动着消息队列循环起来。

    那Hanlder在传送带模型中相当于什么呢?我们能够将Handler看做是放入货物以及取走货物的管道:货物从一端顺着管道划入传送带,货物又从还有一端顺着管道划出传送带。我们在传送带的一端放入货物的操作就相当于我们调用了Handler的sendMessageXXX、sendEmptyMessageXXX或postXXX方法,这就把Message对象放入到了消息队列MessageQueue中了。当货物从传送带的还有一端顺着管道划出时。我们就相当于调用了Hanlder的dispatchMessage方法。在该方法中我们完毕对Message的处理。

    啰啰嗦嗦说了非常多。希望本文对于大家理解Android中的Handler和消息循环机制有所帮助。

  • 相关阅读:
    百度影音盒插入论坛帖子自动播放代码及方法
    vFloppy1.5-虚拟启动软盘
    飞秋的实现原理
    博客盈利请先考虑这七点
    下载站运行广告合作exe文件然后再运行程序文件的bat
    木马病毒是什么以及手工清除木马病毒具体步骤
    网站盈利模式分析分类
    软件更新原理
    浅析php学习的路线图
    网页常用分享代码大全(前端必备)
  • 原文地址:https://www.cnblogs.com/yutingliuyl/p/7207257.html
Copyright © 2011-2022 走看看