那些Handler该知道的事

引言

源码版本:Android10

Handler的作用

做过Android的人都知道,Handler一般用于线程间通信,无论是在Android的平常开发中,还是Android的官方源码中,都有大量使用Handler的身影。所以,Handler的具体实现原理,是作为Android开发所必不可少的一个知识,必须要掌握。

构建一个Handler

public Handler(@Nullable 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 " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

复制代码

上面是Handler的构造方法,我们可以看到,上面出现了mLoop、mQueen、mCallBack、mAsynchronous,那这些变量都是什么呢?

  • mLooper Looper,用于为线程运行消息循环的类
  • mQueen MessageQueen,包含了要发送消息的列表
  • mCallBack Handler$Callback,实例化Handler时可以使用的回调接口,以避免必须实现一个自己的Handler子类。
  • mAsynchronous boolean值标记发送的是同步还是异步的消息,我们需要探讨同步消息和异步消息的区别以及实现。

这样,就引出了整个消息机制所涉及到的类,Looper、MessageQueen、Handler,另外,还有一个Message类,即上面一直提到的消息。

Looper

Handler的构造方法中,通过Loop.myLoop()方法,获得了一个Looper对象。我们来看看这个Loop对象是如何获得的,跟踪源码。

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}
复制代码

可以看到Looper对象是调用sThreadLocal.get()方法获得的。
一般情况下,我们定义了一个全局变量,无论在子线程和主线程是都可以访问,所以可以说是线程之间共享的。Android中有一个类名为ThreadLocal
,是线程内部的存储类,保存的是当前线程私有的数据(详情见ThreadLocal实现原理)。
上面出现的sThreadLocal其实就是ThreadLocal对象。既然是通过get()方法获得的,那我们需要看一下,是那个地方进行了sThreadLocal的set()调用

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.prepare()时,就可以给Looper内部的sThreadLocal设置一个Looper对象。

Looper是线程运行消息循环的类,其实在Android应用在启动时,整个消息机制就已经建立了,在我们应用初始化的时候,会调用到ActivityThread类的main(),即一个入口函数,在这个main方法中,就会调用Looper.prepareMainLooper(),内部其实还是调用prepare()。这样主线程的Looper对象就创建好了。

Looper.loop()

重要的Looper.loop()方法,真正消息机制开始启动,就必须调用Looper.loop(),loop()方法把消息通信的基本框架完全展示出来了。
下面我们来看一下它的实现

public static void loop() { 
    final Looper me = myLooper();
    ...
    final MessageQueue queue = me.mQueue;
    ...
    for (;;) {
        Message msg = queue.next();
        ...
        msg.target.dispatchMessage(msg)
    }
}
复制代码

主线程中,myLooper()返回的Looper对象,就是我们在应用启动时,初始化好的Looper对象,该Looper对象里,持有了一个MessageQueen,通过循环调用queue.next(),获取消息msg,最终调用msg.target.dispatchMessage(msg)处理消息。
quene是MessageQueen对象,它包含了要发送的消息列表,每次调用next,其实是取出对应时间需要执行的Message对象。现在我们可以想象,MessageQueue 中有一大堆需要处理的消息对象,我们在循环中,每次调用queue.next(),都会给我们当前需要执行的消息,而真正处理消息的是msg.target,msg.traget其实就是我们创建的handler对象,我们到时候会在Handler的handlerMessage(Message)中收到消息,然后做出对应的处理。

其实消息机制整个流程就是这样,但是还有很多细节,我们并不知道。所以可以先在这里提出几个问题,以便我们能更好的分析代码。

  1. 消息是如何发送的,并且如何正确的保存到MessageQueen当中的呢?
  2. 消息是如何取出的,Looper.loop()方法,是一个死循环,为什么不会造成ANR?
  3. 系统也是通过Handler来进行进程内部通信的,那我们使用Handler会不会影响系统的任务执行呢?

消息的发送

无论是通过Handler.post(runnable),Handler.postDelay(runnable,delayMillis)还是Handler.sendMessage(msg).最终都会调用一个方法Handler的sendMessageAtTime(msg,uptimeMillis)。

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);
}
复制代码

