• Android解析异步消息机制——Android筑基


    Android中的异步消息处理主要是由4个部分组成,Message、handler、MessageQueue和Looper。其中Message和Handler将会后续出文章详细介绍。本文重点介绍这四个部分。


    1、Message

    Message是线程之间传递的消息,它可以在内部携带少量的信息,用于在不同的线程之间交换数据信息,可以利用Message的what字段另外也可以使用arg1和arg2来携带一些整形数据,使用obj字段携带一个Object对象。

    给出参考代码:

    	@SuppressLint("HandlerLeak")
        private Handler handler = new Handler() {
            @SuppressLint("SetTextI18n")
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case UPDATE_TEXT:
                        text.setText("Nice to meet you mac");
                        break;
    
                    case UPDATE_TEXT2:
                        text.setText("Hello mac Wust!");
                        break;
    
                    default:
                        break;
                }
            }
        };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19


    2、Handler

    顾名思义,Handler就是处理器的意思。主要用来发送和处理消息,发送消息使用Handler的sendMessage方法,发出的消息经过一系列处理之后,最终传递给Handler的handleMessage方法。

    给出参考代码:

    	@SuppressLint("HandlerLeak")
        private Handler handler = new Handler() {
            @SuppressLint("SetTextI18n")
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case UPDATE_TEXT:
                        text.setText("Nice to meet you mac");
                        break;
    
                    case UPDATE_TEXT2:
                        text.setText("Hello mac Wust!");
                        break;
    
                    default:
                        break;
                }
            }
        };
    
        @SuppressLint("NonConstantResourceId")
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.change_text:
                    new Thread(() -> {
                        Message message = new Message();
                        message.what = UPDATE_TEXT;
                        handler.sendMessage(message);
                    }).start();
                    break;
    
                case R.id.change_text2:
                    new Thread(() -> {
                        Message message = new Message();
                        message.what = UPDATE_TEXT2;
                        handler.sendMessage(message);
                    }).start();
                    break;
    
                default:
                    break;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    3、MessageQueue

    MessageQueue就是消息队列的意思。用于存放所有的通过Handler发送的消息,这部分的消息会一直存在于消息队列中,等待被处理。每一个线程中只有MessageQueue对象。

    • Handler.sendMessageDelayed()的源码如下:
    	public final boolean sendMessageDelayed(Message msg, long delayMillis)
        {
            if (delayMillis < 0) {
                delayMillis = 0;
            }
            return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    根据上述代码,可以得到消息被处理的时间 = 当前时间+延迟的时间

    差异在于使用SystemClock.uptimeMillis(),而不用SystemClock. currentTimeMillis()。

    • System.currentTimeMillis() 方法产生一个标准的自1970年1月1号0时0分0秒所差的毫秒数。该时间可以通过调用setCurrentTimeMillis(long)方法来手动设置,也可以通过网络来自动获取。这个方法得到的毫秒数为“1970年1月1号0时0分0秒 到 当前手机系统的时间”的差。因此如果在执行时间间隔的值期间用户更改了手机系统的时间,那么得到的结果是不可预料的。因此它不适合用在需要时间间隔的地方,如Thread.sleep, Object.wait等,因为它的值可能会被改变。
    • SystemClock.uptimeMillis() 方法用来计算自开机启动到目前的毫秒数。如果系统进入了深度睡眠状态(CPU停止运行、显示器息屏、等待外部输入设备)该时钟会停止计时,但是该方法并不会受时钟刻度、时钟闲置时间亦或其它节能机制的影响。因此SystemClock.uptimeMillis()方法也成为了计算间隔的基本依据,比如Thread.sleep()、Object.wait()、System.nanoTime()以及Handler都是用SystemClock.uptimeMillis()方法。这个时钟是保证单调性,适用于计算不跨越设备的时间间隔。

    Handler.sendMessageDelayed()方法最终会调用enqueueMessage方法进入MessageQueue的enqueueMessage方法中,源码如下:

    	private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
            msg.target = this;
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            return queue.enqueueMessage(msg, uptimeMillis);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    其中最为重要的就是两个方法:

    1. enqueueMessage向队列中插入消息
    2. next 从队列中取出消息

    给出enqueueMessage源码:

    	boolean enqueueMessage(Message msg, long when) {
            if (msg.target == null) {
            	//msg.target就是发送此消息的Handler
                throw new IllegalArgumentException("Message must have a target.");
            }
            if (msg.isInUse()) {
            	//表示此消息正在被使用
                throw new IllegalStateException(msg + " This message is already in use.");
            }
    
            synchronized (this) {
                if (mQuitting) {
                	//表示此消息队列已经被放弃了
                    IllegalStateException e = new IllegalStateException(
                            msg.target + " sending message to a Handler on a dead thread");
                    Log.w(TAG, e.getMessage(), e);
                    msg.recycle();
                    return false;
                }
    
                msg.markInUse();
                msg.when = when;//将延迟时间封装到msg内部
                Message p = mMessages;//消息队列的第一个元素
                boolean needWake;
                if (p == null || when == 0 || when < p.when) {
                   //如果此队列中头部元素是null(空的队列,一般是第一次),或者此消息不是延时的消息,则此消息需要被立即处理,此时会将这个消息作为新的头部元素,并将此消息的next指向旧的头部元素,然后判断如果Looper获取消息的线程如果是阻塞状态则唤醒它,让它立刻去拿消息处理
                    msg.next = p;
                    mMessages = msg;
                    needWake = mBlocked;
                } else {
                    //如果此消息是延时的消息,则将其添加到队列中,原理就是链表的添加新元素,按照when,也就是延迟的时间来插入的,延迟的时间越长,越靠后,这样就得到一条有序的延时消息链表,取出消息的时候,延迟时间越小的,就被先获取了。插入延时消息不需要唤醒Looper线程
                    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;
                        }
                    }
                    // invariant: p == prev.next
                    msg.next = p; 
                    prev.next = msg;
                }
                // We can assume mPtr != 0 because mQuitting is false.
                if (needWake) {
                	//唤醒线程
                    nativeWake(mPtr);
                }
            }
            return true;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    MessageQueue中enqueueMessage方法的目的有两个:

    1. 插入消息到消息队列中
    2. 唤醒Looper中等待的线程,若是及时消息并且线程是阻塞态。

    下面给出next方法源码:

    	Message next() {
            final long ptr = mPtr;
            if (ptr == 0) {
               //从注释可以看出,只有looper被放弃的时候(调用了quit方法)才返回null,mPtr是MessageQueue的一个long型成员变量,关联的是一个在C++层的MessageQueue,阻塞操作就是通过底层的这个MessageQueue来操作的;当队列被放弃的时候其变为0。
                return null;
            }
    		// -1 only during first iteration
            int pendingIdleHandlerCount = -1; 
            int nextPollTimeoutMillis = 0;
            for (;;) {
                if (nextPollTimeoutMillis != 0) {
                    Binder.flushPendingCommands();
                }
    
                //阻塞方法,主要是通过native层的epoll监听文件描述符的写入事件来实现的。
               //如果nextPollTimeoutMillis=-1,一直阻塞不会超时。
               //如果nextPollTimeoutMillis=0,不会阻塞,立即返回。
               //如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时),如果期间有程序唤醒会立即返回。
                nativePollOnce(ptr, nextPollTimeoutMillis);
    
                synchronized (this) {
               
                    final long now = SystemClock.uptimeMillis();
                    Message prevMsg = null;
                    Message msg = mMessages;
                    if (msg != null && msg.target == null) {
                        //msg.target == null表示此消息为消息屏障(通过postSyncBarrier方法发送来的)
                        //如果发现了一个消息屏障,会循环找出第一个异步消息(如果有异步消息的话),所有同步消息都将忽略(平常发送的一般都是同步消息)
                        do {
                            prevMsg = msg;
                            msg = msg.next;
                        } while (msg != null && !msg.isAsynchronous());
                    }
                    if (msg != null) {
                        if (now < msg.when) {
                            // 如果消息此刻还没有到时间,设置一下阻塞时间nextPollTimeoutMillis,进入下次循环的时候会调用nativePollOnce(ptr, nextPollTimeoutMillis)进行阻塞;
                            nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                        } else {
                            //正常取出消息
                            //设置mBlocked = false代表目前没有阻塞
                            mBlocked = false;
                            if (prevMsg != null) {
                                prevMsg.next = msg.next;
                            } else {
                                mMessages = msg.next;
                            }
                            msg.next = null;
                            msg.markInUse();
                            return msg;
                        }
                    } else {
                        //没有消息,会一直阻塞,直到被唤醒
                        nextPollTimeoutMillis = -1;
                    }
    
                    if (mQuitting) {
                        dispose();
                        return null;
                    }
    
                pendingIdleHandlerCount = 0;
                nextPollTimeoutMillis = 0;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    根据源码可以得知,

    1. 首次进入或者所有的消息队列处理完毕之后,由于此刻队列空,mMessage为null,这时候nextPollTimeoutMillis = -1,然后开始处理一些不紧急的任务,例如idleHandler,之后线程一直阻塞等待,一直到被主动唤醒。
    2. 读取消息列表的消息,若发现了**消息屏障**,则跳过后面的同步消息。
    3. 若拿到的消息没有到时间,就重新赋值nextPollTimeoutMillis = 延时的时间,线程阻塞等待,一直到规定时间被唤醒。
    4. 若消息是及时消息或者延时消息到时了,就把这个消息返回给Looper处理。

    消息的入列和出列是一个生产-消费者模式,Looper.loop()在一个线程中调用next()不断的取出消息,另外一个线程则通过enqueueMessage向队列中插入消息,所以在这两个方法中使用了synchronized (this) {} 同步机制,其中this为MessageQueue对象,不管在哪个线程,这个对象都是同一个,因为Handler中的mQueue指向的是Looper中的mQueue,这样防止了多个线程对同一个队列的同时操作。


    4、Looper

    Looper类分别用来对一个线程开启一个消息循环。
    默认情况下Android中新诞生的线程是没有开启消息循环的,主线程除外,主线程系统会自动给她创建looper,并且开启消息循环。

    Looper对象通过MessageQueue消息存放消息和事件,一个线程只有一个Looper,对应一个MessageQueue。

    在这里插入图片描述

    Handler可以看作Looper的一个接口,用来指定向Looper发送消息以及定义处理的方法。

    在这里插入图片描述

    实例代码

    具体请见gitee地址:请见master分支的AndroidThreadTest模组


  • 相关阅读:
    aasist-bladedisc 音频反欺骗算法模型
    [NOIP2010 普及组] 三国游戏
    J. 金色传说【10.14训练补题】
    多线程(【多线程案例】单例模式+阻塞式队列+定时器+线程池)
    Vue 2的学习
    学习大数据DAY33 Flask 库 API 开发介绍,OS 库,pandas 库和简单爬虫
    l8-d9 UDP通信实现
    js - 原生js中offset和client以及scroll之间的区别
    使用 Apache DolphinScheduler 构建和部署大数据平台,将任务提交至 AWS 的实践经验
    面试官:Redis中字符串的内部实现方式是什么?
  • 原文地址:https://blog.csdn.net/qq_42544728/article/details/126444565