Android的消息机制@松鼠笔记

移动开发 简书

前言

Android的消息机制主要是指Handler的运行机制。Handler的运行需要底层的MessageQueue消息队列和Looper消息循环的支撑。简单来说,Android的消息机制就是:Handler给MessageQueue添加消息,然后Looper无限循环读取消息,再调用Handler处理消息。下面将从细节方面进行详细描述:

  1. 为什么会有消息机制
  2. 消息机制概述
  3. 消息机制源码分析

一、为什么会有消息机制

在Android的UI主线程中,不能执行超出5秒的耗时任务,并且所有View和ViewGroup都只能在UI主线程中运行。如果View或者ViewGroup在工作线程中运行,将会抛出【Only the original thread that created a view hierarchy can touch its views】异常,所以在android中会通过消息机制来解决线程和线程之间的通信问题。

二、消息机制概述

消息机制由以下四个部分组成:

  • Message消息(数据载体)
  • Handler消息处理器
    发送消息
    处理消息
  • MessageQueue消息队列(存储消息)
  • Looper轮循器
    去MessageQueue取消息
    分发给Handler处理

Message、MessageQueue、Looper、Handler之间的关系(即Handler机制原理)

Activity只要一创建Thread里面就多了一个looper(消息轮循器),在主线程里面要定义一个内部类handler,handler的初始化在主线程,有很多子线程要更新UI,Thread拿到主线程的handler,调用sendMessage方法,发送消息,把消息放到消息队列里面,只要消息进到消息队列,looper就会把消息取出来,调用里面的loop方法,取出来的消息就交给handler里面的handlerMessage方法,处理消息.

之所以这样做的原因是因为避免多线程并发更新UI线程所产生的问题的,如果我们允许其他子线程都可以更新界面,那么势必会造成界面的错乱(因为没有加锁机制),如果我们加锁,又会影响速度,而使用Handler机制,所有更新UI的操作,都是在主线程消息队列中轮询去处理的。

Message

Message消息,又叫task任务。封装了任务携带的信息和处理该任务的Handler。

Message的用法:

  • 可以通过Message.obtain()来从消息池中获得空消息对象
  • 如果message只需要携带简单的int信息,使用Message.arg1和Message.arg2来传递信息,这比用Bundle更省内存
  • 用message.what来标识信息,以便用不同方式处理message。

Handler

什么是Handler?Handler扮演了往MessageQueue中添加消息和处理消息的角色(只处理由自己发出的消息),即通知MessageQueue它要执行的任务(sendMessage),并在loop到自己的时候执行该任务(handMessage),整个过程是异步的。Handler创建时会关联一个looper,默认的构造方法会关联当前线程的looper。

handler必须关联一个looper才会起作用,Android UI主线程关联了一个Loop线程,但是我们自定义的线程必须开启Loop。

Handler发送消息

Handler能发送两种消息:一种是Runnable对象,一种是Message对象。但其实post发出的Runnable对象最后都被封装成message对象了。

具体:Handler创建完毕后,其内部的Looper以及MessageQueue就可以和Handler一起协同工作了,然后通过Handler的post方法将一个Runnable投递到Handler内部的Looper中去处理,也可以通过Handler的send方法发送一个消息,这个消息也会在Looper中去处理。其实post方法最终也是通过send方法来完成的。

  1. send方法发送消息(需要回调才能接收消息)

    sendMessage()立即发送Message到消息队列

    sendMessageAtFrontOfQueue() 立即发送Message到队列,而且是放在队列的最前面

    sendMessageAtTime() 设置时间,发送Message到队列

    sendMessageDelayed() 在延时若干毫秒后,发送Message到队列

send方法的工作过程:当Handler的send方法被调用时,它会调用MessageQueue的enqueueMessage方法将这个消息放到消息队列中,然后Looper发现有新消息到来时,就会处理这个消息,最终消息中的Runnable或者Handler的handleMessage方法就会被调用。注意:Looper是运行在创建Handler所在的线程中的,这样Handler中的业务逻辑就被切换到创建Handler所在的线程中去执行了。

  1. post方法发送消息(直接绑定handler当前线程执行,需要Runnable对象)

    post() 立即发送Message到消息队列

    postAtFrontOfQueue() 立即发送Message到队列,而且是放在队列的最前面

    postAtTime() 设置时间,发送Message到队列

    postDelayed() 在延时若干毫秒后,发送Message到队列

