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

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

您可能感兴趣的

AndroidButtonProgress AndroidButtonProgress This is a combination of button and progress bar. There are 4 states in this view first Idle, Indeterminate, Determinate a...
LinkedIn now offers courses to prepare for the Goo... Google and LinkedIn have announced a partnership that should be very useful to the Android Developer community. The two companies are teaming up to of...
基于百度SDK的在线语音识别 基于百度的语音识别demo 最近在做一个关于语音识别的项目,所以借鉴了百度的语音识别的SDK和相关案例。目前用到的只是将语音转换成文字的部分,所以我就着这个方向总结一下。 首先要在百度语音开放平台申请注册,注意事项: 在百度语音开放平台注册应用地址: http://yuyin...
安卓BitmapFactory.decodeStream报java.lang.OutOfMemory... 一丶java.lang.OutOfMemoryError异常常见原因: 1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据; 2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收; 3.代码中存在死循环或循环产生过多重复的对象实体; 二丶BitmapFactory....
Building My own website Introduction So as far as I've seen most developers have there own website but for me, sadly I'm yet to do this so I've decided It's time to emba...
简书责编内容来自:简书 (源链) | 更多关于

阅读提示:酷辣虫无法对本内容的真实性提供任何保证,请自行验证并承担相关的风险与后果!
本站遵循[CC BY-NC-SA 4.0]。如您有版权、意见投诉等问题,请通过eMail联系我们处理。
酷辣虫 » Android的消息机制@松鼠笔记



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

使用声明 | 英豪名录