美文网首页Android开发经验谈Android开发Android技术知识
源码分析Handler机制中的内存共享和巧妙的设计模式

源码分析Handler机制中的内存共享和巧妙的设计模式

作者: 程序员三千_ | 来源:发表于2020-03-25 12:56 被阅读0次

    这里我们就不去讲handler的基本使用了,我们会讲一些网上没提到过的,我们会从内存共享的角度去分析handler的源码。通过这篇文章相信大家能get很多干货。那么现在,我问大家一个问题。

    为什么Handler机制能实现各个线程间的内存共享呢?

    带这个这个问题,直接进入主题。handler相信所有Android的开发者都会用。我们都知道在Android线程通信中涉及到几个很重要的类:Handler、Message、MessageQueue、Looper、Thread其实还包括ThreadLocal和ActivityThread。下面我给出一个Handler的通信流程。


    Handler流程图

    结合上面的流程图,我们知道,handler是通过sendXXX和postXXX等一系列方法发送消息的,发送的消息会按时间顺序组装到MessageQueue里面,然后Looper会执行loop方法从MessageQueue里面不断的取消息,取出来通过message的msg.target.dispatchMessage(msg)方法,返回给message指定的handler(可能是一个线程中多个不同的handler,多个线程怎么保证消息的顺序,我们后续会讲到)。

    了解了handler的基本运作流程,我们再去具体分析下各个方法的调用者,我们都是知道handler是通过调用handler的sendXXX和postXXX的一些列方法发送信息的,但不管是它们其中的哪个方法,最后都会调用到MessageQueue的enqueueMessage方法去发送消息,将消息放到MessageQueue里,那么既然有发送消息,那么接收消息是怎么接收的呢?就是MessageQueue的next方法,他会去接收消息,从MessageQueue取出消息,那么next方法是谁去调用的呢?其实就是Looper.loop方法里去循环调用的。那么Looper.loop又是谁去调用的呢?其实每个非主线程都必须自己去调用Looper.loop的方法(而且在Looper.loop之前必须先调用looper的prepare方法去获取一个looper)。那么非主线程是自己去调用的,那么主线程里呢?是怎么去调用的呢?我们在使用主线程的handler发送消息的时候,我们并没有进行Looper新建这些一系列的操作啊?那到底怎么回事呢?其实在主线程在自己的mian方法里会去维护一个looper,我们Android的主线程就是ActivityThread这个类,所以我们从这个切入点,具体到源码的分析

        public static void main(String[] args) {
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
            SamplingProfilerIntegration.start();
    
            // CloseGuard defaults to true and can be quite spammy.  We
            // disable it here, but selectively enable it later (via
            // StrictMode) on debug builds, but using DropBox, not logs.
            CloseGuard.setEnabled(false);
    
            Environment.initForCurrentUser();
    
            // Set the reporter for event logging in libcore
            EventLogger.setReporter(new EventLoggingReporter());
    
            AndroidKeyStoreProvider.install();
    
            // Make sure TrustedCertificateStore looks in the right place for CA certificates
            final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
            TrustedCertificateStore.setDefaultUserDirectory(configDir);
    
            Process.setArgV0("<pre-initialized>");
    
            Looper.prepareMainLooper();
    
            ActivityThread thread = new ActivityThread();
            thread.attach(false);
    
            if (sMainThreadHandler == null) {
                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方法,我们注意到两个方法:Looper.prepareMainLooper()和 Looper.loop(),所以我们现在大致知道了,主线程其实是自己内部调用了Looper.prepare和Looper.loop。那么prepareMainLooper里面到底干了什么事情呢?我们进入到Looper的prepare方法里去看看,我们注意到一个关键方法:

        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));//把新创建的Looper和ThreadLocal关联起来,保证looper的唯一性
        }
    
    

    我们注意到在主线程在new一个Looper的时候把new的这个Looper通过set方法放到了ThreadLocal里,主线程在初始化Looper对象的时候,为什么要把Looper放进ThreadLocal里面么?我们都知道ThreadLocal是一个线程隔离工具类,其实这样做的目的,就是保证了多线程间的线程隔离。要具体了解ThreadLocal是怎么工作的。我们肯定得进入源码了解下ThreadLocal类,我们直接定位到ThreadLocal类的set方法里

    /**
         * Sets the value of this variable for the current thread. If set to
         * {@code null}, the value will be set to null and the underlying entry will
         * still be present.
         *
         * @param value the new value of the variable for the caller thread.
         */
        public void set(T value) {
            Thread currentThread = Thread.currentThread();//拿到当前的线程
            Values values = values(currentThread);//获取这个线程的Values对象
            if (values == null) {
                values = initializeValues(currentThread);
            }
            values.put(this, value);//把threadLocal当作key,Looper当作value存储起来
        }
    
    

    第一步它是拿到当前的线程currentThread,为什么要先拿到当前线程呢?其实每个线程的内部都会有一个成员变量ThreadLocal.Values localValues;


    image.png

    我们再继续set里源码的分析,然后通过currentThread获取到当前线程的Values对象,然后把当前的this也就是ThreadLocal和传进来的value也就是我们新建的looper对象put进values里,那么Values对象是什么呢?我们发现Values是ThreadLocal的内部类


    image.png

    ,除了一些int类型的成员变量,我们只发现了一个table数组(其实早些源码版本里是一个map数组,只是这个table数组实现了和map相同的功能),是数组我们会想到数组的增加和删除方法,所以我们再进入Values里面的add方法(其实put方法也是差不多的)看看,

     /**
             * Adds an entry during rehashing. Compared to put(), this method
             * doesn't have to clean up, check for existing entries, account for
             * tombstones, etc.
             */
            void add(ThreadLocal<?> key, Object value) {
                for (int index = key.hash & mask;; index = next(index)) {//根据key获取index下标
                    Object k = table[index];
                    if (k == null) {
                        table[index] = key.reference;//index未知存储key
                        table[index + 1] = value;//index+1位置存储value
                        return;
                    }
                }
            }
    

    我们发现是入参key和value的键值对,我们知道table是数组,那么为什么要用键值对map的这种方式存储呢?(上面说过了,早期版本确实是map来实现的)我们分析下这个add方法,首先他会根据key通过key.hash 获取index下标(.hash通过哈希值获取的,就是保证key的唯一性),然后下标是index的存储key,下标是index+1的存储value,所以我们综上分析,table的数组的确切数据存储方式是以threadLocal当作key,Looper当作value,并且以key1,value1,key2,value2,key3,value3..........】这种顺序存储起来的。现在,我们再回到Looper类的 prepare(boolean quitAllowed) 方法里

    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));//把新创建的Looper和ThreadLocal关联起来,保证looper的唯一性
        }
    
    

    我们从上面ThreadLocal的源码分析知道ThreadLocal是通过set的方式将sThreadLocal和looper绑定起来的。我们再看到prepare里有一个 if (sThreadLocal.get() != null) {这样的判断,它的意思是通过ThreadLocal去取对应的looper,如果已经存在了,就会抛出"Only one Looper may be created per thread"异常,所以它保证了每个ThreadLocal的values的table数组里存储的looper是唯一的,也就是说ThreadLocal和Looper是一一对应的,不会重复的,又因为每个Thread都会有一个ThreadLocal.Values localValues的成员变量,所以

    结论一:不管是主线程还是非主线程,每一个Thread对应了一个唯一的looper。(当然一个线程可以创建多个Handler)。

    分析完主线程里的prepareMainLooper方法,我们再进入主线程里的另外一个方法Looper.loop()里去看看,所以我们进入Looper类的looper方法去分析:

    public static void loop() {
            final Looper me = myLooper();//保证Looper的唯一性
            if (me == null) {
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
            }
            final MessageQueue v = me.mQueue;//保证MessageQueue的唯一性
    
            // Make sure the identity of this thread is that of the local process,
            // and keep track of what that identity token actually is.
            Binder.clearCallingIdentity();
            final long ident = Binder.clearCallingIdentity();
    
            for (;;) {
                //死循环里从MessageQuene里取消息,没有消息就会一直等待
                //如果没有要执行的消息,就会一直在等待,所以主线程永远不会退出,有消息就执行消息,没消息就一直等待
                //这就是主线程永远不需要创建handle,但可以一直执行,而且looper一直在工作的原因
                //主线程不是在工作,就是在等待
                Message msg = queue.next(); // might block
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
    
                // This must be in a local variable, in case a UI event sets the logger
                Printer logging = me.mLogging;
                if (logging != null) {
                    logging.println(">>>>> Dispatching to " + msg.target + " " +
                            msg.callback + ": " + msg.what);
                }
    
                msg.target.dispatchMessage(msg);//通过调用target(实际是handle实例)的dispatchMessage传回给handle
    
                if (logging != null) {
                    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                }
    
                // Make sure that during the course of dispatching the
                // identity of the thread wasn't corrupted.
                final long newIdent = Binder.clearCallingIdentity();
                if (ident != newIdent) {
                    Log.wtf(TAG, "Thread identity changed from 0x"
                            + Long.toHexString(ident) + " to 0x"
                            + Long.toHexString(newIdent) + " while dispatching to "
                            + msg.target.getClass().getName() + " "
                            + msg.callback + " what=" + msg.what);
                }
    
                msg.recycleUnchecked();//msg会去回收msg
            }
        }
    

    我们看到final Looper me = myLooper();这一句,一开始先是去拿到一个looper对象,我们进入myLooper()看看是怎么拿到的?

    
     /**
         * Return the Looper object associated with the current thread.  Returns
         * null if the calling thread is not associated with a Looper.
         */
        public static @Nullable Looper myLooper() {
            return sThreadLocal.get();//从ThreadLocal里拿到Looper对象
        }
    

    它就是从sThreadLocal里去拿到的,就是保证了looper对象的唯一性,只要你这个线程new了一次looper,以后不管你在这个线程的哪里再次用到looper,都会去sThreadLocal去拿。

    
     if (me == null) {
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
            }
    

    如果你的looper是空,会报异常,提示你"No Looper; Looper.prepare() wasn't called on this thread.",小伙伴么,这个异常我们在刚刚开始学习多线程通信的时候是不是常常遇到啊?我们想实现两个子线程的通信,是不是要在其中一个子线程里调用looper.loop方法啊?我们在调用looper.loop之前是不是要先调用Looper.prepare()?所以你看这里,如果你在looper.loop之前不调用Looper.prepare()是会报异常的,我们继续往下看,然后再通过looper对象,拿到MessageQueue对象,我们先去看看这个MessageQueue对象怎么拿到的?进入到looper的构造方法看看

     private Looper(boolean quitAllowed) {
            mQueue = new MessageQueue(quitAllowed);//保证了MessageQueue的唯一性
            mThread = Thread.currentThread();
        }
    

    MessageQueue就是在Looper的构造方法里创建的,因为looper我们知道在一个线程里是唯一的,所以这样创建MessageQueue的好处就是保证了MessageQueue的唯一性。我们接着看,我们发现取消息是在一个死循环里去取的?那么为什么是一个死循环呢?直接死循循环就是为了保持当前应用执行不退出。


    image.png

    我们看到ActivityThread执行到最后就会抛出异常,实际上因为looper.loop的死循环,主线程是不会执行到这一步的。我们接下去看,我们看到 死循环里有那么一句代码Message msg = queue.next(); // might block,就是说这句代码kennel会阻塞,具体怎么会阻塞呢?我们后面再去分析messagequeue的收发消息机制(enqueueMessage和next),这个问题就能解答了,现在先把loop的流程看完。我们直接看到msg.target.dispatchMessage(msg);这一句代码,这句代码的意思,就是会把接收到的消息,通过msg的target的dispatchMessage方法,将消息返回给上层的handler,其实这个target对象就是msg的一个成员变量,它是handler类型的,就是为了区分不同消息的来源。最后在看到loop方法里的最后一句代码 msg.recycleUnchecked();//msg会去回收msg,我们现在先想一个问题,在 handler流程里,有那么多msg对象,它是怎么管理的呢?是每次都去new一个msg对象么?我们进入Messenger类的recycleUnchecked方法

    /**
         * Recycles a Message that may be in-use.
         * Used internally by the MessageQueue and Looper when disposing of queued Messages.
         */
        void recycleUnchecked() {//
            // Mark the message as in use while it remains in the recycled object pool.
            // Clear out all other details.
            //享元模式(并不是销毁msg,而是把msg里的各种成员变量置为null,下次有新的消息的时候,直接复用
            //,而不是再去new msg,减少了内存碎片的出现,减少了内存抖动,提高了程序的性能
            flags = FLAG_IN_USE;
            what = 0;
            arg1 = 0;
            arg2 = 0;
            obj = null;
            replyTo = null;
            sendingUid = -1;
            when = 0;
            target = null;
            callback = null;
            data = null;
    
            synchronized (sPoolSync) {
                if (sPoolSize < MAX_POOL_SIZE) {
                    next = sPool;
                    sPool = this;
                    sPoolSize++;
                }
            }
        }
    

    它这里其实就是用到了一个java的设计模式:享元模式 。也就是每次并不是销毁msg,而是把msg里的各种成员变量置为null,下次有新的消息的时候,直接复用,而不是再去new新的msg,我们知道new的过程就是在jvm里开辟一块内存区域的过程,如果每次new一下,是不是会出现很多内存碎片啊?但有人会说,其实虚拟机会去gc回收啊?小伙伴们想,等你要到gc回收的时候其实已经是内存不够用的时候了,我们直接不让内存不够用不是更好么,更能提升app的性能么,所以,享元模式其实就是为了减少了内存碎片的出现,减少了内存抖动,提高了程序的性能。好了loop源码讲完了,为什么了解loop是怎么休眠的,怎么被唤醒的,Message msg = queue.next();这句代码为什么会被阻塞,我们现在直接进入messagequeue的next和enqueueMessage的源码分析

     Message next() {//消费者,取消息是从队伍头部开始取的
            // Return here if the message loop has already quit and been disposed.
            // This can happen if the application tries to restart a looper after quit
            // which is not supported.
            final long ptr = mPtr;
            if (ptr == 0) {
                return null;
            }
    
            int pendingIdleHandlerCount = -1; // -1 only during first iteration
            int nextPollTimeoutMillis = 0;
            for (;;) {
                if (nextPollTimeoutMillis != 0) {
                    Binder.flushPendingCommands();
                }
                //native函数没进入等待的方法nextPollTimeoutMillis=-1会一直等待,
                //nextPollTimeoutMillis>0会等待nextPollTimeoutMillis时间
                //nextPollTimeoutMillis = 0;不会等待
                nativePollOnce(ptr, nextPollTimeoutMillis);
    
                synchronized (this) {
                    // Try to retrieve the next message.  Return if found.
                    final long now = SystemClock.uptimeMillis();//获取当前时间
                    Message prevMsg = null;
                    Message msg = mMessages;
                    if (msg != null && msg.target == null) {
                        // Stalled by a barrier.  Find the next asynchronous message in the queue.
                        do {
                            prevMsg = msg;
                            msg = msg.next;
                        } while (msg != null && !msg.isAsynchronous());
                    }
                    if (msg != null) {
                        //如果当前的时间now还没达到消息执行的时间when,就开始等待
                        if (now < msg.when) {//msg.when是当前时间加上消息的延迟时间,在handle发送消息的时候会带过来(SystemClock.uptimeMillis() + delayMillis)
                            // Next message is not ready.  Set a timeout to wake up when it is ready.
                            //等待的时间就是msg.when - now,也就是再经过这些时间再执行这个消息
                            nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                        } else {
                            // Got a message.
                            mBlocked = false;
                            if (prevMsg != null) {
                                prevMsg.next = msg.next;
                            } else {
                                mMessages = msg.next;
                            }
                            msg.next = null;
                            if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                            msg.markInUse();
                            return msg;
                        }
                    } else {//msg==null没有消息的时候nextPollTimeoutMillis为-1
                        // No more messages.
                        nextPollTimeoutMillis = -1;
                    }
    
                    // Process the quit message now that all pending messages have been handled.
                    if (mQuitting) {
                        dispose();
                        return null;
                    }
    
                    // If first time idle, then get the number of idlers to run.
                    // Idle handles only run if the queue is empty or if the first message
                    // in the queue (possibly a barrier) is due to be handled in the future.
                    if (pendingIdleHandlerCount < 0
                            && (mMessages == null || now < mMessages.when)) {//消息为空走这个if
                        //将 mIdleHandlers.size()赋值给pendingIdleHandlerCount,因为大多数情况下 mIdleHandlers.size()是为0的,所以
                        //pendingIdleHandlerCount大多数情况下等于0,也就是大多数情况下会走下面pendingIdleHandlerCount <= 0这个if,
                        //所以主线程大多数是不走这个死循环的会直接continue,直接continue也就是extPollTimeoutMillis为-1
                        //extPollTimeoutMillis为-1传入native等待函数的时候native等待函数会一直等待。
                        pendingIdleHandlerCount = mIdleHandlers.size();
                    }
                    if (pendingIdleHandlerCount <= 0) {//消息不为空走这个if,
                        // No idle handlers to run.  Loop and wait some more.
                        mBlocked = true;//阻塞标志置为true
                        continue;//continue就会退出当前的循环继续走循环的下一个,所以循环末尾的 nextPollTimeoutMillis = 0;不会走,所以调用前面的native休眠的方法,进入等待
                    }
    
                    if (mPendingIdleHandlers == null) {
                        mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                    }
                    mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
                }
    
                // Run the idle handlers.
                // We only ever reach this code block during the first iteration.
                for (int i = 0; i < pendingIdleHandlerCount; i++) {
                    final IdleHandler idler = mPendingIdleHandlers[i];
                    mPendingIdleHandlers[i] = null; // release the reference to the handler
    
                    boolean keep = false;
                    try {
                        keep = idler.queueIdle();
                    } catch (Throwable t) {
                        Log.wtf(TAG, "IdleHandler threw exception", t);
                    }
    
                    if (!keep) {
                        synchronized (this) {
                            mIdleHandlers.remove(idler);
                        }
                    }
                }
    
                // Reset the idle handler count to 0 so we do not run them again.
                pendingIdleHandlerCount = 0;
    
                // While calling an idle handler, a new message could have been delivered
                // so go back and look again for a pending message without waiting.
                nextPollTimeoutMillis = 0;
            }
        }
    
        //生产者:消息是按时间排序的,越早进来的消息(加上消息延迟时间),越排在队伍的前面
        boolean enqueueMessage(Message msg, long when) {
            if (msg.target == null) {
                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;
                Message p = mMessages;
                boolean needWake;
                if (p == null || when == 0 || when < p.when) {//当前要执行的消息(队头消息)为空,说明消息已经被取走了
                    // New head, wake up the event queue if blocked.
                    msg.next = p;
                    mMessages = msg;//把新消息赋值给队头消息
                    needWake = mBlocked;//如果线程是阻塞的,就把needWake置为true,在下面唤醒线程
                } else {
                    // Inserted within the middle of the queue.  Usually we don't have to wake
                    // up the event queue unless there is a barrier at the head of the queue
                    // and the message is the earliest asynchronous message in the queue.
                    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;
                        }
                    }
                    msg.next = p; // invariant: p == prev.next
                    prev.next = msg;
                }
    
                // We can assume mPtr != 0 because mQuitting is false.
                if (needWake) {
                    nativeWake(mPtr);//native函数唤醒主线程,让主线程工作,不再等待
                }
            }
            return true;
        }
    

    我们看到,不管是next方法和enqueueMessage方法都用了synchronized修饰,其实就是为了保证线程安全。我们想在线程间通信时,消息的next和enqueueMessage是不是可能被多个线程调用啊?例如现在thread1调用主线程的handler发送消息,同时thread2也调用这个handler发送消息,那么怎么保证MessageQueue的顺序呢?怎么保证保消息收发的顺序呢?所以必须加上synchronized修饰。回过头来,那么整个MessageQueue是什么设计模式呢?其实enqueueMessage往MessageQueue里添加消息是一个生产者,next往MessageQueue里拿消息是一个消费者,MessageQueue就是一个仓库,而这个仓库就是由message构成的队列。


    image.png

    为了保证这个仓库里的所有的消息不变乱,当我往这个仓库里添加消息的时候,这个生产者可能是多个线程的,所以加入了synchronized修饰添加消息的方法enqueueMessage,同时从这个仓库取出消息的时候,为了保证一个消息只被一个线程取出,所以加入了synchronized修饰取出方法next。大方向讲完了,我们现在直接进入next方法的分析阻塞和唤醒机制。我们看到 nativePollOnce(ptr, nextPollTimeoutMillis);这个方法,这个就是线程挂起的native方法,我们发现里面有一个nextPollTimeoutMillis入参 ,其实这个方法的原理是这样的,如果nextPollTimeoutMillis=-1会一直等待;如果nextPollTimeoutMillis>0会等待nextPollTimeoutMillis时间;如果nextPollTimeoutMillis = 0;不会等待,我们再往下看,那是怎么实现线程挂起的呢?
    我们先看到 int pendingIdleHandlerCount = -1; ; int nextPollTimeoutMillis = 0;这两个变量一开始pendingIdleHandlerCount为-1;nextPollTimeoutMillis为0;我们看到if (msg != null) {也就是msg不为空,来消息了,它还有一个if (now < msg.when)这个判断。msg.when是什么呢?其实msg.when是当前时间加上消息的延迟时间,在handle发送消息的时候会带过来(SystemClock.uptimeMillis() + delayMillis),这个判断的意思就是如果当前的时间now还没达到消息执行的时间when,然后他把nextPollTimeoutMillis赋值为msg.when - now,也就是消息的延迟时间,else不用说,就是执行这个消息


    image.png
    我们在看到下面的这段代码,我们的消息不为空,并且还没到消息的执行时间时走下面 if (pendingIdleHandlerCount <= 0) {这个判断,它是把阻塞标志mBlocked置为true(mBlocked这个标志我们在enqueueMessage方法里会讲到,大家留意下),然后再continue,就会退出当前的循环继续走循环的下一个,所以循环末尾的 nextPollTimeoutMillis = 0;不会走,所以调用前面的native休眠的方法,进入线程挂起。我们再分析下上面这个判断, if (pendingIdleHandlerCount < 0&& (mMessages == null || now < mMessages.when)) {看这个意思就是mMessages为空的时候走这个判断,然后把 mIdleHandlers.size()赋值给pendingIdleHandlerCount;因为大多数情况下 mIdleHandlers.size()是为0的,所以pendingIdleHandlerCount大多数情况下等于0,也就是大多数情况下会走下面pendingIdleHandlerCount <= 0这个if,
    因为一开始判断我们知道msg为空的时候nextPollTimeoutMillis=-1

    所以continue跳出循环走到native等待函数的时候native等待函数会一直等待。
    所以经过上面的分析我们得出

    结论二:、没有消息的时候。2、消息还没达到发送的时间的时候,线程都会被挂起

    分析完next方法,我们再进入enqueueMessage方法看看

    
     if (p == null || when == 0 || when < p.when) {//当前要执行的消息(队头消息)为空,说明消息已经被取走了
                    // New head, wake up the event queue if blocked.
                    msg.next = p;
                    mMessages = msg;//把新消息赋值给队头消息
                    needWake = mBlocked;//如果线程是阻塞的,就把needWake置为true,在下面唤醒线程
                } else {
                    // Inserted within the middle of the queue.  Usually we don't have to wake
                    // up the event queue unless there is a barrier at the head of the queue
                    // and the message is the earliest asynchronous message in the queue.
                    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;
                        }
                    }
                    msg.next = p; // invariant: p == prev.next
                    prev.next = msg;
                }
    
                // We can assume mPtr != 0 because mQuitting is false.
                if (needWake) {
                    nativeWake(mPtr);//native函数唤醒主线程,让主线程工作,不再等待
                }
    

    我们直接看到 if (p == null || when == 0 || when < p.when) 这个判断的意思是:当前要执行的消息(队头消息)为空,说明消息已经被取走了,然后会把把新消息赋值给队头消息,然后把阻塞标志mBlocked的值赋给needWake,如果阻塞表示是ture,就会调用nativeWake唤醒线程,让线程不再等待。大家还记得么?在next取出方法的时候,如果没消息或者消息还没到达发送时间我们最后是把mBlocked置为true的,这里加入消息的方法enqueueMessage刚刚好利用了这个标志,如果当前线程的消息又来了,就把线程唤醒。这个配合就实现了整个handler的消息收发机制,也解答了loop里为什么Message msg = queue.next(); // might block这句话可能会阻塞的原因,这个实现机制是不是特别精辟。
    到此,整个handler消息的收发机制都讲完了,相信大家都能回答为什么handler机制能实现线程间的内存共享这个问题了。

    总结:其实归结到一点就是MessageQueue,这个MessageQueue里每一个节点message是不是一个独立的内存,不管你在主线程还是子线程里创建的message,都是一个独立的线程,它们之间的通信,就是在于共享MessageQueue这片空间,为了实现线程间的隔离,使用了ThreadLocal;为了让MessageQueue能够被多个线程同时调度不会冲突,使用了synchronized线程锁;为了使MessageQueue中的message能够被最大程度的复用,使用了享元设计模式;然后整个MessageQueue的收发消息又是使用了生产者消费者模式。

    最后我们讲一些亮点代码,也就是面试中经常会问到的问题。

    问题一:handler的dispatchMessage方法

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

    我们在使用handler的时候,是不是有以下几种方式1、会让msg自己去实现一个callback.2、我们会给handler传一个callback。3、直接继承handler,实现
    handleMessage方法。其实这也是面向对象的一个封装的思想,把处理消息的方法封装到了一个dispatchMessage方法里,其实事件的分发机制里,也使用了这种思想。

    问题二:Looper什么时候退出(其实handler内存泄露有很多要点要讲,其实原因就是该回收的变量的生命周期比当前的生命周期长,我这里就不展开了,网上有很多文章大家都可以去看,其实主要几点避免内存泄露的方法,就是1、使用静态内部类定义Handler 2、对Activity的引用使用弱引用3、在销毁方法里例如Activity的onDestroy及时去回收)。

    在子线程中Looper经常会内存泄露,就是因为Looper没有释放。所以我们需要释放Looper。Looper结束其实有两种方法,

    
    public void quit() {
        mQueue.quit(false); //消息移除
    }
    
    public void quitSafely() {
        mQueue.quit(true); //安全地消息移除
    }
    
    
    void quit(boolean safe) {
            // 主线程的MessageQueue.quit()行为会抛出异常,
            if (!mQuitAllowed) {
                throw new IllegalStateException("Main thread not allowed to quit.");
            }
            synchronized (this) {
                if (mQuitting) { //防止多次执行退出操作
                    return;
                }
                mQuitting = true;
                if (safe) {
                    removeAllFutureMessagesLocked(); //移除尚未触发的所有消息
                } else {
                    removeAllMessagesLocked(); //移除所有的消息
                }
                 // We can assume mPtr != 0 because mQuitting was previously false.
                nativeWake(mPtr);
            }
        }
    

    问题三:Thread、Handler、Looper、MessageQueue之间的对应关系。

    一个Thread可以创建多个Handler,但只能创建一个Looper,一个MessageQueue。Handler跟Looper之间没有对应关系;多个Thread可以同时使用某个线程中的Handler来发送消息。

    大家如果觉得get到干货了,可以关注点赞一下,后续文章我还会继续分享、解读很多面试里特别关注的android源码,感谢大家!

    相关文章

      网友评论

        本文标题:源码分析Handler机制中的内存共享和巧妙的设计模式

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