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;
            }
        }
    };


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;
        }
    }

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);
    }

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

差异在于使用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. 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;
    }

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. 首次进入或者所有的消息队列处理完毕之后,由于此刻队列空,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模组


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值