这个方法就是存消息的,当你要执行一个Runnable的时候,内部会帮你构建一个Message(Handler.getPostMessage(runnable)),只是会把Message的Callback使用我们的Runnble进行赋值。如果直接是一个Message,就不需要包装一下。uptimeMillis其实是任务执行的时间,即何时执行这个任务或者处理这个消息,updateMillis会等于SystemClock.uptimeMillis() + delayTime。一般即时消息,delayTime是0,updateMillis会等于SystemClock.uptimeMillis()
SystemClock.uptimeMillis()是什么时间呢?其实这个时间呢,就是你开机了多久时间,单位是毫秒。其实是一个相对时间。

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
复制代码

可以看到,消息的target设置成了this,即Handler对象本身。剩下的就是通过quene对象进行添加消息。
msg.setAsynchronous(true)这个代码的作用我们稍后再讲,一般我们使用的时候,mAsynchronous是false,所以也进不了这个判断里面。

往MessageQueen中添加消息

下面是MessageQueen的enqueqeMessaege()方法,看下这个方法

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) {
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            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;
            prev.next = msg;
        }
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}
复制代码

现在我们主要分析这个方法。
前面是两个判断,一个是判断msg.target,我们设置的消息,msg.target是当前的Handler对象,所以肯定不为空。并且msg.isInUse()也是false,消息只有被添加到队列中,才会标记为正在使用。msg.when = when,设置消息执行的时间。Message其实是一个单链表结构,mMessages是链表的表头。Message链表中的元素是通过任务执行的时间进行排序的,从小到大进行排序。如果链表不为空的话,那表头的那个Message其实就是下一个需要处理的Message。

所以当mMessages=null、when=0(Message的when是有一个基础值的,使用的是系统开机时间,这个值一般不等于0)、when<p.when(新添加进来的消息比表头的Message执行时间还要更近),出现上述情况,我们都需要更新链表的表头。

并且我们还需要更新一下needWake的值,因为当消息队列为空时,取消息的方法是阻塞的(阻塞的原因也可能是消息未到执行时间),需要在存消息的时候唤醒一下。这也是为什么Android的进程内通信是事件驱动机制,这个我们也稍后在分析MessageQueen.next()的时候再说。

如果不用给链表一个新的head,就通过代码中的for循环,根据已有的Message的when,把我们刚新增的Message的添加进去。进而又是一个有序的Message链表。

往MessageQueen中取出消息

终于看到取Message的方法了

Message next() {
    ...//省略
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) { 
        ...//省略
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    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 {
                nextPollTimeoutMillis = -1;
            } 
            ...//省略 处理IdlerHandler的逻辑
        pendingIdleHandlerCount = 0;
        nextPollTimeoutMillis = 0;
    }
}
复制代码
阻塞的实现

这里面的next方法调用时机上面已经讲过了,就是Loop.loop()方法中,通过一个死循环进行调用。next()方法中,nativePollOnce(ptr, nextPollTimeoutMillis)方法被首先调用,这是一个native方法,它实际的作用就是用来阻塞的。

为什么要把方法阻塞?

主线程的Loop.loop()方法中,存在了一个死循环,如果MessageQueen的next()不是阻塞的,直接会造成程序的ANR。实际上,主线程不会直接运行结束退出,也是因为这个死循环。

其实我们追踪这个native方法,在android_os_MessageQueue.cpp文件中,可以通过调用关系发现android_os_MessageQueue_nativePollOnce()->pollOnce()->pollInner(),在pollInner()中,有一个epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis)方法,通过这个方法来实现阻塞的。

epoll_wait()是通过epoll监听文件描述符的写入事件来实现的。

  • nextPollTimeoutMillis = -1时,会一直阻塞
  • nextPollTimeoutMillis = 0时,不会阻塞,会立即返回
  • nextPollTimeoutMillis > 0 时,阻塞nextPollTimeoutMillis毫秒(这是一个阻塞的超时时长),如果这期间被唤醒了,也会立即返回。

与之对应的是nativeWake方法

