美文网首页Android开发经验谈Android开发Android技术知识
大厂Android高频面试题:Android中消息机制分析

大厂Android高频面试题:Android中消息机制分析

作者: 愿天堂没Android | 来源:发表于2022-04-02 17:16 被阅读0次

    一、概述

    对于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了?

    作者:心灵行者

    转载来源于:https://blog.csdn.net/zxm528/article/details/104742619

    如有侵权,请联系删除!

    相关文章

      网友评论

        本文标题:大厂Android高频面试题:Android中消息机制分析

        本文链接:https://www.haomeiwen.com/subject/zrotsrtx.html