大话Android中的Handler机制

在Android的线程间通信中,Handler独当一面,无论是framework层还是app层中都出现的相当频繁,有必要好好的拿出来深挖一下它的实现原理。而要说Handler的通信机制,除了Handler外,另外几个重要的类是不得不提的,他们就是Looper、MessageQueue、Message。在仔细研读这几个类的代码后,我试着用一件身边的事情来阐述他们之间的关系。

好的,下面开始我们的角色扮演游戏。Handler饰演寄信的小白,Looper饰演店员B,MessageQueue饰演店员A,Message饰演信。

小白同学逛某宝的时候看到一个店铺,店铺slogen大概是这样——“写封信给N年后的自己”,小白看到这个创意被深深吸引,果断下单,的标题是写给10年后的自己,内容非常简洁,大致是这样的:当你看到这封信的时候说明咱家还没拆迁,别死等了,好好工作吧(请忽略信内容 -_-)。店铺接单后,店员A不久便制作出了小白的信件然后按照小白要求的10年延迟时间将信件按延迟时间从小到大插入到店里存信件的书架中,同时店员A还需要负责从书架取信(你丫放的信只有你知道怎么取)。而店里还有店员B,他每天的工作内容是问店员A拿第一封信,如果时间已到就把它拿走寄出去。至此,故事剧情结束。


我们把上面的剧情再按程序的逻辑走一遍,看是否有瑕疵。Handler是Message的发起者,它把Message告诉MessageQueue,MessageQueue按执行时间when的大小,将Message插入单向链表中(下单),Looper不断从MessageQueue里取出Message检测执行时间when并执行(取信、寄出)。消息的入口和出口都拎出来了,但是还是有瑕疵:

问题1、Handler是如何与Looper、MessageQueue建立关联的?
当创建Handler对象会传入一个Looper对象或者通过静态方法Looper.myLooper()拿到当前线程的Looper对象及Looper对象中的MessageQueue对象。

问题2、既然是多线程通信,那当Handler在sendMessage的时候,如何知道自己的出生地是哪个线程,换句话说将Message给到哪个线程的MessageQueue?
在问题1中我们已经明确了Handler大部分情况下是通过Looper.myLooper()获得当前线程的Looper对象,拿到了Looper对象自然也就拿到了对应的MessageQueue对象,但是现在问题来了,Looper.myLooper()作为一个静态方法是如何做到返回调用该方法的线程的Looper对象的呢?嘿嘿,下面ThreadLocal<T>隆重登场!Looper类中有一神奇的静态常量ThreadLocal<Looper> sThreadLocal,它能对访问做到线程隔离,即在线程A中get()只能拿到在线程A中set进去的值(关于ThreadLocal<T>这里只讲这么多)。正是基于ThreadLocal这种神奇的实现,使得我们可以通过Looper.myLooper()获得当前线程的Looper对象。贴代码

public final class Looper {
    ...
    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));
    }
    ...  
    /**
     * 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();
    }
    ...
}

可以看到,myLooper()返回不为空的前置条件是Looper.prepare()被调用,所以如果要在子线程new一个Handler,切记先在子线程中调用prepare()并且一个线程只能调用一次哦!什么?你问为什么在主线程中不需要调prepare()?主线程当然也要调,只不过主线程在进程启动时系统就帮我们把Looper prepare好了(对启动过程感兴趣的可以去看看android.app.ActivityThread::main(String[])),千万不要再次调用了。

问题3、多个线程都在sendMessage,MessageQueue在插入消息时如何做到线程同步?
这个不太好吹,就是加了同步锁锁住了当前MessageQueue对象呗,详见下方代码

public final class 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;
    }
    ....
}

问题4、Looper对象以死循环的方式取Message,会不会导致线程阻塞,主线程这样操作不会ANR吗?当队列中没有Message时,是否仍会造成CPU资源消耗?
既然死循环,那当然会阻塞,而且要的就是阻塞,是不是很惊喜~原因在于main函数作为入口,main函数执行完毕进程就结束了,那为了保持我们的app持续的运行,当然要想办法不让main执行完毕,而loop()方法正好达到了这个目的。至于是否ANR,我们要知道Android的所有生命周期都是通过Handler机制处理的,如果Handler中的某个处理消息的方法耗时过长,就会导致其它消息响应不及时,如果这些消息里有用户的操作类消息或者别的ANR敏感的消息,就可能出现ANR。
关于死循环是否会造成无意义的资源消耗,这个是不会的,这里用到了linux的管道和epoll模型,在取消息的时候通过nativePollOnce(ptr, nextPollTimeoutMillis);阻塞循环,等待nextPollTimeoutMillis后继续取消息(有Message但是when未到达)或阻塞直到管道中有新消息写入(nextPollTimeoutMillis=-1),而在入队的时候检测并唤醒epoll。


了解了Handler机制后,在使用Handler的时候要注意以下问题:
1、初始化Handler前务必调用Looper.prepare(); 主线程中不要画蛇添足去操作Looper;
2、sendMessage前确保Looper.loop()已被调用;
3、利用Message.obtain()的复用机制,避免创建对象的资源消耗。

猜你喜欢

转载自www.cnblogs.com/duanzi6/p/9035371.html