Android消息处理机制源码分析(一):整体过程

什么是Android消息机制?从开发者使用的角度,消息机制只有一个上层接口,就是我们经常使用的Handler,通过Handler可以轻松的将任务切换到Handler所在的线程去执行,而最常见的做法相信每个开发者都用过,就是更新UI。Android的UI控件的操作只能在主线程(也叫UI线程)中执行,但我们经常在子线程或者线程池做一些耗时或者联网的工作,当工作完成我们希望在UI中做一些更新,比如关闭对话框、改变按钮显示等等。那么最常见也是比较容易的方法就是创建一个Handler对象,将操作在Handler的handleMessage函数中执行(也就是在主线程中),只需要在子线程发送消息,就可以轻松切换到主线程。当然我们也可以看出Handler不是专门来做此操作才出现的,只是非常适合这个情况,那么Handler是如何轻松切换到主线程的呢?为什么Handler就可以在主线程中创建呢?我们从源码的角度来做一次全面的分析。

一、消息机制的角色分析
首先我们介绍一下消息机制的几位主人公,正是有它们的通力合作,消息机制才能正常的运行:
1、Handler:处理器,负责的内容是消息的发送和具体处理流程,一般使用时由开发者重写handleMessage函数,根据自定义的不同message做不同的UI更新操作;
2、Message:消息对象,代表一个消息实体,存放消息的基本内容;
3、MessageQueue:消息队列,按顺序存放消息实体,由单链表实现,被Looper(4)所使用(一个Looper具有一个消息队列);
4、Looper:循环器,也是我们的消息机制的主角,一个线程至多具有一个并且与线程一一对应(如何理解等会来说),负责的内容是不断从消息队列取出放入的消息并且交由消息对应的Handler进行处理(Handler的具体handleMessage操作);
5、ThreadLocal:线程本地数据,是一个线程内部的数据存储类,为每个线程存储属于自己的独立的数据。

二、消息机制总览
我们来用最简短的语言说明一下消息循环的整个过程,有个整体性的认识,之后再进行逐一的进行源码分析。
1、首先,我们知道ThreadLocal是线程内部的数据存储类,一个线程对应一个自己独一无二的数据,而我们的主角Looper就是这样一个对象,每个线程都可以有自己独一无二的Looper,而Looper自己具有一个属于自己的MessageQueue,它不断地从MessageQueue中取Message(注意,Looper的代码是运行在自己对应的线程中的),如果MessageQueue中没有消息,Looper只会不断地循环尝试取消息(阻塞)。
2、这时,我们在主线程创建了Handler对象,它需要它所在的线程(这里是主线程)所拥有的Looper对象(也就是说,没有Looper的线程创建不了Handler,后面我们也会看到具体代码),一旦我们用Handler发送了消息(可以在别的线程中,这里假设在某个子线程中),Handler就会把这个消息放入自己拥有的Looper对象的属于这个Looper对象的MessageQueue中(这句有点拗口,就是放入Looper的MessageQueue中)。
3、我们已经知道Looper会不断地尝试从自己的MessageQueue中取出消息,我们刚刚放入的消息立刻被Looper取出来了,它得到了消息就执行了发出消息的Handler(也就是2过程我们所创建的)的消息处理函数handleMessage,我们编写的UI更新操作就在Looper对象的代码中执行了,而我们刚才也说了这个Looper运行在我们创建Handler的线程中的,也就是主线程(UI线程),那这样一来就达到了我们的目标,成功的把代码执行从子线程切换到了主线程中,这个过程我们也就有个总览了,下面我们进行源码的一步一步分析。

三、MessageQueue与Looper
1、MessageQueue
MessageQueue的作用我们已经大致了解了,就是一个可以存放消息和可以取消息的消息队列,那么它主要的两个操作就是插入和读取,分别对应的函数就是enqueueMessage函数和next函数,分别是用作插入消息和读取并删除消息,我们来看一下具体的实现源码:

(1)enqueueMessage方法