Activity中有一个方法runOnUiTheread()实际上就是Handler的post方法。

MessageQueue

消息队列,它的内部存储了一组消息,以队列的形式对外提供插入和删除的工作,但是它的内部存储结构并不是真正的队列,而是采用单链表的数据结构来存储消息列表。

Looper

消息循环。由于MessageQueue只是一个消息存储单元,它不能去处理消息,而Looper就填补了这个功能,Looper会以无限循环的形式去查找是否有新消息,如果有的话就处理消息,否则就一直等待着。

Looper中有一个特殊的概念:ThreadLocal,它的作用是可以在每个线程中存储数据。Handler创建的时候采用当前线程的Looper来构造消息循环系统,那么Handler内部如何获取到当前线程的Looper呢?这就要使用ThreadLoacal了,ThreadLocal可以在不同的线程中互不干扰地存储并提供数据,通过ThreadLoacal可以轻松获取每个线程的Looper。

注意:线程默认没有Looper的,如果需要使用Handler就必须为线程创建Looper。我们经常提到主线程,也叫UI线程,它就是ActivityThread,ActivityThread被创建时就会初始化Looper,所以在主线程中默认可以使用Handler。

三、消息机制源码分析

Message消息

两种创建方式:

Message msg1=Message.obtain();
Message msg2=new Message();

从源码上看:

/** 
*空的构造方法
*/
public Message() {
}

Handler

伪代码如下:

new Handler(){
  handleMessage(Message msg){
    //处理消息
  }
};

下面从源码角度看,new Handler做了哪些操作:

Handler的构造方法

public Handler() {
...
    //获取Looper(是在ActivityThread里面设置的Looper),只要一new Handler就会从当前主线程去拿Looper,在子线程去new会报错
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    //设置了一个队列
    mQueue = mLooper.mQueue;
    mCallback = null;
}

主线程设置Looper,在ActivityThread类里面

public static final void main(String[] args) {
...
    //第一步:主线程创建Looper
    //点进去,ActivityThread是运行在主线程的,调用了prepareMainLooper()这里面的方法,给当前线程set一个Looper,我们在
    //new Handler的时候,去Looper.myLooper,拿到的是主线程的Looper,这就是我们主线程的Looper何时去创建的。
    Looper.prepareMainLooper();
    if (sMainThreadHandler == null) {
        sMainThreadHandler = new Handler();
    }

    ActivityThread thread = new ActivityThread();
    thread.attach(false);

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }
    ////主线程创建Looper完成以后,就调用Looper的loop方法
    Looper.loop();
...

Looper

/** 
  *既然知道它是从prepare方法里面放的,就要知道哪个地方调用prepare方法
  */
public static final void prepare() {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    //第三步:在主线程中设置Looper。new Looper()里面同时创建了一个MessageQueue,这样就知道消息队列在哪new出来的
    sThreadLocal.set(new Looper());
}

public static final void prepareMainLooper() {
    //第二步: 调用prepare()方法
    prepare();
    setMainLooper(myLooper());
    if (Process.supportsProcesses()) {
        myLooper().mQueue.mQuitAllowed = false;
    }
}

主线程调用Looper.loop()方法,主线程就会阻塞,是一个死循环,使用管道(Pipe),管道是Linux中的一种进程间通信的方式。管道的原理:使用了特殊的文件,文件里面有两个文件描述符(一个是读取,一个是写入)。
应用场景:当Linux下有两个进程需要通信时,主进程拿到读取的描述符等待读取,没有内容就阻塞,然后另一个进程拿到写入描述符去写内容,唤醒主进程,主进程拿到读取描述符读取到的内容,继续执行。

管道在Handler的应用场景:Handler在主线程中创建,Looper会在死循环里等待取消息,一种是没取到消息,就阻塞;一种是主线程一旦被子线程唤醒,取到消息,就把Message交给Handler去处理。子线程是用Handler去发送消息,拿写入描述符去写消息,写完之后就唤醒主线程。

