Android笔记之消息处理机制

Android笔记之消息处理机制

本文转载于 http://www.cnblogs.com/qingblog/archive/2012/06/27/2566021.html并加以整理

要素概念

  1. Message
    消息,线程间通讯的数据单元。
  2. Message Queue
    消息队列(FIFO),用来存放通过Handler发布的消息。封装于Looper里面。
  3. Handler
    Handler是Message的主要处理者,负责将Message发送到Message Queue和对Message Queue的Message进行处理。
  4. Looper
    消息循环器,扮演Message Queue和Handler之间桥梁的角色,循环取出Message Queue里面的Message,并交付给相应的Handler进行处理。
  5. 线程
    UI thread 通常就是main thread,而Android启动程序时会替它建立一个Message Queue。每一个线程里可含有一个Looper对象以及一个MessageQueue数据结构。在你的应用程序里,可以定义Handler的子类别来接收Looper所送出的消息。

运行机理

  每个线程都可以并仅可以拥有一个Looper实例,消息队列MessageQueue在Looper的构造函数中被创建并且作为成员变量被保存,也就是说MessageQueue相对于线程也是唯一的。Android应用在启动的时候会默认会为主线程创建一个Looper实例,并借助相关的Handler和Looper里面的MessageQueue完成对Activities、Services、Broadcase Receivers等的管理。而在子线程中,Looper需要通过显式调用Looper. Prepare()方法进行创建。Prepare方法通过ThreadLocal来保证Looper在线程内的唯一性,如果Looper在线程内已经被创建并且尝试再度创建”Only one Looper may be created per thread”异常将被抛出。

  Handler在创建的时候可以指定Looper,这样通过Handler的sendMessage()方法发送出去的消息就会添加到指定Looper里面的MessageQueue里面去。在不指定Looper的情况下,Handler绑定的是创建它的线程的Looper。如果这个线程的Looper不存在,程序将抛出”Can’t create handler inside thread that has not called Looper.prepare()”。因此我们可以指定子线程的Handler使用 UI 线程的Looper,这样我们就可以通过该Handler通知UI 线程更新。

  整个消息处理的大概流程是:1. 包装Message对象(指定Handler、回调函数和携带数据等);2. 通过Handler的sendMessage()等类似方法将Message发送出去;3. 在Handler的处理方法里面将Message添加到Handler绑定的Looper的MessageQueue;4. Looper的loop()方法通过循环不断从MessageQueue里面提取Message进行处理,并移除处理完毕的Message;5. 通过调用Message绑定的Handler对象的dispatchMessage()方法完成对消息的处理。

  在dispatchMessage()方法里,如何处理Message则由用户指定,三个判断,优先级从高到低:1. Message里面的Callback,一个实现了Runnable接口的对象,其中run函数做处理工作;2. Handler里面mCallback指向的一个实现了Callback接口的对象,由其handleMessage进行处理;3. 处理消息Handler对象对应的类继承并实现了其中handleMessage函数,通过这个实现的handleMessage函数处理消息。

线程

  在Android,线程分为有消息循环的线程和没有消息循环的线程,有消息循环的线程一般都会有一个Looper; Android 应用的主线程(UI线程)就是一个消息循环的线程。
  对于没有消息循环的线程,当Run()方法运行完后,线程就会退出。
  针对这种消息循环的机制,我们引入一个新的机制Handle,我们有消息循环,就要往消息循环里面发送相应的消息,自定义消息一般都会有自己对应的处理,消息的发送和清除,消息的的处理,把这些都封装在Handle里面,注意Handle只是针对那些有Looper的线程,不管是UI线程还是子线程,只要你有Looper,我就可以往你的消息队列里面添加东西,并做相应的处理。但是只要是关于UI相关的东西,就不能放在子线程中,因为子线程是不能操作UI的,只能进行数据、系统等其他非UI的操作。


线程的魔法师 Looper

  关于标准的异步消息处理线程(拥有Looper,可异步处理信息), Android官方文档上的Damo:

class LooperThread extends Thread {  
      public Handler mHandler;    
      public void run() {  
          // 将当前线程初始化为Looper线程 
          Looper.prepare();    
          /*mHandler = new Handler() {  
              public void handleMessage(Message msg) {  
                  // 进行Message处理  
              }  
          };*/ 
          // 开始循环处理消息队列 
          Looper.loop();  
      }  
  }  

加入Handler后线程效果如下:
Image

下面进行分析上面调用方法的源码:
  1)线程就会拥有一个 Looper对象,它的内部维护了一个消息队列MQ。 Looper.java部分源码:

public class Looper {
    // 每个线程中的Looper对象其实是一个ThreadLocal,即线程本地存储(TLS)对象
    private static final ThreadLocal sThreadLocal = new ThreadLocal();

    // Looper内的消息队列
    final MessageQueue mQueue;

    // 当前线程
    Thread mThread;

