Android中消息机制分析

本文中的源码基于Android 29;

一、概述

对于Android开发者而言,我们处理异步消息用的最多的也是轻车熟路的一种方式,就是使用Handler进行消息的分发和处理。但是我们在一个页面(Activity 或者 Fragment)中可以直接使用Handler进行消息的分发和处理。实例如下:

private Handler mHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(@NonNull Message msg) {
            //处理消息
            return false;
        }
    });

我们一般在handleMessage()方法里进行消息的处理,同时在需要发送消息的地方使用mHandler.sendEmptyMessage(0)发送消息即可。callback回调是怎么收到消息的呢?同时Handler又是怎么发送消息的呢?我们带着问题看一下源码。

Android开发者们都知道:当APP启动时,会默认产生一个主线程(也就是UI线程),这个线程会关联一个消息队列,然后所有的操作都会被封装成消息后在主线程中处理。那么到底Android中消息机制是什么样子的呢?

对于Android程序而言,运行程序也是需要通过Java中的程序入口开始执行。而在ActivityThread中的就存在这么一个main方法,作为程序的入口。我们看一下源码:

public static void main(String[] args) {
       //省略部分代码…
        Process.setArgV0("<pre-initialized>");

	//创建主线程Looper
        Looper.prepareMainLooper();

	//省略部分代码…
	
        ActivityThread thread = new ActivityThread();
        thread.attach(false);

	//创建主线程对应Handler
        if (sMainThreadHandler == null) {//UI线程的Handler
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

	//开始消息轮询
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

在应用程序中,当执行ActivityThread中的main方法之后,程序就运行起来了。如上面main()方法中的代码所示,首先是通过 Looper.prepareMainLooper();创建主线程的Looper对象,然后通过sMainThreadHandler = thread.getHandler()创建了Handler,也就是ActivityThread中的类H,该类继承于Handler,我们在这里不在分析。最后通过Looper.loop()方法开始了消息的轮询。业务逻辑就这么简单。

二、消息轮询器:Looper

我们通过第一部分的代码发现对于Android消息机制而言,Looper扮演者非常重要的角色。那么我们就像了解一下Looper有哪些特性吧。

首先我们先了解一下Looper的成员变量吧,mQueue是消息队列(MessageQueue),主要用来存储消息。

mThread:是指当前的线程,严格的是与Looper关联的线程。sThreadLocal:主要用来获取或者设置Looper对象。主要就说这三个变量吧。

下面我们说一下它的构造器:

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

在构造器中,初始化了消息队列和当前线程的信息,仔细的同学可能注意到了,这里构造器的参数quitAllowed是什么意思呢?我们通过查看MessageQueue的源码我们可以发现,主要用于限制能否安全清除所有消息。若为主线程不允许使用quit方法。

接下来我们了解一下它的主要方法。在第一部分我们看到主线程中,首先使用了Looper.prepareMainLooper()方法创建了主线程的Looper对象。下面我们分析一下源码:

/**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

我们看到在这里使用了prepare(false)方法传入了false,我们看一下源码:

private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

通过以上代码我们通过sThreadLocal获取当前线程的Looper对象,若已经存在了则会抛出异常“Only one Looper may be created per thread”,也就是说一条线程对应一个Looper对象。若没有调用过该方法,则会通过sThreadLocal保存一个Looper对象。我们回过头来,prepareMainLooper()方法,通过设置quitAllowed = false,来创建一个不允许终止的Looper对象,也就是主线程是不允许调用quit方法的,这样我们在主线程创建的Handler就可以一直进行消息轮询了~。sMainLooper 就是主线程对应的Looper对象。

同时还有一个方法prepare(),该方法主要用于子线程创建Looper,使用Handler();同时建议在子线程中调用了prepare()方法之后需要调用loop()方法,最后以quit()方法结束本次轮询;

最后,我们看一下消息轮询的逻辑吧,也就是loop()方法。源码如下:

/**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
        //获取Looper对象
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        //获取looper对象对应的消息队列
        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;
            }

            //省略部分代码...

            //进行消息的分发处理
            try {
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if (observer != null) {
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            
            //省略部分代码...

            msg.recycleUnchecked();
        }
    }

该方法是Handler 消息机制的核心方法,首先该方法会获取当前线程对应的looper对象MessageQueue的对象,然后就会进入一个死循环。在该死循环中首先是通过消息队列获取下一条消息,也就是源码中的“queue.next()”方法,该方法的源码在这里就不在多余解释了,它的作用我们不看源码也能猜得出来,哈哈~,但是如果消息队列中没有消息,就会发生阻塞~

我们继续向下阅读,我们看到msg.target.dispatchMessage(msg);这就是消息分发的调用方法,我们知道msg就是Message的对象,那么target又是啥啊?哈哈,看过Message源码的同学一定都知道它里面有一个成员变量target,原来是Handler,现在一切都清楚了吧!我们的Handler就是这么收到消息的

三、纽带:Handler

我们分析完了Looper的源码,作为消息的轮询器Looper扮演着很重要的角色,也可以说是是整个消息机制的动力核心,是Looper驱动着消息的传递。而Handler扮演着纽带的角色,它承载着Looper ,连接着MessageQueue,进行消息的输入输出,我就叫它为纽带吧。

在第一部分我们通过使用Callback将消息回调回页面进行处理,我们先看一下Callback吧。

/**
     * Callback interface you can use when instantiating a Handler to avoid
     * having to implement your own subclass of Handler.
     */
    public interface Callback {
        /**
         * @param msg A {@link android.os.Message Message} object
         * @return True if no further handling is desired
         */
        boolean handleMessage(@NonNull Message msg);
    }

Callback就是Handler暴露给外部的数据接口,用于接收分发的消息,我们就可以在handleMessage()方法里进行处理了。在第二部分中我们分析Looper的loop()方法发现通过“msg.target.dispatchMessage(msg)”方法回调回Handler,我们看一下dispatchMessage()的源码

/**
     * Handle system messages here.
     */
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

在该方法里首先是判断msg.callback是否为空,我们通过源码知道callback实际上就是一个Runnable对象,若非空的话,就会执行handleCallback(msg)方法,该方法就是让msg.callback这个线程运行起来。若msg.callback为空的话,然后看Handler的成员变量mCallback是否为空不为空的话就会回调Callback的handleMessage()方法;若mCallback为空的话就会调用Handler的handleMessage()方法。现在清楚了吧,想要处理消息要么通过Callback回调,要么实现Handler并重写handleMessage()方法。这就是为啥Handler有一个没有方法体的handleMessage()方法。

 我们再看一下消息的添加的逻辑吧。在第一部分我们就是使用mHandler.sendEmptyMessage(0)将消息添加进消息队列的,这类的方法有以下几个:

这类方法都会在一定的时间的情况下,将消息推送在消息队列的底部。然后会在与该Handler关联的线程里的handleMessage方法下处理该消息。

还有种情况就是我们有时候会使用mHandler.post(r),这类方法一般是开了一条子线程去运行,这又是怎么回事呢?首先这类方法有以下几种:

我们看到该类型的方法其参数都包含Runnable参数,当我们调用这类方法时,所添加Runnable就会被添加进相应的消息队列中,该线程最终会在Handler的dispatchMessage()方法中执行,我们知道对于Message的callback就是一条线程,当callback非空时就会运行;

总体上上述方法:

  • post()和postDelayed()方法就会通过调用sendMessageDelayed()方法将线程添加进队列,同时在延迟一定时间运行;
  • 同时postAtTime()方法是调用sendMessageAtTime()方法将该线程添加进消息队列,然后在指定的时间运行;

我们继续查看源码发现post(),postDelayed()和postAtTime()方法最终都是通过sendMessageAtTime()方法将消息添加进消息队列。我们看一下源码:

 /**
     * Enqueue a message into the message queue after all pending messages
     * before the absolute time (in milliseconds) <var>uptimeMillis</var>.
     * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
     * Time spent in deep sleep will add an additional delay to execution.
     * You will receive it in {@link #handleMessage}, in the thread attached
     * to this handler.
     * 
     * @param uptimeMillis The absolute time at which the message should be
     *         delivered, using the
     *         {@link android.os.SystemClock#uptimeMillis} time-base.
     *         
     * @return Returns true if the message was successfully placed in to the 
     *         message queue.  Returns false on failure, usually because the
     *         looper processing the message queue is exiting.  Note that a
     *         result of true does not mean the message will be processed -- if
     *         the looper is quit before the delivery time of the message
     *         occurs then the message will be dropped.
     */
    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

该方法最终通过enqueueMessage()方法将消息添加进入消息队列,随后就会通过Looper将消息分发下去。

postAtFrontOfQueue()方法是通过sendMessageAtFrontQueue()方法将消息添加的消息队列,并保证下一个就会执行。然而最终也是通过enqueueMessage()方法完成的将消息添加进消息队列的操作。关于消息添加进入消息队列的逻辑在这里不在分析了,感兴趣的同学可以看一下MessageQueue的enqueueMessage()方法。

同时在我们的开发中多数情况下页面是会销毁的,这就需要销毁一些消息,有一个方法就能解决这个问题:

removeCallbacksAndMessages()该方法提供了一种解决方法,当Object为null是,消息队列中的消息会被标记回收。具体可以去MessageQueue的removeCallbacksAndMessages()方法中分析。

现在我们理解了为什么在子线程中需要这么使用了吧。

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

四、小问题

给大家留个小问题,为什么在Activity中就不需要创建Looper了?与君共勉~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

心灵行者

你的鼓励是我最大的创作动力~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值