欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android Handler、Looper、MessageQueue分析

程序员文章站 2022-07-14 17:07:48
...

前言

最近在面试的时候总是被问题Handler、Looper这几者之间的关系,其实自己本来就已经烂熟于心的了,问多了问的特别的烦,于是乎就把这篇总结在搬上来翻炒一下吧。其实这个类是一个非常重要的类,因为我们需要在异步线程里面更新界面操作的话都是需要用到这个类的;还有一个目的是为我后面分析HandlerThread、IntentService、BlockCanary框架做铺垫。

使用示例

public class MainActivity extends AppCompatActivity {
    
    @SuppressLint("HandlerLeak")
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            //在这里的话我们需要处理
            switch (msg.what) {
            }
        }
    };
    
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        new Thread() {
            @Override
            public void run() {
                //这里我们睡眠3s时间来模拟耗时的操作
                SystemClock.sleep(3000);
                //然后我们将消息发送到主线的Handler的 handleMessage 方法处理消息
                Message msg = mHandler.obtainMessage();
                msg.what = 1;
                msg.obj = "hello";
                mHandler.sendMessage(msg);

            }
        }.start();
    }
}

以上就是我们刚开始学习Android的时候使用Handler的最简单的一种方法,我们没有考虑Handler的内存泄漏和封装等等,就是纯粹的使用一下,接下我们就分析一下其内部的流程和原理。

代码分析

  • Handler 构造方法
public class Handler {
    public Handler() {
        this(null, false);
    }
    
    public Handler(@Nullable Callback callback, boolean async) {
        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;
    }
}

public class Looper {

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
}

从案例中初始化的Handler的默认构造函数,最后无非就是就是一些参数的初始化。但是这里却有一个非常关键的类 ThreadLocal,该类内部你可以理解为一个集合用于存储数据的。但是在Looper类中我们只是看到了 sThreadLocal 但是并没有创建Looper对象呀。这里我们就需要去跟踪 framework的源代码,需要了解 Android的一些启动流程。其初始化是在 frameworks/base/core/java/android/app/ActivityThread 中

public class ActivityThread {
    public static void main(String[] args) {
        ....
        Looper.prepareMainLooper();

        .......
        Looper.loop();
    }
}

我们将main 方法进行了简化以后大概逻辑就是创建Looper对象(通过静态方法prepareMainLooper),然后接着调用 loop() 方法不断的循环获取消息。注意:该方法是阻塞的,就是为了确保整个Android程序不会退出,因为我们知道 main 方法执行完毕以后程序也就是销毁了。同时这个方法又不会阻塞主线程,不会导致ANR的,因为涉及到的内容比较,后续会单独抽出一篇文章分析。

public class Looper {
    
    //该静态方法就是用于创建Looper对象,并且把对象存放到 sThreadLocal 中
    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));
    }
    
    public static void prepareMainLooper() {
        //创建一个Looper对象
        prepare(false);
        synchronized (Looper.class) {
            //如果之前创建过 sMainLooper对象的话,再次调用 prepareMainLooper 则会抛异常
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            //将创建的 Looper对象赋值给 sMainLooper,我们也把该对象表示主线程的Looper。
            sMainLooper = myLooper();
        }
    }
}
  • 消息的分发

接着我们再看 loop方法,该方法是不断的从消息队列(MessageQueue)中获取消息,当获取到消息以后然后调用 Handler的 messageDispatched 方法进行消息分发。

public class Looper {
    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;

        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
        
        boolean slowDeliveryDetected = false;

        for (;;) {
            //这里开了一个死循环是不断的从 MessageQueue的next 方法中获取消息的,如果没有消息的,则当前会一直处于阻塞状态的
            Message msg = queue.next(); // might block
            //如果获取到的 msg为空的话,表示消息队列退出状态
            if (msg == null) {
                return;
            }
            //这个地方是一个重点,我们以后在监控UI卡顿的时候会用的到的,这里是消息分发开始的统计
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            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;

            //这个地方是通过系统的 Trace 函数来监控 消息处理的速度,最后这个日志将会反应在 TraceView 文件中,这个其实也可以用来监控主线程 UI的卡顿情况。
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            //消息分发的开始时间
            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            Object token = null;
            long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
            //这里就是 消息分发的核心了, Message target是一个Handler类型,这里也就是调用Handler的
            //dispatchMessage 方法
            try {
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                //消息分发的结束时间
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                //同时调用 Trace 结束方法,用于统计 dispatchMessage 函数的执行时间
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            
            .......
            
            //如果事件分发速配度太慢(也就是说dispatchMessage 方法执行时间过长的话)则会打印日志
            if (logSlowDispatch) {
                showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
            }
            //事件分发完成以后,如果我们传入了自定义的 Printer 对象的话,则会打印日志。这里也是我们以后的一个关键地方,用于监听 UI 卡顿的地方。
            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }
            //同时将该消息进行一个回收
            msg.recycleUnchecked();
        }
    }
}

