每日一道面试题(第9期)---谈谈Handler机制和原理

零零碎碎的东西总是记不长久,仅仅学习别人的文章也只是他人咀嚼后留下的残渣。无意中发现了这个每日一道面试题 ,想了想如果只是简单地去思考,那么不仅会收效甚微,甚至难一点的题目自己可能都懒得去想,坚持不下来。所以不如把每一次的思考、理解以及别人的见解记录下来。不仅加深自己的理解,更要激励自己坚持下去。

Handler 机制

Handler 想必接触 Android 的都已经很熟悉了,我们通常用用于子线程与主线程的通信,并在主线程中处理相关消息,比如更改 UI 等。Handler 的消息机制为我们处理线程中的通信确实方便了许多。

基本使用

最简单的使用就是在主线程中创建 Handler 对象并重写 handlerMessage() 方法处理消息,在子线程中通过 handler 对象的 sendMessage 方法发送消息。

Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                // TODO write your message processing code
                ...
            }
        };

        new Thread(){
            @Override
            public void run() {
                super.run();
                //TODO  handling events
                ...
                handler.sendMessage(Message.obtain());
            }
        };

为了简单示例,Handler 才这样写的。这种创建方法会造成内存泄漏,具体愿意以及解决方案在每日一道面试题(第 1 期)—自定义 handler 如何有效保证内存泄漏问题 都已经说明,这里不再赘述。

而关于 Handler 的原理,我们可能会多多少少的了解到与 Looper、Message、MessageQueue 都是离不开的,那么具体是怎麽配合呢?下面我根据消息机制的过程,结合源码一步一步的解析。

Handler 原理

创建 Handler 对象

在我们使用时,首先就是创建 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());
            }
        }
        //获取本地 TLS 存储区的 Looper 对象引用
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        //使用 Looper 中的 MessageQueue
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
}

public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
}

好吧,很多构造函数,不过有用的就是这两个,其他都是调用这两个而已。从两个对比可以看出,如果你没有传入 Looper 对象,那么就会通过 Looper.myLooper() 获取。传入的话,就是用自定义的,并且 MessageQueue 一直是使用 Looper 中的 MessageQueue,所以这里出现了第两个重要结论:

  • Handler 对象的创建一定需要一个 Looper 对象
  • Looper 中一定有 MessageQueue

然后还有两个参数 callBack 和 async,callBack 是 Handler 内部的一个接口,内部只有一个 handleMessage() 方法,作用我们下面讲到再说。然后就是 async,这个就好理解了,就是决定是异步处理消息,还是同步处理消息,默认为同步 false;

public interface Callback {
    public boolean handleMessage(Message msg);
}

Looper 对象的创建

上一个步骤中我们知道,Handler 是直接从 Looper.myLooper() 中获得对象的,我们具体探讨一下。

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

很简单,是从一个集合中拿到的,这个 sThreadLocal,是 ThreadLocal 类的对象,代表着本地存储区(Thread Local Storage 简称 TLS),线程中的唯一 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));
}

找到啦,由两个方法的权限访问修饰符可知,我们只能调用第一个,也就是说,第二个方法的参数永远是 true。再来看第二个方法,首先就是一个异常捕获,有的人可能已经很熟悉了,那就是一个线程中只能有一个 Looper。后面就是没有的话就 new 一个 Looper 对象,构造函数中都干了什么呢?

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

也很简单,初始化了 mQueue 也就是 MessageQueue,还有就是当前所在的线程 mThread。从权限访问修饰符可以看出,这是一个私有的构造方法,所以说,我们创建 Looper 方法只有 prepare() 方法啦。

MessageQueue 对象的创建

上述 Looper 对象的创建中,new 了一个 MessageQueue 方法,我们看看都干了什么。

MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

也很简单,初始化了两个变量 mQuitAllowed 与 mPtr,第一个代表着消息队列是否可以退出,上面也说了,我们么的办法,只能是 true。第二个 mPtr,涉及到 Native 层的代码,在 native 层也做了一个初始化,具体深入了解可到此处Android 消息机制 2-Handler(Native 层)

发送消息

创建完 Handler,下一步就是发送 Message 了,去看看源码

public final boolean sendMessage(Message msg){};
public final boolean sendEmptyMessage(int what){};
public final boolean sendEmptyMessageDelayed(int what, long delayMillis){};
public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis){};
public final boolean sendMessageDelayed(Message msg, long delayMillis){};
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);
}

又是好多发送消息的,不过他们最后都调用了 sendMessageAtTime 方法。第一个参数是要发送的消息,而第二个是一个绝对时间,也就是发送消息的时间。在做了一些判断之后,调用了 enqueueMessage 方法。

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    //消息获得发送该消息的 Handler 对象引用
    msg.target = this;
    //是否同步属性
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

在又做了一些 Message 的属性初始化后,调用了 queue 的 enqueueMessage 方法,而这个 queue 就是在 Looper 对象中获得的 MessageQueue 对象。下面是 MessageQueue 中的 enqueueMessage 方法

boolean enqueueMessage(Message msg, long when) {
    // 每一个 Message 必须有一个 target
    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) {  //正在退出时,回收 msg,加入到消息池
            msg.recycle();
            return false;
        }
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;//mMessages 为当前消息队列的头结点
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            //p 为 null(代表 MessageQueue 没有消息) 或者 msg 的触发时间是队列中最早的, 则进入该该分支
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked; 
        } else {
            //将消息按时间顺序插入到 MessageQueue。一般地,不需要唤醒事件队列,除非
            //消息队头存在 barrier,并且同时 Message 是队列中最早的异步消息。
            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;
}

