Android Handler 梳理(二)

来源:互联网 发布:java调用ant脚本 参数 编辑:程序博客网 时间:2024/06/10 07:41

在Handler梳理里,我主要讲到了主线程在Looper的loop里循环读取消息,然后处理消息的过程,并没有说明这个消息是怎样发送的。这一篇我准备继续顺着线程执行的顺序来一步步看,怎样从发送消息到处理一个消息,以及消息究竟是个什么东西。

在接着上一篇思路往下走之前,先说一点关于进程和APP的东西。多进程的东西在此不涉及,默认我们的APP只有一个进程,当点击APP图标,需要启动一个APP时,系统为我们用孵化出一个新进程,用来执行这个APP的代码。这个新进程里面的有个线程,就是我们的主线程,同时这个主线程和其他线程的区别就是有一个Looper和MessageQueue。简单来说就是:点击APP图标后系统为我们做的事就是孵化出一个进程,进程里有一个主线程,主线程里面有个Looper,MessageQueue,然后让执行到Looper的loop循环里。这个时候主线程就在这个loop里面跑循环,每次去取消息来处理了。

然后我们再想一个问题,既然主线程都在跑圈圈了,我们怎么让主线程去显示界面呢,Activity在哪创建呢,又是如何执行到Activity的 onCreate() 方法的呢?其实这个问题的答案很好想到,只要给主线程的MessageQueue发消息,就可以了。而系统也正是这样做的, onCreate() 是由系统给主线程发消息到主线程的MessageQueue中,当loop循环取到这个消息的时候,处理消息也就是执行 onCreate() 肚子里的代码。从loop循环里面到 onCreate() 肚子里面代码的执行过程跟上篇讲的执行Handler的 handleMesaage() 方法的是一样的。

从上一篇到现在,基本上基于事件的线程的窗口机制的模型已经说清楚了。只剩下具体每一部分的细节了,比如具体怎样发消息,发到哪,取消息,从哪取,怎样取?针对这些细节,我们再根据源码,进行细致的说明。

先不急于看源码,我们先做一件事,就是自己猜,忘掉以前对Handler的认知。猜Google是怎样实现的。因为这样我们才能了解google设计这个东西的时候究竟是怎样想的,他的代码妙在什么地方,然后才对整个知识点有更深刻的认知。先不要觉得我在瞎几把扯,我真的觉得这样的理解才是顺其自然的,真正弄懂了这个东西,而不是死记硬背,含糊不清。

现在我们有四个类,Looper,Handler,Message,MessageQueue:
目前来说我们仅知道的内容就是主线程跑到Looper的 loop() 循环里面,Handler 发送消息到MessgeQueue,Looper的loop中要从MessageQueue拿出消息,然后最终在Handler的handleMessage里取处理消息。

基于上面的认知我们如果设计的话,至少要Handler在调sendMessge方法的时候应该是拿到了MessageQueue的引用,然后Looper取消息的时候肯定也要有MessageQueue的引用。message被取出来的时候调要找到handler并调用它的handleMessage方法,所以,message也应该持有Handler的引用。
有了这些认知以后再去看源码就相当简单了,甚至说自己都能设计出简化版。下面顺着发消息到MessageQueue,再从MessageQueue里面取消息去处理的过程撸一遍源码:

先从new一个Handler开始,看Handler的构造方法:

public Handler(Callback callback, boolean async) {    if (FIND_POTENTIAL_LEAKS) {        final Class<? extends Handler> klass = getClass();        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&                (klass.getModifiers() & Modifier.STATIC) == 0) {            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +                klass.getCanonicalName());        }    }    mLooper = Looper.myLooper();    if (mLooper == null) {        throw new RuntimeException(            "Can't create handler inside thread that has not called Looper.prepare()");    }    mQueue = mLooper.mQueue;    mCallback = callback;    mAsynchronous = async;}public Handler(Looper looper, Callback callback, boolean async) {    mLooper = looper;    mQueue = looper.mQueue;    mCallback = callback;    mAsynchronous = async;}

在这里我只罗列了两个构造方法,其他的构造方法最终还是要调到这两个构造方法(这种写法在Android源码里随处可见,包括我们发送sendMessage和postMessage,或者延时发送消息最终都是调的同一个发送消息的方法)。从这两个方法我们可以看出Handler发送消息对MessageQueue的引用时通过:

mQueue = looper.mQueue;

这就说明对MessageQueue的所有引用都在Looper中的,而整个线程的MessageQueue就是looper.mQueue。而Handler持有的是Looper的引用。无论是添加消息还是摘取消息都是拿的looper.mQueue来操作的。然后我们再去看sendMessage时添加一条消息到looper.mQueue:

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);}private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {    msg.target = this;    if (mAsynchronous) {        msg.setAsynchronous(true);    }    return queue.enqueueMessage(msg, uptimeMillis);}

sendMessage和postRunnable有好几种方法,比如 sendMessageDelayed(Message msg, long delayMillis)postDelayed(Runnable r, long delayMillis) 还有另外几个,他们最终调的都是 sendMessageAtTime(Message msg, long uptimeMillis) 其实并没有什么区别,都是在某个时刻发送一个message的方式来实现的,其中延时:

sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);

由上面的Handler的enqueueMessage方法可以看出,Handler其实只是算出在什么时候发送消息,并把它作为参数传给了messageQueue,但是并没有等待到那个时间去发送,而是立即交给了MessageQueue去处理。由此便知道MessageQueue里面要么在插入消息时有一个等待过程,要么在摘取消息时有一个等待过程,然后我们继续看源码:

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

上面的代码也比较简单:

因为MessageQueue插入操作可能来自不同线程,所以在执行插入操作时加锁,然后从这里可以看出消息队列实际上是通过链表实现的,mMessages是当前链表的头,明白了这些之后,整个代码主要就是根据message里面的时间(也就是我们在sendMessageAtTime传进来的那个时间)来判断当前入队列的这个message应该插入到链表的哪个位置,来保证整个链表是一个按时间先后排序的链表。

现在已经执行完了 enqueueMessage(Message msg, long when) 的代码,下一步应该是摘取消息来进行处理了,上篇说过,主线程在loop里绕圈,每次都去摘取一条消息进行处理,摘取消息的代码上一篇已经贴出,在loop里面:

Message msg = queue.next(); // might block

摘取消息的代码稍长,而且也有点不是很好理解,因为这里面关系到同步消息和异步消息处理机制不同的问题,需要做一些铺垫才能讲的明白。现在我们先按自己的理解继续往下走,消息队列是一个链表实现的,然后时间先后顺序已经排好了,按我们的理解应该就是每次loop的过程来取消息,拿系统当前时间和链表头部的第一条消息的时间进行对比,到第一条执行时间就让它去执行,时间不到就返回一个null。

当然这只是我们的猜想,系统的实现方式比这要复杂一点,但是也复杂不了太多,下面来看queue.next()要执行的代码;

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();        }        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) {                if (now < msg.when) {                    // Next message is not ready.  Set a timeout to wake up when it is ready.                    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 {                // 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)) {                pendingIdleHandlerCount = mIdleHandlers.size();            }            if (pendingIdleHandlerCount <= 0) {                // No idle handlers to run.  Loop and wait some more.                mBlocked = true;                continue;            }            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;    }}

和我们想的并不一样,这里面也有一个死循环来摘取下一条消息,而我们再看下loop里面取下一条消息的注释:

Message msg = queue.next(); // might block

有可能阻塞?对,其实loop时从MessgeQueue里摘取下一条消息的过程并不是直接返回的,而是有可能在MessageQueue.next()里面阻塞住的,我们还是看代码:nextPollTimeoutMillis这个变量就是通过messge上携带的时间减去当前时间。然后用到这个时间的只有一个地方:

nativePollOnce(ptr, nextPollTimeoutMillis);

这个方法实际上就是去调C++的代码,让当前线程等待管道阻塞一段时间,传进去的也就是阻塞时间。什么管道乱七八糟的不懂也没关系,就是让当前线程阻塞住这么长时间。至于为什么是阻塞而不是让线程空转,这里有很多考虑在里面,让我这样的小白不得感叹一句,Google的工程师真的6。在这里讲下我个人的一些理解:

  • 我们可以从JVM假想有个cpu存在,这不难理解吧,JVM本来就是虚拟机,然后主线程如果一直空转的话,不管cpu采用哪种调度策略,主线程都有可能在这一段时间得到cpu的使用权,而它执行的代码竟然是空转,这是没必要的,反正它这段时间没有消息,主线程就是通过loop摘取消息才执行的,离下一条要执行的消息既然还有时间,那就让线程在这段时间阻塞了也无妨。
  • 第二个就是为gc考虑了。Android最重要的就是用户体验,响应速度,而Java的gc垃圾回收刚好是和这个目标相悖,因为gc的时候要:stop the world,就是要所有的东西都停下来,gc完了你们再执行。如果一个正在打字和MM聊天的用户突然被gc卡了一下,会不会觉得很不爽(当然没那么明显,但是还是肯定要考虑gc的时间的)。而这个主线程阻塞的时间既不会影响用户交互,干嘛不用来gc呢,至于其他线程,只要不是UI线程,gc阻碍一会又有什么关系呢。所以这个时间也可以给gc来考虑要不要gc。

到此摘取消息也讲的差不多了,我们之前已经讲过了处理消息的过程,消息机制的基本功能已经讲完了。

TODO:

不过我还是有所保留的,对同步消息和异步消息这一块还没讲,然后对IdleHandler这一块也没有讲,还有就是Looper为什么要用ThreadLocal而不是用synchronized。因为要想讲明白这些,必须一层一层的往下走,先有一个基本的框架,然后再去看同步消息和异步消息其实也就是两个不同的消息类型而已,然后再取消息的过程有一点点区别,有一个时间分割栏的概念。主要还是为了让UI更流畅的显示的,平时开发基本用不着,在读View相关的源码的时候经常碰到,先有个这个印象。Looper用ThreadLocal也是基于让主线程在loop时不必因其他线程锁住Looper而等待(如果用synchronized,当其他线程用sendMessage时要拿到从Handler到Looper再到MessageQueue的引用,这是就要对Looper进行加锁。)
(本篇完,下篇待续)

0 0
原创粉丝点击