final boolean enqueueMessage(Message msg, long when) {  
    if (msg.isInUse()) {  
        throw new AndroidRuntimeException(msg + " This message is already in use.");  
    }  
    if (msg.target == null) {  
        throw new AndroidRuntimeException("Message must have a target.");  
    }  

    boolean needWake;  
    synchronized (this) {  
        if (mQuiting) {  
            return false;  
        }  

        msg.when = when;  
        Message p = mMessages;  
        if (p == null || when == 0 || when < p.when) {  
            // New head, wake up the event queue if blocked.  
            msg.next = p;  
            mMessages = msg;  
            needWake = mBlocked;  
        } else {  
            // Inserted within the middle of the queue.  Usually we don't have to wake  
            // up the event queue unless there is a barrier at the head of the queue  
            // and the message is the earliest asynchronous message in the queue.  
            needWake = mBlocked && p.target == null && msg.isAsynchronous();  
            Message prev;  
            for (;;) {  
                prev = p;  
                p = p.next;  
                if (p == null || when < p.when) {  
                    break;  
                }  
                if (needWake && p.isAsynchronous()) {  
                    needWake = false;  
                }  
            }  
            msg.next = p; // invariant: p == prev.next  
            prev.next = msg;  
        }  
    }  
    if (needWake) {  
        nativeWake(mPtr);  
    }  
    return true;  
}  

首先,进行消息的合理性的检查,防止两个非法情况:消息已经在使用中和消息没有target(也就是我们发出消息的Handler,它是作为target最终要来真正做相应处理);之后,进行单链表的插入操作。插入完成之后,我们会看到这样一个操作:

if (needWake) {  
    nativeWake(mPtr);  
}  

这个needWake和本地方法nativeWake(mPtr)有一些关系,同样有关的还有插入过程的一些if语句,但这里对我们了解整体机制没有非常重要的作用,本地方法也非常复杂,要与next方法中的本地方法一起讲解,在讲完整体机制后我们再详细介绍。

(2)next方法
省略了部分非关键代码(用于一些优化操作)

final Message next() {  
     int pendingIdleHandlerCount = -1; //空闲处理器的个数 
     int nextPollTimeoutMillis = 0;  //等待时间

     for (;;) {  
         if (nextPollTimeoutMillis != 0) {  
             Binder.flushPendingCommands();  
         }  
         nativePollOnce(mPtr, nextPollTimeoutMillis); //本地方法调用 

         synchronized (this) {  
             if (mQuiting) {  
                 return null;  
             }  

             // Try to retrieve the next message.  Return if found.  
             final long now = SystemClock.uptimeMillis();  
             Message prevMsg = null;  
             Message msg = mMessages; 
             if (msg != null) {  
                 if (now < msg.when) {  
                     // Next message is not ready.  Set a timeout to wake up when it is ready.  
                     nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);  
                 } else {  
                     // Got a message.  
                     mBlocked = false;  
                     if (prevMsg != null) {  
                         prevMsg.next = msg.next;  
                     } else {  
                         mMessages = msg.next;  
                     }  
                     msg.next = null;  
                     if (false) Log.v("MessageQueue", "Returning message: " + msg);  
                     msg.markInUse();  
                     return msg;  
                 }  
             } else {  
                 // No more messages.  
                 nextPollTimeoutMillis = -1;  
             }  
             ...
 }  

next的作用就是为了获取队列中的被放入的消息,我们先介绍一下变量mMessages,这个其实就是MessageQueue的链表头,也就是最先需要处理的消息;接着我们可以看出next是一个阻塞方法,如果没有寻找到需要处理的消息,即msg == null的情况,那么next方法一直在无限循环中;如果寻找到了位于链表头的消息,还要判断是否处于需要执行的时间,如果这些都满足了要求,就把message取走(移除)并返回,关键代码如下:

// Got a message.  
mBlocked = false;  
if (prevMsg != null) {  
    prevMsg.next = msg.next;  
} else {  
    mMessages = msg.next;  
}  
msg.next = null;  
if (false) Log.v("MessageQueue", "Returning message: " + msg);  
msg.markInUse();  
return msg;  

这里有本地方法上面我们也说到了,其实涉及到了底层的一些处理,其中最重要的就是这句函数:

nativePollOnce(mPtr, nextPollTimeoutMillis);  

并且我们还有两个变量的作用没有讲解,就是next开头创建的两个变量:

int pendingIdleHandlerCount = -1; //空闲处理器的个数 
int nextPollTimeoutMillis = 0;  //等待时间

这两个变量的作用我们也没有讲解,他们与这个本地方法有着非常重要的联系,我们会在底层源码分析中进行介绍,在分析完整体机制之后会着重分析MessageQueue的底层部分,先不着急,我们现在只需要确定MessageQueue是作为一个存放消息的消息队列就行了,但实际上它在消息循环中的地位非常的特殊,讲完整体框架我们再细看这个类的实现,从整体来看我们只需要大致了解它添加、读取的作用即可。

2、Looper
Looper作为我们消息处理的主人公,在消息处理中起了非常大的作用,我们来好好看一下Looper的具体实现。Looper拥有一个属于自己的MessageQueue,并且一直查看其中是否有新的消息,如果有就会立刻进行处理,否则就会一直阻塞在循环中。

(1)首先看一看Looper拥有的MessageQueue

//成员变量之一
final MessageQueue mQueue;
//私有构造函数
private Looper() {
    mQueue = new MessageQueue();
    mRun = true;
    mThread = Thread.currentThread();
}

这里可以看出,每个Looper对象都有自己的MessageQueue,在构造函数中初始化。

(2)Looper的初始化过程
既然Looper是一个与线程有关的变量,它在哪里初始化呢?前面我也说过,一个线程没有Looper就不能创建Handler,那我们平时怎么没接触到Looper的初始化呢?因为我们那时在主线程中创建的Handler,而主线程本身就有一个主消息循环(MainLooper),就不用我们自己去创建了。我们来看一看如何在子线程中初始化Looper,开启属于自己的消息循环:

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();//开启消息循环
  }
}

