码迷,mamicode.com
首页 > 移动开发 > 详细

【Android】消息机制原理

时间:2015-05-12 11:33:20      阅读:172      评论:0      收藏:0      [点我收藏+]

标签:android   looper   handler   消息队列   消息   

Android 消息机制涉及到的类主要有

  • Looper
  • Handler
  • Message、MessageQueue
  • HandlerThread

下面结合 Android API 22 的源码分析上面几个类的内部实现细节,以窥探其中的原理一二。

Looper 消息循环

Looper 是一个循环处理消息的类,Looper内部维护一个消息队列,循环的从消息队列中取出消息并处理,如果队列为空则等待新消息。Looper 必须关联到某个线程中,这样其才能获得操作系统的调度而执行消息循环处理,默认情况下,通过 Thread 类创建的线程是没有 Looper 的,如果需要该线程拥有消息循环的功能,需要像下面这样在 Runnable 接口实现方法 run() 中创建Looper。

  class LooperThread extends Thread {
      public Handler mHandler;

      public void run() {
          Looper.prepare();            // 创建与当前线程关联的 Looper 对象

          mHandler = new Handler() {
              public void handleMessage(Message msg) {
                  // 处理接收的消息
              }
          };

          Looper.loop();                // 开始消息循环
      }
  }

Looper.loop() 调用后,Looper 循环处理发送到其消息队列中的消息。后面我们会发现,消息处理的最终结果就是调用 Handler.handleMessage() 方法。

技术分享

Looper 类

先看下 Looper 类的大致内容,为了突出重点,不重要的代码我省略了,只展示 Looper 类的重要成员变量和构造函数。源码如下:

public final class Looper {

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;  // guarded by Looper.class

    final MessageQueue mQueue;        // 消息队列
    final Thread mThread;             // 关联的线程

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

从上面可以看到,Looper 类在创建时,会创建一个消息队列,并关联到当前线程。

Looper.prepare()

Looper 对象的构造函数是private 修饰,不能直接通过 new 来创建,只能通过其静态方法 Looper.prepare() 来创建,同时注意到”sThreadLocal.get() != null“这句代码判断是否已存在 Looper 对象,如果 Looper 对象已经存在则会抛出异常,所以每个线程最多只能有一个 Looper 对象。源码如下:

    public static void prepare() {
        prepare(true);
    }

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

Looper.loop()

一旦调用 Looper.loop() 方法,Looper 对象开始消息循环,其过程很简单,首先检查消息队列是否为空,如果消息队列为空就退出,否则从消息队列中取出一个消息,分发给消息中指定的 Handler 对象处理,然后回收消息放入消息池,然后循环该过程。源码如下:

    public static void loop() {
        // 获取当前线程的 Looper 对象
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn‘t called on this thread.");
        }
        // 获取当前线程 Looper 对象内部的消息队列
        final MessageQueue queue = me.mQueue;

        ...

        for (;;) {
            Message msg = queue.next(); // 下一个消息
            if (msg == null) {
                // 如果没有消息表明消息队列将要退出
                return;
            }

            ...
            // 分发消息
            msg.target.dispatchMessage(msg);

            ...

            // 回收消息放到消息池中
            msg.recycleUnchecked();
        }
    }

Looper 类其他有用方法

  • static Looper myLooper()
    返回与当前线程关联的 Looper 对象。在任意线程调用Looper.myLooper()返回的都是那个线程的looper对象。源码如下:
    public static Looper myLooper() {
        return sThreadLocal.get();
    }
  • static MessageQueue myQueue()
    返回与当前线程关联的消息队列。源码如下:
    public static MessageQueue myQueue() {
        return myLooper().mQueue;
    }

总结

1.每个线程有且最多只能有一个Looper对象,它是一个ThreadLocal

2.Looper内部有一个消息队列,loop()方法调用后线程开始不断从队列中取出消息执行

3.Looper使一个线程变成Looper线程。

Handler

Handler 不仅用于处理消息,还用于发送消息(到线程所关联的消息队列)。

Handler 对象发送消息到消息队列后(sendMessage),Looper 进行消息循环处理(loop),将消息传递(dispatchMessage)给该消息创建时所指定的 Handler 对象进行处理(handleMessage)。