public class Handler {
    //在 loop方法中调用 Handler的 dispatchMessage 用于事件分发
    public void dispatchMessage(@NonNull Message msg) {
        //首先判断我们是否有传入接口用于回调函数
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //最后调用该方法用于处理消息方法,其实如果我们传入 callback 接口的话,我们也可以直接重新 dispatchMessage 方法用于处理消息的
            handleMessage(msg);
        }
    }
}

总结: 上面就是消息分发的主要函数,其中我们精简了一个很关键的地方就是 MessageQueue.next() ,我们只要把这个理解成开启一个死循环(实际上这里不是一个简单的死循环,这里涉及到Linux的epoll机制)不断的去获取 消息队列中的任务,如果MessageQueue 中没有消息的话则当前的死循环一直处于阻塞等待的状态,直到 MessageQueue中有消息来的时候才会继续以下的操作,我们这里可以类比成线程中的阻塞队列(但是这里远比阻塞队列的原理复杂的多,后续会单独的讲解)来理解; 如果获取到消息为空的话则直接退出死循环执行完毕。然后我们获取到消息以后就调用Message 变量中的Hander的 dispatchMessage 方法,最后调用 handleMessage 来处理消息分发。

  • 事件发送

上面我们介绍了消息分发的一些基本原理以及函数的调用过程,说白了就是从消息队列中取出任务,然后通过Handler来对进行一个分发或者是处理。同时我们还需要将消息存储到队列中,其实也是通过Handler这个中介来完成的;在案例的代码中我们通过 sendMessage 来发送消息我们看看具体的源代码

public class Handler {
    public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }
    
    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    
    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        //首先会判断当前Handler 的队列是否为空,注意这里的 MessageQueue 是通过 Looper获取的
        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;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        //最后我们发现所有的发送消息都是把消息插入到 MessageQueue 消息队列中
        return queue.enqueueMessage(msg, uptimeMillis);
    }
}

通过上面的代码我们可以看出 Handler其实就是一个包装类,发送消息的时候我们不是不是直接调用MessageQueue里面的方法,而且通过Handler来进行封装的,所有对外的发送消息都是通过调用Handler进行处理的。

public class MessageQueue {
    boolean enqueueMessage(Message msg, long when) {
        //记住这个地方是一个非常关键的地方,如果一个Message没有Handler,相当于这个Message不知道最终要分发到什么地方去的,这个是一个要非常值得注意的地方。
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        synchronized (this) {
            //首先判断消息队列是否关闭,如果关闭直接抛出异常,同时将msg进行重置,利于垃圾回收
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                msg.recycle();
                return false;
            }
            //接着将消息标志为正在使用中
            msg.markInUse();
            //这里的 when 表示的是
            msg.when = when;
            //这里的 mMessages 表示头消息
            Message p = mMessages;
            boolean needWake;
            //如果传入进来的消息的执行时间要 小于 链表的头部时间的话,则将head 执行 msg
            if (p == null || when == 0 || when < p.when) {
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                //首先这里定义一个前置指针,主要是是用于负责将 msg 插入到 pre的后面。
                Message prev;
                //下面就是基本的链表的查找以及插入数据操作。
                for (;;) {
                    prev = p;
                    p = p.next;
                    //如果链表到末尾或者是前一个节点的时间小于当前msg的时间,但是下个节点的时间大于当前时间,所以就跳出循环。
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                //这里就执行消息的插入,也就是链表的插入操作
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }
            //这个地方非常关键,判断是否需要唤醒
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        //如果 msg成功的插入到消息队列中以后,我们也可以说是消息发送成功,但是这个不是真正意义上的成功,具体是否达到了目的地的话,我们还需要看 loop 方法的消息分发。
        return true;
    }
}

从上面的源代码中我们可以很清楚看到 MessageQueue 内部使用的是链表数据结构来保存数据的,所谓的插入数据就是将数据保存到链表的合适位置。注意:这里的链表是有序的排序是根据消息转发的时间升序排序的,也就是说越早转发的消息则越在怕在前面的。

  • 生成 Message 消息载体

上面我们通过代码分析了消息的发送已经消息的轮训获取,但是我们怎么创建这个 Message呢?刚开始学习的人可能会直接通过 new的方式直接创建一个对象:Message msg = new Message()。但是这种方式是非常不好的。我们在示例代码的时候是通过Handler.obtainMessage 创建的。

public class Handler {
    //从这里我们可以看出最后还是调用 Message的方法。
    public final Message obtainMessage() {
        return Message.obtain(this);
    }
}

public class Message {
    