这里用到了两个颇为关键的方法,prepare和loop,我们来详细的看一下

2.1 prepare方法

//静态成员变量之一
private static final ThreadLocal sThreadLocal = new ThreadLocal();

//静态方法prepare
public static final void prepare() {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper()); //放入新的Looper对象(对应当前线程)
}  

首先我们要明确一下刚才说的另一个小主角:ThreadLocal,它的作用就是为每个线程存储属于每个线程自己的数据,这里的sThreadLocal就是为每个线程存储属于自己的Looper变量,prepare方法做了什么呢?首先判断当前线程是否已经有Looper变量,如果有就报异常,因为每个线程不能有多个Looper对象;然后为当前线程创建属于自己的Looper对象,并保存到sThreadLocal变量中,这样当前线程就具有了属于自己的Looper。

2.2 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;  
      ...  
      for (;;) {  
          Message msg = queue.next(); // might block  
          if (msg == null) {  
              // No message indicates that the message queue is quitting.  
              return;  
          }  

          msg.target.dispatchMessage(msg);  

          msg.recycle();  
      }  
}  

终于看到loop方法的真面目,首先我们要明确一个重要的观点,loop函数是运行在Looper所在的线程中的,比如我们的loop就是运行在我们的LooperThread中,而主线程得loop方法就是运行在主线程中。我们来慢慢看loop的操作:

步骤1、得到当前所在线程的Looper对象,调用了函数myLooper

final Looper me = myLooper();  

这个myLooper的作用就是获取当前线程的Looper对象,相信大家估计都知道是怎么实现的了:

//静态成员变量之一
private static final ThreadLocal sThreadLocal = new ThreadLocal();

//静态方法
public static final Looper myLooper() {
    return (Looper)sThreadLocal.get();
}

其作用就是获取当前线程的Looper对象,从代码中我们可以很清楚的看到,从静态成员变量ThreadLocal中获取当前线程的Looper对象,我们接着往下看。

步骤2:

if (me == null) {  
    throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");  
}  

这里做一个简单的判断,如果没有调用之前的Looper.prepare()进行Looper对象的创建,这里就会触发异常,下面进入关键的loop阶段:

步骤3:
首先获取了当前线程Looper对象的MessageQueue,紧接着开始循环,在循环中不断进行获取message的尝试:

final MessageQueue queue = me.mQueue;  
...  
for (;;) {  
    Message msg = queue.next(); //阻塞方法,可能阻塞
    if (msg == null) {  
        // No message indicates that the message queue is quitting.  
        return;  
    }  

    msg.target.dispatchMessage(msg);  //Handler的消息处理函数执行

    msg.recycle();  
}  

MessageQueue的next方法我们刚才已经分析过了,这是一个阻塞方法,如果没有Message会一直阻塞;一旦获取到了Message,首先判断是否为null,这个的作用就是用来判断Looper的退出,当Looper调用quit方法,就会调用MessageQueue的quit方法,它的next就会返回null,然后loop方法就return,即退出了循环,达到了退出的作用。一旦获取到了不为null的Message,就会在loop函数中调用msg的target的dispatchMessage函数,Message我们知道,那这个target是什么呢?我们来看一看Message的成员变量中的target:

Handler target;    // 发送和处理消息的 Handler  

原来target就是我们的Handler对象,也就是我们下一个要介绍的主人公,现在我们只要知道一点,这个Handler的dispatchMessage函数是在loop中执行的,也就是Looper所在的线程中执行的(也可以说是Looper对应的线程),其实这里已经完成了我们所熟知的线程切换,Handler发送消息的线程已经切换到执行消息处理的Looper所在线程(熟知的主线程)了。

四、Handler
终于要说到我们经常使用的Handler类了,说了前面这么多,相信大家对Handler在消息循环中的地位也有了一定的认识。我们都知道真正消息处理的代码是写在Handler中的,但Looper还是真正的主角,毕竟Handler中的处理函数还是Looper调用的,可以说一切的获取Message、执行处理都是Looper调用,Handler只是在别的线程中“发出”一个Message而已,之后的执行通过对Looper的分析我们都知道是Looper的活。这里为什么说“发出”要加双引号更多的是因为不是真正意义的发出,通过下面的分析我们可以知道,其实真正的操作只是在Looper的MessageQueue中加入新的Message对象而已。
首先来看一看Handler的构造函数,也是我们创建Handler的调用之一:

(1)Handler构造函数

public Handler(){
    ...
    mLooper = Looper.myLooper(); //获取Looper对象
    if (mLooper == null) {
        throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue; //获取消息队列
    mCallback = null;
}

这里我们清晰的看到Handler的默认构造函数做的第一件事就是获取当前线程的Looper对象,如果没有Looper对象自然也创建不了默认的Handler对象(前面的结论在这里得到了印证);第二件事就是获取当前线程的Looper的MessageQueue,我们记住这个mQueue,实际上大家肯定明白为什么要这个mQueue,其实就是为了插入消息才获取的;第三个这个mCallback是干嘛的呢,我们往下看就知道了。

(2)sendMessage的系列函数

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

public final boolean sendMessageDelayed(Message msg, long delayMillis){
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

public boolean sendMessageAtTime(Message msg, long uptimeMillis){
    boolean sent = false;
    MessageQueue queue = mQueue;
    if (queue != null) {
        msg.target = this; 
        //把target设置为自己,这样我们就知道Message的target就是发送它的Handler
        sent = queue.enqueueMessage(msg, uptimeMillis);
        //队列的添加消息操作
    }
    else {
        RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
    }
    return sent;
}

无论是哪种sendMessage方法,最终都会调用sendMessageAtTime函数,我们来看看它做了哪些操作:

步骤一:获取消息队列,其实就是Handler初始化时获取到的

MessageQueue queue = mQueue;

步骤二:当队列不为空时(正常情况),做了两件事,第一件事把消息的target设置为自己,这也印证了我们之前讲到的msg.target对象;第二件事就是简单的队列添加操作,调用之前分析过的enqueueMessage函数,关键代码如下:

if (queue != null) {
    msg.target = this; 
    //1.把target设置为自己,这样我们就知道Message的target就是发送它的Handler
    sent = queue.enqueueMessage(msg, uptimeMillis);
    //2.队列的添加消息操作
}

(3)dispatchMessage函数
分析了前面那么多,终于要说到我们分析loop函数时调用的那句msg.target.dispatchMessage(msg);中的关键函数dispatchMessage,让我们来看看代码,但大家一定要清楚,这句话是运行在Looper所属于的线程中的哟。

public void dispatchMessage(Message msg) {
    //1.如果msg本身设置了callback,则直接交给这个msg的callback处理了
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
    //2.如果该handler的callback有的话,则交给这个callback处理了
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
            return;
        }
    }
    //3.在msg和自身都没有callback 的情况下,运行自己的handleMessage(是由我们重写的handleMessage,Handler本身的handleMessage什么也不会干)
    handleMessage(msg);
}