这里需要理清几个数量对应关系:
* 一个 Thread 仅与一个 Looper 关联
* 一个 Looper 只包含一个 MessageQueue
* 一个 Handler 仅与一个 Looper 关联 (这样每个Handler知道将消息发送给哪个Looper)
* 一个 Message 仅与一个 Handler 关联 (这样每个Message知道将由哪个Handler处理)
技术分享

  • 多个 Handler 可以关联同一个 Looper(即关联不同Handler的消息可以放在同一个消息队列中)

Handler 类

Handler 类的成员变量和构造函数如下:

public class Handler {
    final MessageQueue mQueue;        // 关联的消息队列
    final Looper mLooper;             // 关联的 Looper
    final Callback mCallback;         // 回调接口
    final boolean mAsynchronous;      // 是否异步

    // 最常见的一种方式,关联当前线程的Looper和消息队列
    public Handler() {
        this(null, false);
    }

    public Handler(Callback callback) {
        this(callback, false);
    }

    public Handler(Looper looper) {
        this(looper, null, false);
    }

    public Handler(Looper looper, Callback callback) {
        this(looper, callback, false);
    }

    public Handler(boolean async) {
        this(null, async);
    }

    public Handler(Callback callback, boolean async) {
        ...

        mLooper = Looper.myLooper();    // 关联到当前线程的 Looper 对象 
        if (mLooper == null) {          // Looper不能为空,Handler只能在关联了Looper的线程中创建
            throw new RuntimeException(
                "Can‘t create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;    // 关联消息队列,Handler发送的消息就存放在该消息队列中
        mCallback = callback;
        mAsynchronous = async;
    }

    public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

    ...
}

从 Handler 的构造函数可以知道,平时我们写的 “mHandler = new Handler(){…}” 这句代码,内部其实已经关联了当前线程的消息循环和消息队列。所以我们创建 Handler 后不需要做这些关联工作,只需要重写 handleMessage() 方法添加自己的业务逻辑,这种设计方式让使用者编写更少的代码。

Handler 发送消息

handler 发送消息有两种方式:

  • send message
    通过sendMessage、sendMessageDelayed、sendMessageAtTime等方法,发送 Message 对象。

  • post Runnable Object
    通过 post、postDelayed、postAtTime等方法,发送实现了 Runnable 接口的对象。这种方式其本质也是发送消息,内部通过将 Runnable 对象封装成 Message 然后发送。

结合源码看可能更清楚些,所有发送消息的方法都是通过enqueueMessage(...)方法实现的,发送Runnable对象时,先通过getPostMessage(...)方法将Runnable 对象封装成 Message 然后通过发送消息的函数发送。

    // 将消息 msg 添加到消息队列 queue 中
    // uptimeMillis 表示系统从启动开始计时,正常运行 uptimeMillis 毫秒后分发出去(单位:毫秒)
    // uptimeMillis 值越小消息越早得到处理。
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

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

    // 延迟 delayMillis 指定毫秒时间后发送消息
    // SystemClock.uptimeMillis() 可获取系统从启动到现在正常运行的时间,不包含睡眠时间(单位:毫秒)
    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

    public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }

    public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageDelayed(msg, delayMillis);
    }

    /* post 方式 */

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

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

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

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

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

    // 其他几个 send 和 post 方法的实现类似(通过调用其它成员函数实现),这里不一一举出
    ...

Handler 处理消息

前面在将 Looper 时已经提到,Looper 对象调用 loop() 方法后开始循环处理消息队列,对消息的处理其实就是调用消息所绑定的 Handler 对象的 dispatchMessage 方法。

// 分发消息
msg.target.dispatchMessage(msg);

Handler类的 dispatchMessage 方法是处理消息的地方,其源代码如下:

public class Handler {
    ...

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {        
            // 如果消息设置了callback对象(即Runnable接口),则执行callback
            handleCallback(msg);            
        } else {
            if (mCallback != null) {
                // 如果 Handler 自身设置了 callback 对象,则执行callback
                // 如果返回true,则返回,否则继续执行
                if (mCallback.handleMessage(msg)) {    
                    return;
                }
            }
            // 调用Handler类的handleMessage方法处理消息
            handleMessage(msg);       
        }
    }

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

    Callback mCallback;

     /* 这种方法允许让activity等来实现Handler.Callback接口,避免了自己编写handler重写handleMessage方法。参考 http://alex-yang-xiansoftware-com.iteye.com/blog/850865  */
    public interface Callback {
        public boolean handleMessage(Message msg);
    }

    // Handler 的子类必须重写该方法实现对消息的处理
    public void handleMessage(Message msg) {
    }

    ...
}

