美文网首页
Android Handler消息传递机制深入解析

Android Handler消息传递机制深入解析

作者: 百度不清 | 来源:发表于2019-04-10 09:09 被阅读0次

    众所周知,Handler被广泛用于Android多线程之间的消息传递,我们带着几个问题对Handler消息机制进行分析:
    1、Handler是如何实现跨线程的消息传递?
    2、为什么在子线程中创建Handler之前,要先调用Looper.prepare()? 为什么主线程中不用?
    3、子线程中能不能创建多个Looper?为什么?
    4、为什么Handler创建消息一般用handler.obtainMessage(),而不是直接new Message()?

    首先我们来看看handler发送消息

    Handler handler = new Handler();
    handler.sendMessage(handler.obtainMessage(1, new Object()));
    

    使用handler包装一个消息,然后使用sendMessage发送消息。这里为什么要使用obtainMessage()包装生成Message实例呢?
    我们看到handler.obtainMessage(),定位到Message类中的obtain()方法。

      public static Message obtain() {
            synchronized (sPoolSync) {
                if (sPool != null) {
                    Message m = sPool;
                    sPool = m.next;
                    m.next = null;
                    m.flags = 0; // clear in-use flag
                    sPoolSize--;
                    return m;
                }
            }
            return new Message();
        }
    

    Message类中维护了一个消息回收池,如果消息回收池中没有消息对象,就会新建一个消息返回,如果消息回收池中有消息对象,则利用缓存对象。消息回收池中的消息是在消息被处理之后加入到回收池中,因此使用handler.obtainMessage()方法获取消息实例,能有效地利用被回收的消息对象,避免过多的重复创建,从而提高效率。
    继续看sendMessage方法,最终会调用下面的方法,意思是将消息加入到消息队列MessageQueue中。

     public boolean sendMessageAtTime(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);
        }
    

    那么,这个消息队列MessageQueue哪里来的呢?我们看到Handler的构造方法中,MessageQueue是由mLooper提供。
    mLooper 为空时,会抛出异常,这个异常很眼熟,在现场中创建Handler,如果没有调用Looper.prepare(),就会抛出该异常。

        public Handler(Callback callback, boolean async) {
           // 省略一部分代码
            mLooper = Looper.myLooper();
            if (mLooper == null) {
                throw new RuntimeException(
                    "Can't create handler inside thread " + Thread.currentThread()
                            + " that has not called Looper.prepare()");
            }
            mQueue = mLooper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
        }
    

    我们发现myLooper()就是获取Looper 对象,prepare()方法创建Looper对象,因此子线程中没有调用prepare()会报错。

      public static @Nullable Looper myLooper() {
            return sThreadLocal.get();
        }
    
        public static void prepare() {
            prepare(true);
        }
    
        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));
        }
    

    那为什么主线程中不调用该方法没有问题呢?我们看到主线程ActivityThread类中的main方法,原来主线程已经帮我们做了这件事,自然就不会报错了。

     public static void main(String[] args) {
            //省略代码
            Looper.prepareMainLooper();
            //省略代码
            ActivityThread thread = new ActivityThread();
            thread.attach(false, startSeq);
    
            if (sMainThreadHandler == null) {
                sMainThreadHandler = thread.getHandler();
            }
            //省略代码
            Looper.loop();
    
            throw new RuntimeException("Main thread loop unexpectedly exited");
        }
    

    我们再来看看Looper.prepare()方法,如果重复调用prepare()方法程序将会报错,为什么呢?试想如果一个线程产生多个Looper对象,将产生多个消息队列,这样存消息和取消息都将发生紊乱,因此一个线程可以有多个handler实例,但是只能有一个Looper实例和一个消息队列。
    发送消息是这样那是如何获取消息的呢?有意思的是我们在子线程中不写Looper.loop(),代码正常运行,但是handler无法收到消息,因此我们猜测,消息通知是通过Looper.loop()方法执行的。

    /**
         * Run the message queue in this thread. Be sure to call
         * {@link #quit()} to end the loop.
         */
        public static void loop() {
            final Looper me = myLooper();
            if (me == null) {
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
            }
            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);
                    dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
                } finally {
                }
                 // 省略代码
                msg.recycleUnchecked();
            }
        }
    

    通过源码分析,我们发现Looper.loop()建立了一个死循环,不断的从消息队列中获取消息进行派发,msg.target就是我们的handler,因此我们能通过handler.handleMessage(Message msg)方法获取到发送的消息对象。

    总结

    回想一下,发现线程间传递数据,实际上就是多线程之间共享数据对象,对象存在JVM堆上,堆是所有线程共享的数据区域。
    Handler为我们搭建了完善的数据缓存机制与消息传递机制,保障消息准确传递的同时,避免了过多的开销。

    其它文章

    Android面试宝典——JVM内存模型与数据结构

    Android面试宝典——网络通信

    Android面试宝典——经典试题

    相关文章

      网友评论

          本文标题:Android Handler消息传递机制深入解析

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