追踪这个方法,也可以根据调用关系发现android_os_MessageQueue_nativeWake()->wake()->write(mWakeEventFd, &inc, sizeof(uint64_t)。通过epoll监听的文件写入指定值,这时候epoll监听到事件,进而被唤醒了。

异步消息以及Barrier

看完阻塞之后,我们查看第一个if判断,判断msg!=null,并且msg.target==null,我们正常添加的消息msg.target是Handler对象,不会等于null。那什么情况下,msg.target会等于null。
其实当我们添加同步屏障的时候,就会添加一个msg.target=null的消息。

private int postSyncBarrier(long when) {
    synchronized (this) {
        final int token = mNextBarrierToken++;
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;
        Message prev = null;
        Message p = mMessages;
        if (when != 0) {
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
        if (prev != null) { // invariant: p == prev.next
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        return token;
    }
}
复制代码

同步屏障的作用是什么呢?
同步屏障其实是在消息队列中设置一个屏障,当链表的表头是一个同步屏障消息,那么会遍历整个消息队列,不理会同步消息,把最近的异步消息取出来,进而让这个异步消息能够更优先执行。

异步消息和同步消息的区别?
还记得我们在Handler.enqueueMessage()方法中,添加消息时,有一个判断

if (mAsynchronous) {
    msg.setAsynchronous(true);
}
复制代码

当mAsynchronous的值为true时,这个Handler发送的消息,都是异步消息。默认的Handler空参构造,mAsynchronous的默认值为false,所以一般情况下,我们所发送的消息,都是同步消息。
另外,如果当检测到屏障时,如果找不到异步消息,那next方法会一直阻塞。除非移除屏障消息。
在Android源码中,在ViewRootImpl中scheduleTraversals()方法中,就存在屏障消息的应用。scheduleTraversals()是遍历view树,进行视图测量、布局、绘制等操作的。系统为了尽可能保证其优先得到执行,发送的是异步消息。触发mTraversalRunnable的执行。

同步消息

如果不是同步屏障,那就取msg。判断msg的执行时间是否到了,没到的话,就设置阻塞超时,阻塞的时长=消息的处理时间 – System.uptimeMillis()。
到了的话,就取出msg(链表头),并且重新设置链表头。

消息的处理

消息的处理比较简单

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是不是空的,如果通过Handler发送的是一个Runnable,那msg.callback不为空,会调用runnable.run()方法;接着判断mCallback是否为空,mCallback可以通过构造方法传递进来,一般如果调用的是空参构造,mCallback也是空的。如果上述判断都不成立,会调用Handler内部的
handleMessage(msg)方法,我们构建Handler的时候,可以构建一个Handler的子类重写handleMessage(msg)来处理消息。

子线程使用Handler

如何在子线程使用Handler,并接收消息。
子线程是没有Looper对象的,所以我们需要在子线程调用Looper.prepare(),构建一个属于该子线程的Looper对象。在这之后,才能创建Handler对象,否则会抛出一个运行时异常(在该线程没有调用Looper.prepare(),是不能够创建Handler的)。
最后调用Looper.loop()让整个轮询器工作。

IdleHandler

public static interface IdleHandler {
    boolean queueIdle();
}
复制代码

在MessageQueue中分析next()方法时,贴出的源码中省略了IdleHandler的代码。我们在最后也提一下这个东西。

IdleHandler其实是一个简单的接口,MessageQueue里面有一个mPendingIdleHandlers对象,通过addIdleHandler(IdleHandler handler)方法,保存了添加进来的IdleHandler对象,当MessageQueue中消息列表为空或者需要执行的消息时间还没到,即闲置的时候,才会执行得到执行的任务。

for (int i = 0; i < pendingIdleHandlerCount; i++) {
    final IdleHandler idler = mPendingIdleHandlers[i];
    mPendingIdleHandlers[i] = null;
    boolean keep = false;
    try {
        keep = idler.queueIdle();
    } catch (Throwable t) {
        Log.wtf(TAG, "IdleHandler threw exception", t);
    }
    if (!keep) {
        synchronized (this) {
            mIdleHandlers.remove(idler);
        }
    }
}
复制代码

通过idler.queueIdle(),来执行任务的实际代码,最后,通过返回值Keep判断,这个idle是否还需要继续保持下去,不需要就把这个idleHandler移除掉。否则下次闲置的时候,还是会执行这个IdleHandler。

IdleHandler在源码中也有应用,例如在ActivityThread中,就有几个idleHandler的实现(GcIdler、PurgeIdler、Idler),比较好理解的就是Android应用的GC回收(GcIdler),IdleHandlder执行的就是一些必要但是不紧急的任务。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享