如果发送消息采用的是 post Runnable 对象,那么 Message.callback 对象不为空。在处理消息时,处理 Message.callback 后退出,Handler 的成员函数 handlerMessage() 方法不会调用。所以如果采用 post Runnable 对象方式发送消息,我们不必重写 Handler 类的 handlerMessage() 方法。

传递 Handler

主线程是一个拥有handler和Looper的消息循环(默认创建好的)。主线程上创建的Handler会自动与它的Looper
相关联。我们可以将主线程上创建的Handler传递给另一线程。传递出去的Handler与创建它的线程 Looper 始终保持着联系。因此,任何已传出 Handler 负责处理的消息都将在主线程的消息队列中处理。

下图中,主线程和后台线程都关联了各自的 Looper,并有各自的 Handler,主线程需要下载图片时创建后台线程,同时将自己的 Handler 传递给后台线程,后台线程通过自己的 Handler(mHandler)发送下载图片的消息,并处理下载图片消息。如下图:

技术分享

也可以反过来,通过后台线程想主线程发送消息。后台线程在自己的 Handler 中处理完下载图片消息后,通过主线程的 Handler(mResponseHandler)发送图片下载完毕的消息,然后主线程在自己 Handler 中处理这个消息(如将图片进行显示)。如下图:
技术分享

Message

Message 可以看作是一个数据结构,用于保存消息相关的数据。MessageQueue内部使用了队列实现,并处理了并发访问,这里对其不做过多深入,只要知道 MessageQueue 是一个存储消息的队列即可。

创建 Message 实例

创建 Message 最好使用方法 Message.obtain() 或者 Handler.obtainMessage() 方法,因为其创建的 Message 实例来自消息池,可以减少创建新消息实例的时间和空间开销,该方法有很多重载形式。

public final class Message implements Parcelable {
    /* 下面字段是 public 访问权限,可以直接访问 */
    public int what;        // 用户定义的消息消息码用于区分不同消息
    public int arg1;        // 保存int数据
    public int arg2;        // 保存int数据
    public Object obj;      // 发送给接收者的任意对象
    ...


    // 从全局消息池中返回一个新的消息实例
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

    // 返回一个新的消息实例,并通过传入的参数初始化返回的消息实例成员字段
    public static Message obtain(Handler h, int what, 
            int arg1, int arg2, Object obj) {
        Message m = obtain();
        m.target = h;
        m.what = what;
        m.arg1 = arg1;
        m.arg2 = arg2;
        m.obj = obj;

        return m;
    }

    // 其他静态的 obtain() 方法类似,通过参数初始化返回的消息实例的部分成员字段
    ...



}

消息回收池

消息内部用一个链表结构实现了消息池,同时还有一个记录消息池当前大小的静态变量。回收消息时将消息放入链表,同时大小增 1;通过 Message.ObtainMessage() 从消息池获取消息时,从消息链表中取出一个消息,同时大小减 1。

public final class Message implements Parcelable {
    ...

    /*package*/ Message next;
    private static Message sPool;
    private static int sPoolSize = 0;
    private static final int MAX_POOL_SIZE = 50;

    ...

    // 回收消息实例,即放入到全局的消息池中
    public void recycle() {
        if (isInUse()) {            
            if (gCheckRecycle) {                     // 如果当前消息实例正在使用,并且开启了检查消息回收,则抛出异常
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        recycleUnchecked();        // 执行未受检查的消息回收
    }

    /* 回收未受检查的消息,消息可能正在使用中 */
    void recycleUnchecked() {
        flags = FLAG_IN_USE;       // 标记当前消息实例为"正在使用"
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;    
                sPool = this;
                sPoolSize++;
            }
        }
    }

    ...
}

Message 中保存 Bundle 类型数据