    // 。。。其他属性

    // 每个Looper对象中有它的消息队列,和它所属的线程
    private Looper() {
        mQueue = new MessageQueue();
        mRun = true;
        //标志Looper持有的当前线程
        mThread = Thread.currentThread();
    }

    // 我们调用该方法会在调用线程的TLS中创建Looper对象
    public static final void prepare() {
        if (sThreadLocal.get() != null) {
            // 试图在有Looper的线程中再次创建Looper将抛出异常
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper());
    }
    // 其他方法
}

  其核心就是将looper对象定义为ThreadLocal。ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。详细可参考理解ThreadLocal.

  2) 只是拥有Looper对象,还要调用 Loop(); 它不断从自己的MQ中取出队头的消息(也叫任务)执行。
Image

public static final void loop() {
        Looper me = myLooper();  //得到当前线程Looper
        MessageQueue queue = me.mQueue;  //得到当前looper的MQ

        ...

        // 开始循环
        while (true) {
            Message msg = queue.next(); //May block// 取出message
            if (msg != null) {
                if (msg.target == null) {//Target变量为发该Message的Handler
                    // message没有target为结束信号,退出循环
                    return;
                }

                ...

                // 非常重要!将真正的处理工作交给message的target,即后面要讲的handler
                msg.target.dispatchMessage(msg);

               ...

                // 回收message资源
                msg.recycle();
            }
        }
    }

  其它Looper经常使用的方法:

//得到当前线程looper对象
public static final Looper myLooper() {  
    // 在任意线程调用Looper.myLooper()返回的都是那个线程的looper   
    return (Looper)sThreadLocal.get();  
} 

//得到looper对象所属线程
public Thread getThread() {  
   return mThread;  
}  

//结束looper循环
public void quit() {  
   // 创建一个空的message,它的target为NULL,表示结束循环消息   
   Message msg = Message.obtain();  
   // 发出消息   
   mQueue.enqueueMessage(msg, 0);  
}  

总结:

  1. 每个线程有且最多只能有一个Looper对象,它是一个ThreadLocal
  2. Looper内部有一个消息队列,loop()方法调用后线程开始不断从队列中取出消息执行
  3. Looper使一个线程变成Looper线程。

异步处理大师 Handler

  handler扮演了往MQ上添加消息和处理消息的角色(只处理由自己发出的消息),即通知MQ它要执行一个任务(sendMessage),并在loop到自己的时候执行该任务(handleMessage),整个过程是异步的。handler创建时会关联一个looper,默认的构造方法将关联当前线程的looper,不过这也是可以set的。

创建Handler

默认的构造方法:

public Handler()
public Handler(Callback callback)
public Handler(Looper looper)
public Handler(Looper looper, Callback callback)
...

源代码如下:

public class handler {  
    final MessageQueue mQueue;  // 关联的MQ   

    final Looper mLooper;  // 关联的looper   

    final Callback mCallback;   

    // 其他属性   

    public Handler() {  

        ...

        // 默认将关联当前线程的looper   
        mLooper = Looper.myLooper();  
        // looper不能为空,即该默认的构造方法只能在looper线程中使用   
        if (mLooper == null) {  
            throw new RuntimeException(  
                "Can't create handler inside thread that has not called Looper.prepare()");  
        }  
        // 重要!!!直接把关联looper的MQ作为自己的MQ,因此它的消息将发送到关联looper的MQ上   
        mQueue = mLooper.mQueue;  
        mCallback = null;  
    }  
    // 其他方法   
}  

  一个线程可以有多个Handler,但是只能有一个Looper!
  在创建 Handler对象,要确保已经为该线程创建Looper对象,不然会抛出异常。

    if (mLooper == null) {  
        throw new RuntimeException(  
            "Can't create handler inside thread that has not called Looper.prepare()");  
    }  
Handler发送消息

Handler和Looper都创建成功了,接下来就是发送信息。 可以使用:

 post(Runnable)
 postAtTime(Runnable, long)
 postDelayed(Runnable, long)
 sendEmptyMessage(int)
 sendMessage(Message)
 sendMessageAtTime(Message, long)
 sendMessageDelayed(Message, long)

从上面直观看到,Handler能发2种消息: Runnable对象 和 Message 对象。但通过源码学习,其实只有一种Message对象,对于 Runnable对象会被封装成Message对象。

    public final boolean post(Runnable r)
    {
       //通过sendMessageDelayed发送Runnable消息
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
    //封装成Message
    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

最后都会调用到sendMessageAtTime(), 下面看看其源码:

    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {//MessageQueue为空,抛出异常
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        //Delay时 uptimeMillis = currentTime + delayTime;
        return enqueueMessage(queue, msg, uptimeMillis);
    }

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        //最终把Message推送到MessageQueue
        return queue.enqueueMessage(msg, uptimeMillis);
    }
