Handler消息机制(四)Looper原理

1,157 阅读6分钟

原文链接

本篇文章是Handler消息机制中的第四篇,主要讲述Looper在Handler中的作用及实现的原理。 在Android Handler消息机制中Looper是一个非常重要的角色,扮演着线程消息的轮询和派发等重要功能,可以说是Handler消息机制的核心部分。

该系列的其他文章

  1. Handler消息机制(一)Message复用原理
  2. Handler消息机制(二)ThreadLocal原理
  3. Handler消息机制(三)MessageQueue原理
  4. Handler消息机制(四)Looper原理
  5. Handler消息机制(五)Handler原理

本文主要包含以下三个部分:

  • 什么是Looper?
  • 如何使用Looper?
  • Looper实现的原理?

1. 什么是Looper?

官方描述:

Class used to run a message loop for a thread. Threads by default do not have a message loop associated with them; to create one, call {@link #prepare} in the thread that is to run the loop, and then {@link #loop} to have it process messages until the loop is stopped.

Looper主要用于线程消息的轮询和派发,在默认情况下,线程没有可以关联的消息循环,需要为线程创建一个Looper对象,在线程中去使用loop来循环消息。 一般我们在使用线程时,当分配给线程的任务处理完毕后,线程会被回收掉。但是当Looper和线程关联在一起之后,线程会进入阻塞状态,当Looper提供一个消息事件时,线程会从阻塞中唤醒并处理消息事件。在这里,Looper主要是负责为线程提供处理的事件,Looper是如何将消息提供到指定的线程中的呢?带着这个疑问我们在后面会分析。

2. 如何使用Looper?

在官方描述里,我们看到Looper必须要和线程关联在一起才可以使用,如何关联呢? 示例代码:

    private static class LooperThread extends HandlerThread {
        private static final String TAG = "LooperThread";
        private Handler mHandler;

        public LooperThread() {
            super(TAG);
        }

        @Override
        public void run() {
            Looper.prepare();
            mHandler = new Handler(getLooper()) {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                }
            };
            Looper.loop();
        }
    }

在线程内部调用Looper.prepare()可以在线程内部关联一个Looper对象,然后在构建mHandler时,通过线程的getLooper()方法获取到Looper对象传入到Handler的构造方法中,最后调用Looper.loop()方法就可以实现在Thread线程内部轮询消息事件了,是不是很简单。

注意我这里使用的是HandlerThread线程,该线程默认提供了getLooper()方法,一般线程是没有Looper的。

3. Looper的实现原理?

在使用Looper时,我们调用了Looper的prpare方法和loop方法,下面我们就从这两个地方入手,分析下Looper的具体的实现原理。

3.1 prepare方法

    public static void prepare() {
        prepare(true);
    }

    /**
    * @params quitAllowed : 该参数表示是否允许退出消息轮询,true为允许,false为不允许
    */
    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));
    }

prepare是一个静态方法,可以在任意线程中调用,在方法内直接调用了私有的prepare方法,并将允许退出的标记位置为true,然后将创建的Looper对象存储到sThreadLocal中,sThradLocal是ThreadLocal对象,它可以存储属于线程私有的数据,通过ThreadLocal将Looper和Thread关联在一起,这就是prepare的主要职责,构建Looper对象并且将Looper和线程相关联。关于ThreadLocal的内容,可以参考该系列的Handler消息机制(二)ThreadLocal原理

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

这里可能有人会疑问,sThreadLocal不是Looper私有的对象么? 看上面的定义,sThreadLocal是一个静态的变量,它是属于Looper类的数据,会被加载到JVM的方法区,是在JVM虚拟机中一个全局唯一的变量,而不属于Looper的具体对象中。后续有机会详细说下JVM的虚拟机内存模型。 继续到Looper的构造方法:

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

在Looper的构造方法内,主要做了两件事,创建MessageQueue对象和与该Looper对象相关联的Thread。 关于MessageQueue的详细内容,可以参考该系列的Handler消息机制(三)MessageQueue原理

3.2 loop方法

    public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        // 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();

        // Allow overriding a threshold with a system prop. e.g.
        // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
        final int thresholdOverride =
                SystemProperties.getInt("log.looper."
                        + Process.myUid() + "."
                        + Thread.currentThread().getName()
                        + ".slow", 0);

        boolean slowDeliveryDetected = false;

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // 当msg为null时,退出消息轮询,只有调用quit方法且该轮询允许退出时,msg才会为null
                // No message indicates that the message queue is quitting.
                return;
            }

            final long traceTag = me.mTraceTag;
            long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
            long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
            if (thresholdOverride > 0) {
                slowDispatchThresholdMs = thresholdOverride;
                slowDeliveryThresholdMs = thresholdOverride;
            }
            final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
            final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

            final boolean needStartTime = logSlowDelivery || logSlowDispatch;
            final boolean needEndTime = logSlowDispatch;

            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }

            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            ...
            msg.recycleUnchecked();
        }
    }

loop方法是一个静态方法,可以在任意线程中调用,首先通过getLooper()方法获取到该线程的looper对象,然后从looper内的MessageQueue中读取消息,当queue读取到消息时,会通过msg.target.dispatchMessage(msg)将消息派发,到这里实际上已经将msg发送到Handler中去消费事件了,target是Handler对象。至此该消息已经实现了从Handler的sendMessage开始,然后进入Looper内的消息队列中,当Looper内的消息队列被唤醒,返回msg消息时,该消息在Thread的run方法内被分到Handler中去处理,消息在指定线程中被消费。

Looper流程图.png

接着我们看下getLooper方法,很简单,就是将sThreadLocal对象记录的该线程下的looper对象返回。

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

通过sThreadLocal很方便的实现了不同线程下的消息可以分别派发到对应的线程内去处理,不得不说,ThreadLocal的确实很精髓。

3.3 quit方法

    public void quit() {
        mQueue.quit(false);
    }
    public void quitSafely() {
        mQueue.quit(true);
    }

quit方法内实际上是调用的MessageQueue的quit方法,在queue的quit方法内,会把还在消息队列内的msg回收掉。 使用方式:

方式一,非安全的退出
    mHandler.getLooper().quit();
方式二,安全的退出
    mHandler.getLooper().quitSafely();

如果消息已经被处理完毕,我们需要线程退出,可以通过上面两个方法实现线程的退出,quit是非安全的,quitSafely是安全的,区别是非安全的不会等队列内的消息处理完毕就会退出线程,安全的是将队列内的消息处理完毕后再退出。

结语:Looper是为Handler消息机制而服务的,Looper必须要和线程关联,然后Looper去轮询msg消息,当没有消息时,线程会因Looper的loop方法内的 "Message msg = queue.next();" 操作而阻塞,当MessageQueue的next方法有消息返回时,线程会将消息发送给Handler处理,handler的dispatchMessage方法是线程的run方法内被调用的,所以Handler消费消息是在我们指定的线程上,实现了跨线程的操作。当我们调用Looper的quit或者quitSafely方法(UI线程不允许退出,调用quit方法会报异常),MessageQueue的next方法会返回null消息,在Looper的loop方法内,当检测到msg==null时,Looper的loop方法会退出,线程也会结束。