public static final void loop() {
...
    //死循环
    while (true) {
        //每次从队列中去取下一个数据(消息),要看什么时候给队列赋值,(在Handler创建的时候,就已经给它设置了队列)
        Message msg = queue.next(); // might block,取消息,如果没有消息,会阻塞
        //if (!me.mRun) {
        //    break;
        //}
        if (msg != null) {
            if (msg.target == null) {
                // No target is a magic identifier for the quit message.
                return;
            }
            if (me.mLogging!= null) me.mLogging.println(
                    ">>>>> Dispatching to " + msg.target + " "
                    + msg.callback + ": " + msg.what
                    );
            //msg.target其实就是一个Handler,Handler就会调用dispatchMessage方法。
            msg.target.dispatchMessage(msg);
            ...
        }
    }
}

Handler发送消息代码:

public boolean sendMessageAtTime(Message msg, long uptimeMillis)
{
...
        if (queue != null) {
        //this是当前的Handler类,这样每个Message里面有一个target,target值就是当前的Handler
        //把message的target置为当前发送的Handler,以便Looper取到message后根据target把message分发给相对应的Handler
        msg.target = this;
        //往队列里面添加Message
        sent = queue.enqueueMessage(msg, uptimeMillis);
    }
...
    return sent;

MessageQueue.enqueueMessage代码

final boolean enqueueMessage(Message msg, long when) {
...
        //when代表发送的时间
        msg.when = when;
        //Log.d("MessageQueue", "Enqueing: " + msg);
        Message p = mMessages;
        //when=0,不延迟,当前发送的message消息需要马上处理(当前只有一个Message),needWake置为true
        if (p == null || when == 0 || when < p.when) {
            msg.next = p;
            mMessages = msg;
            //需要唤醒
            needWake = mBlocked; // new head, might need to wake up
        } else {//当前有很多Message,如果当前Message排在其他message后面,当前Message不用优先处理,不用唤醒主线程,needWake置为false
            Message prev = null;
            while (p != null && p.when <= when)="" {="" prev="p;" p="p.next;" }="" msg.next="prev.next;" prev.next="msg;" needwake="false;" still="" waiting="" on="" head,="" no="" need="" to="" wake="" up="" 是否唤醒主线程,如果要唤醒,调用底层的jni方法去唤醒="" if="" (needwake)="" nativewake(mptr);="" return="" true;="" }
  

Handler.dispatchMessage()方法:

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        //如果有runnable就调用这个
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        //如果没有runnable就调用handleMessage方法,把Message交给Handle处理
        handleMessage(msg);
    }
}

提问:Handler与Looper的对应关系,是多对一。那么每个handler发送的消息,怎么保证不会处理别的handler发送的消息呢?

Handler底层用到了管道的通信方式,Handler在sendMessage的时候,会把Message的target置为Handler,在调用Looper的loop方法的时候,找到每个message,调用message的target,然后去dispatchMessage,这样Looper就会根据每个message找到需要相对应处理message的handler,因为在一个应用里面可能用到多个Handler,所以这个地方通过message的target去区分每个handler需要处理它自己发送的message。

小结:

大致流程:

在主线程创建Handler的时候,就可以拿到主线程的Looper,主线程创建完Looper之后,就会执行Looper中的loop方法,loop方法就会从MessageQueue消息队列中一个一个去拿Message消息,它是一个死循环。死循环里面使用了管道的通信方式,管道里面就会拿到文件描述符,一个是往里面存,一个是往里面写。存的时候,是通过上层的Handler,去sendMessage发送消息,调用MessageQueue的enqueueMessage方法,这样就拿到写入的描述符往里面写了,在loop方法里面有queue.next()方法,这个方法会拿到读取的描述符,当它没有读到消息时就阻塞,读到有消息的时候就调用dispatchMessage方法,dispatchMessage方法就会调用handleMessage方法处理消息

下面是Handler的流程图,描述了Handler消息机制:

image.png

以上是根据我的一些理解,做的总结分享,旨在抛砖引玉,希望有更多的志同道合的朋友一起讨论学习,共同进步!

简书稿源:简书 (源链) | 关于 | 阅读提示

本站遵循[CC BY-NC-SA 4.0]。如您有版权、意见投诉等问题,请通过eMail联系我们处理。
酷辣虫 » 移动开发 » Android的消息机制@松鼠笔记

喜欢 (0)or分享给?

专业 x 专注 x 聚合 x 分享 CC BY-NC-SA 4.0

使用声明 | 英豪名录