Handler处理消息

  从上面的Looper分析中, 我们知道在调用Looper.prepare()和创建Handler对象后,就调用 Looper.Loop() 让Looper进行循环取Message进行处理。

public static final void loop() {
        Looper me = myLooper();  //得到当前线程Looper
        MessageQueue queue = me.mQueue;  //得到当前looper的MQ

        ...

        // 开始循环
        while (true) {
            Message msg = queue.next(); //May block// 取出message
            if (msg != null) {
                if (msg.target == null) {//Target变量为发该Message的Handler
                    // message没有target为结束信号,退出循环
                    return;
                }

                ...

                // 非常重要!将真正的处理工作交给message的target,即后面要讲的handler
                msg.target.dispatchMessage(msg);

               ...

                // 回收message资源
                msg.recycle();
            }
        }
    }

因此当MessageQueue有Message可取出时,会调用msg.target.dispatchMessage(msg) 进行处理消息,而target正是我们Handler, 看看Handler的dispatchMessage(msg):

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
    //直接调用其run()
    private static void handleCallback(Message message) {
        message.callback.run();
    }
    //钩子方法
    public void handleMessage(Message msg) {}

从该方法来看,处理消息优先级是: message.callback.run() -> mCallback.handleMessage(msg) -> handleMessage(msg);

1) 首先来看看message.callback.run(): callback是Runnable对象,我们生成Message对象时,有一个生成方法会传入callback(Runnable), 另外需要注意的:

static Message  obtain(Handler h, Runnable callback)

2) 对于 mCallback, 则在Handler实例对象时,进行传入:

public Handler(Callback callback)
public Handler(Looper looper, Callback callback)

// Handler.java
public interface Callback {
    public boolean handleMessage(Message msg);
}

3) handleMessage(msg)在Handler中空方法,需要用户在创建Handler时进行重写 handlerMessage(Message msg)方法进行处理Message。

特点:

  1. Handler 可以在任意线程发送消息,这些消息会被添加到关联的MQ上
    Image
  2. Handler 是在它关联的looper线程中处理消息的。
    Image

  通过这2个特点,就可以完美解决Android不能在其它非UI 线程更新UI的问题。Android的主线程有一个 Looper线程,在应用创建里, 就会自动创建一个Looper(稍后介绍), 也就是我们可以在其它非UI 线程 通过创建传入 UI 线程的Looper 的Handler: Handler(Context.getMainLooper()) 。 把子线程的更新UI操作 在Handler的 handlerMessage()中实现。 最后 handlerMessage() 的方法 会在UI 线程中进行调用, 从而实现 非UI线程 通过 Handler推送Message进行更新 UI.


UI线程是怎么创建 Looper?

   Looper有专门为 UI 线程设计了对应的方法: prepareMainLooper() 和 getMainLooper()。 源代码如下:

  private static Looper sMainLooper;  // guarded by Looper.class
    /**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
   public static void prepareMainLooper() {
        prepare(false);//创建Looper对象,false表示该Looper不能被 调用quit()停止
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            //保存Looper对象
            sMainLooper = myLooper();
        }
    }

    /**
     * Returns the application's main looper, which lives in the main thread of the application.
     */
    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }

   在ActivityThread对象的 public static void main(String[] args)中进行调用 Looper.prepareMainLooper().


  对于 Message对象的实例,虽然其有构造方法,但为了更好利用资源,请使用 obrain() 系列生成 Message对象, 那么Message是如何利用之前使用过的Message对象来防止内存消耗的?

基本原理

  创建一个回收池(链表)来保存把使用过Mesage对象进行保存起来, 当需要创建Message时,就直接从其回收池中获取Message对象。

源代码实现

Message 持有的变量:

    // sometimes we store linked lists of these things
    // 使用该变量可实现一个持有头的链表
    /*package*/ Message next;
    //同步锁
    private static final Object sPoolSync = new Object();
    //回收池(其实为链表的表头)
    private static Message sPool;
    //当前的回收池里大小
    private static int sPoolSize = 0;
    //回收池的容量
    private static final int MAX_POOL_SIZE = 50;

  当Message进行回收时,最后会调用 Message.recycleUnchecked()进行在回收池中插入当前Message对象,源代码如下:

    void recycleUnchecked() {

        ...

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;//让当前下一个Message对象链接到回收池的表头
                sPool = this;//回收池的表头换成当前需要的Message
                sPoolSize++;
            }
        }
    }

  那么Message的obtain()在生成 新的Message时更简单,如果回收池里已有废弃的Message, 直接将链表的表头清除里面相关信息后取出使用。 源代码如下:

   public static Message obtain() {
        synchronized (sPoolSync) {
            //这里有一个 Message池,
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;//使用之前Message对象,
            }
        }
        return new Message();
    }

  通过上面的学习,以后在内存优化项目上,可以通过创建回收池(链表)来管理对象,可一步的优化内存使用。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值