总结就是,MessageQueue 按照消息的触发时间插入队列,队头是最早要触发的消息。一个新的消息加入会根据触发时长从队头开始遍历。

消息轮询

好了,消息已经发送给 MessageQueue 了,那么谁来管理这个 MessageQueue,将其中的消息正确的、准确的分发呢?那就是 Looper,准确的说是 Looper 中的 loop() 方法,这也是我们在子线程中创建 Handler 先要之前要 Looper.perpare(),之后要 Looper.loop() 的原因。

public static void loop() {
        final Looper me = myLooper();//获取 TSL 存储区的 Looper 对象
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;//获取相对应的消息队列

            ................

        for (;;) {//消息主循环,除非线程退出,不然会一直循环,没有消息时会阻塞
            //获取下一个消息,没有消息时会阻塞,消息队列退出后会返回 null,则该循环也退出
            Message msg = queue.next(); // might block
            if (msg == null) {
                return;
            }

            // 默认为 null,可通过 setMessageLogging() 方法来指定输出,用于 debug 功能
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            ................

            try {
                //获取消息后根据 msg 的 target 即所属 Hnadler 分发消息
                //target 即 Handler 在上面发送消息代码解释中有说明
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

            .................

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            ................

            //分发完此消息,就回收此 Message 对象,留以复用
            msg.recycleUnchecked();
        }
}

一些代码解释已经很清楚了,在在这里面主要是一个死循环轮询消息。由 MessageQueue 的 next() 方法取出消息,再由 Message 所属的 Handler 对象 dispatchMessage() 方法分发消息。首先我们看 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();
            }
            //阻塞函数,参数 nextPollTimeoutMillis 表示等待时长,或者消息队列被唤醒,都会返回
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    //当消息中的 Handler 为空时,在 MessageQueue 中寻找下一个异步消息
                    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 {
                        // 获得消息
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        //改变 message 状态
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    //没有消息,设置阻塞时长
                    nextPollTimeoutMillis = -1;
                }

                // 如果正在退出,返回空
                if (mQuitting) {
                    dispose();
                    return null;
                }

                .............
        }
}

nativePollOnce 是一个阻塞函数,参数 nextPollTimeoutMillis 则代表阻塞时长,当值为-1 时,则会一直阻塞下去。
所以说,next 函数在 MessageQ 有消息时,会获取消息并返回,在没有消息时,则会一直阻塞。

分发消息

获取完消息,就要到分发消息了,也就是消息轮询总结中的 dispatchMessage 函数,在 Handler 类中

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

这里的调用流程就是

  • 首先判断 Message 的 callback 是否存在,如果存在,就执行 handleCallback 函数,这个函数就一个简单的 message.callback.run() 方法调用。Message 中的 callback 实际上是一个 Runnable,所以就会执行自定义的 run 函数。这个的作用是在 Handler 的 post 消息上,其实 post 消息与 send 消息并没有太大的不用,只是通过 getPostMessage 方法将 Message 封装了一下,其实就是将自定义的 Runnable 传给 Message 的 callback,这样在分发消息的时候就是直接执行自定义的 Runnable 中的 run 函数。
    public final boolean post(Runnable r){
      return  sendMessageDelayed(getPostMessage(r), 0);
    }
    private static Message getPostMessage(Runnable r) {
      Message m = Message.obtain();
      m.callback = r;
      return m;
    }
  • 然后就是 Handler 类中有一个 Callback 接口,接口中只有一个 handleMessage() 函数。如果成员变量 mCallBack 存在,就会首先执行此接口中的函数。实际使用中我们可以实现此接口,并重写方法。然后通过 Handler 的构造方法传入。
    public interface Callback {
      public boolean handleMessage(Message msg);
    }
  • 最后才是我们熟悉的重写 Handler 类中的 handleMessage 方法,这个方法如果在上面那种处理过后返回的是 true,那么就根本到不了这个函数。所以说上面两种处理方式都可以拦截消息。其中第一种是一定会拦截消息,第二种则由返回值确定。
    public void handleMessage(Message msg) {
    }

总结

转了一圈,从 Handler 到 Looper 又到 MessageQ 最后又回到 Handler,整个消息机制也就差不多这样啦,当然 Message 在其中就是一个实体啦,可以协上数据一起传递消息。我也是根据这个顺序,一步一步的慢慢了解每个步骤的。

最后,附上一张图吧,会更清晰一点


   转载规则


《每日一道面试题(第9期)---谈谈Handler机制和原理》 飞跃 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
每日一道面试题(第10期)---谈谈对HandlerThread与AsyncTask的理解 每日一道面试题(第10期)---谈谈对HandlerThread与AsyncTask的理解
零零碎碎的东西总是记不长久,仅仅学习别人的文章也只是他人咀嚼后留下的残渣。无意中发现了这个每日一道面试题 ,想了想如果只是简单地去思考,那么不仅会收效甚微,甚至难一点的题目自己可能都懒得去想,坚持不下来。所以不如把每一次的思考、理解以及别
2020-03-31
下一篇 
每日一道面试题(第8期)---ANR出现的场景以及解决方案 每日一道面试题(第8期)---ANR出现的场景以及解决方案
零零碎碎的东西总是记不长久,仅仅学习别人的文章也只是他人咀嚼后留下的残渣。无意中发现了这个每日一道面试题,想了想如果只是简单地去思考,那么不仅会收效甚微,甚至难一点的题目自己可能都懒得去想,坚持不下来。所以不如把每一次的思考、理解以及别人
2020-03-31
  目录