除了通过上面介绍的 Message 类的 public 成员字段保存数据外,Message 类还提供了 Bundle 类型的数据保存。

public final class Message implements Parcelable {
    ...

    Bundle data;

    // 返回当前消息中保存的 Bundle 对象,如果没有,则创建一个空的Bundle对象并返回
    public Bundle getData() {
        if (data == null) {
            data = new Bundle();
        }

        return data;
    }

    public void setData(Bundle data) {
        this.data = data;
    }

    // 返回当前消息中保存的 Bundle 对象,可能为空
    public Bundle peekData() {
        return data;
    }

    ...
}

Message 绑定 Handler

前面讲 Handler 时说过,每个 Message 只能与一个 Handler 关联,Message 对象中关联的 Handler 用于处理该消息。可以通过 Message 类的 sendToTarget() 方法发送消息,这与调用 Handler 类的 sendMessage() 本质一样。

public final class Message implements Parcelable {
    ...

    Handler target;        // 当前 Message 对象关联的 Handler 对象

    public void setTarget(Handler target) {
        this.target = target;
    }

    public Handler getTarget() {
        return target;
    }

    // 发送消息,实际就是调用 Handler 类的 sendMessage() 方法
    public void sendToTarget() {
        target.sendMessage(this);
    }
    ...
}

HandlerThread

Android 中的主线程(UI线程)创建时已经创建了一个 Looper,因此主线程中可以直接创建 Handler 并发送和处理消息。可以通过 Context 类中的成员方法 getMainLooper() 获取当前进程中主线程的 Looper。

// 在 Acitivity 中 
this.getMainLooper();

// 在 Fragment 中
getActivity().getMainLooper();

但是通过 Thread 类创建的其他线程是没有 Looper 的,需要我们手动创建 Looper,为了省去手动创建 Looper,Android 提供了HandlerThred 这个便捷类,用于创建一个带 Looper 的线程。其实现很简单,下面是他的完整源码,由于不多我就全部贴出来了:

package android.os;

public class HandlerThread extends Thread {
    int mPriority;        // 线程优先级
    int mTid = -1;        // 线程 id
    Looper mLooper;       // 消息循环

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }

    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }

    /* 子类重写该方法,添加消息循环开始前的准备操作,例如创建 Handler */
    protected void onLooperPrepared() {
    }

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();    // 创建 Looper 对象
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);    // 设置当前线程优先级
        onLooperPrepared();    // 调用成员方法
        Looper.loop();         // 进入消息循环
        mTid = -1;
    }

    /* 获取当前线程的 Looper 对象 */
    public Looper getLooper() {
        if (!isAlive()) {
            return null;    // 线程没有启动时,返回 null
        }

        // 如果线程已经被启动, 但 Looper 还没创建完成,则等待直到 Looper 创建 */
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

    /**
     * 退出线程的消息循环
     * Looper 退出后,任何发送给当前线程消息队列的消息都将失败,即方法返回 false
     * 使用这个方法是不安全的,因为在 Looper 退出前可能有些消息还没有分发出去
    */
    public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quit();
            return true;
        }
        return false;
    }

    /**
     * 安全地退出线程的消息循环
     * 该方法之所以安全是因为它会在退出前先将消息队列中剩余消息都分发出去,不过对于那些
     * 延迟消息依然不会分发出去,因为当前Looper需要尽快退出,不会为了不确定的延迟时间而
     * 等待。
     */
    public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quitSafely();
            return true;
        }
        return false;
    }

    // 返回线程 Id
    public int getThreadId() {
        return mTid;
    }
}

HanderThread 类的使用,一般自定义一个继承自 HandlerThread 的类,然后重载其 onLooperPrepared() 方法。使用 HandlerThread 类时,需要先调用 start() 方法将线程启动起来,然后调用 getLooper() 方法以确保该线程的 looper 创建完成,之后可以通过关联该线程的 Handler 发送和处理消息了。

参考

[1] Android SDK Documents
[2]《Android 编程权威指南》
[3] android的消息处理机制(图+源码分析)——Looper,Handler,Message

【Android】消息机制原理

标签:android   looper   handler   消息队列   消息   

原文地址:http://blog.csdn.net/xiaohui_hubei/article/details/45665423

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!