其实我的注释已经把能说的都说了,我简单的解释一下:

步骤1:首先就是msg是否有callback,而这个callback我们可以看Message的源码中:

Runnable callback;   // 处理消息的回调

可以看到这是一个Runnable对象,实际就是Handler的post函数所传递的参数,它的调用也非常简单,handleCallback函数只做了一件事:

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

实际上就是针对post方法来进行执行而已,这里延伸一下post方法,我们知道他也是Handler提交任务的一种方式,具体过程如下:

handler.post(new Runnable(){
@Override
public void run() {
    //do something
}});

那么这个post如何实现呢?

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

又回到了我们刚才的sendMessageDelayed函数,相信大家对于getPostMessage是干嘛的也应该有一定想法了,不就是创建Runnable对应的Message嘛,对,我们来看一下实现:

private final Message getPostMessage(Runnable r) {
    Message m = Message.obtain(); //新建一个Message
    m.callback = r; //设置Runnable对象
    return m;
}

这下知道了这个post是干嘛的了吧,我们也可以知道上面的msg的callback是怎么来的了。

步骤2:检查Handler本身是否有callback,也就是我们之前在初始化函数中说到的mCallback对象,它是一个回调接口,定义如下:

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

那他是干嘛的呢?实际上他就是Handler使用的另外一种方式,我们平时都是使用派生Handler的子类,重写handleMessage函数来实现自己的Handler;而另外一种方式就是这个Callback接口,通过传入实现的Callback接口就可以实现Handler处理的自定义,它的另一个构造函数如下所示:

public Handler(Callback callback) {
    ...
    mLooper = Looper.myLooper(); //获取Looper对象
    if (mLooper == null) {
        throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
}

这就是通过实现Callback来进行创建Handler的过程,只不过多了一个mCallback的设置而已,如果你设置了callback,那么就可以直接进行消息的处理。

步骤3:在上面两步都没有执行的情况下,进行消息处理,这里就会调用我们熟悉的handleMessage函数了,你的重写方法真正的得到了执行:

handleMessage(msg);

这样我们就清楚了整个任务调用流程。

五、主线程消息循环的开启
上面我们也说了,之所以你可以不用Looper就创建Handler的原因就是因为你在主线程中进行的操作,而主线程会自己创建Looper循环,那具体在哪里呢,就在主线程的入口函数main中:

public static void main(String[] args) {
    ...
    Looper.prepareMainLooper();
    ...//省略中间代码
    Looper.loop();
}

那这个prepareMainLooper函数是怎么实现的呢?我们来看一看:

public static final void prepareMainLooper() {
    prepare();
    setMainLooper(myLooper());
    ...
}
private synchronized static void setMainLooper(Looper looper) {
    mMainLooper = looper;
}

我们可以看出主要工作就是进行Looper的准备并设置MainLooper对象。

总结:通过整体的流程分析和各个类的关键函数分析,我们已经知道了Handler线程切换的秘密了,其实Handler并不是真正的主角,真正重要的是Looper和MessageQueue,它们才是线程切换的真正主角,但我前面提到的MessageQueue我也说了,它有着非常重要的作用,具体的作用就离不开它的两个本地方法,其实在C++层,MessageQueue做了更多的操作来进行处理,这个底层部分我们在下一篇分析中来慢慢看。不过通过这第一篇的分析,我们已经对消息处理机制有了总体的了解,起码知道Handler的实现比使用要复杂的多。

如果觉得我的文章里有任何错误,欢迎评论指正!如果觉得写得好也欢迎大家留言或者点赞,一起进步、一起学习!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值