    public static Message obtain(Handler h) {
        //我们在将消息插入到消息队列的时候,就有对 target的一个检测判断,如果一个Message的 target为空的话,那么最终就不知道将消息分发到哪里去了。
        Message m = obtain();
        m.target = h;
        return m;
    }
    
    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
     //在 源代码中有这么一句非常重要的提示,就是让我们直接从全局的池子中获取 Message,这样子可以避免创建大量的 Message 对象。
    public static Message obtain() {
        synchronized (sPoolSync) {
            //如果 对象池中还没有对象的话,则直接 new Message 对象出来
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                //清除消息正在使用的标志 in-use flag
                m.flags = 0; 
                //如果我们从 对象池中拿出了一个对象以后,相应的大小就需要减1
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }
    
    //回收 Message 消息对象,这个方法在 MessageQueue 中的 loop方法中调用的,当我们把消息分发完成以后,这个消息载体也就没有多大作用了,这个时候我们就会这个对象放到对象池中。
    void recycleUnchecked() {
        //将所有的标志全部进行一个复位操作,也就是置为 null
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;
        
        synchronized (sPoolSync) {
            //如果 对象池中的 对象个数小于 最大个数(50个),则会再次将对象放回到池子中。
            //注意这个个数对外没有暴漏出来,所以也不能修改的
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }
}

从上述的代码中我们可以明白为什么不建议自己直接通过 new 的方式来创建 Message对象了吧,因为我们每次创建的 Message 对象最后使用完以后都会放到一个对象池中,但是这样子又不利于我们去利用这些对象的,所以我们就直接通过Handler对外暴漏的方法来获取一个Message 对象。还有一个问题就是我们创建出来的 Message对象一定要对 target 赋值,因为如果该字段没有赋值具体的Handler对象的话,这个消息不知道被分发到哪里去

  • ThreadLocal 重要性

我们在Looper 类中的时候有说过 prepare 方法首先会去ThreadLocal 对象中获取Looper对象,如果为空则创建一个Looper对象,然后在将该对象存放到 ThreadLocal对象中。

1. 保存对象 set()

public class ThreadLocal {
    public void set(T value) {
        Thread t = Thread.currentThread();
        //首先线程 t中的 threadLocals (这是一个map)对象是否为空
        ThreadLocalMap map = getMap(t);
        if (map != null)
            //最后我们将当前 ThreadLocal对象作为 key,value保存到map集合中
            map.set(this, value);
        else//为空则创建 ThreadLocalMap 对象并且赋值 线程中的 threadLocals变量
            createMap(t, value);
    }

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
 }

通过上面的代码中我们可以看到一个非常关键的类就是 Thread 中的 threadLocals变量,这个变量是一个 Map类型,说明新创建一个线程的也会创建一个Map集合用来保存数据,这些线程是私有的,所以也是线程安全的。

2. 获取对象 get()

public class ThreadLocal {
    
    public T get() {
        //首先获取当前线程对象
        Thread t = Thread.currentThread();
        //然后获取线程类中的成员变量 threadLocals对象,该对象是每个线程的一个私有成员变量。
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //如果 ThreadLocalMap对象没有初始化,则进行初始化下
        return setInitialValue();
    }
    
    //这里直接创建一个 ThreadLocalMap 对象,并且把当前线程当作key,firstValue当作value保存到map中
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            //如果ThreadLocalMap为空则直接创建一个,并且赋值给当前线程的threadLocals 变量
            createMap(t, value);
        return value;
    }
}

通过上述的代码我们可以总结出:一个线程中只能有一个 Looper对象,由于创建Looper对象的时候也创建 MessageQueue,也就是说一个线程中只有一个Looper和MessageQueue对象。主要是通过线程的一个成员变量 threadLocals,该变量是一个Map类型,也就是相当于我们 Looper和 MessageQueue对象存放到线程的私有空间里,下次需要的时候直接拿出来。只是中间我们通过了一个中介ThreadLocal来获取而已

总结

通过对Handler的源代码的分析我们可以将其总结:Message只是一个数据载体真正的数据是放在 Message的变量 obj中,在消息分发的过程中我们必须指定 Message的target到具体的Handler对象,因为只有指定了target才有处理消息的具体方法;同时 MessageQueue封装在 Looper中,在创建Looper对象的同时也创建MessageQueue对象,一个线程只有一个Looper对象,也只有一个MessageQueue对象;Handler只是对它们内部的一个整体封装,当我们发送的消息的时候其实就是将消息插入到 MessageQueue(内部是由链表实现)中,消息转发主要是通过 Message的target(Handler对象)调用 dispatchMessage 方法,最后调用 handleMessage 方法处理。

提问和反思

  • 一个线程有多少个Looper,多少个MessageQueue?如何保证一个线程中一个 Looper?
  • 我们如何监控 Looper在分发消息的过程中的耗时的操作?我们如何监控 UI的卡顿?
  • Looper对象中的 loop 方法为什么不会阻塞主线程导致ANR?
  • MessageQueue 底层是如何传送消息的,特别是两个线程如何通信的?