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

handler源码学习(3) — Looper

程序员文章站 2022-07-14 16:45:58
...

Handler是面试必问系列问题之一。本系列将从初学者的视角分析面试中常见的问题。

handler源码学习(1) — Handler
handler源码学习(2) — Message
handler源码学习(3) — Looper
handler源码学习(4) — MessageQueue

这里只讲解几个重要方法。其他方法(isCurrentThread(),myLooper()…)很简单就不讲解了。

prepare()

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<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));
    }
    
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

可以看到静态创建一个泛型为Looper ThreadLocal。然后通过prepare方法,将新建的looper设置进去。这里有一个考点,为什么要用ThreadLocal而不是直接创建一个局部变量Looper looper?要想解答这个问题,就要了解ThreadLocal的作用。从名字我们就可以看到ThreadLocal叫做线程本地变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。ThreadLocal的静态内部类ThreadLocalMap为每个Thread都维护了一个数组table,再看sThreadLocal是一个静态变量,这下明白了吗?静态变量是不会被回收的,也就是调用prepare之后,就会缓存每个线程对应的looper,这样就避免了重复创建looper。且只有当前线程可以获取到looper。

那既然是静态变量,就有另一个问题,什么时候回收掉这个looper?总不能线程都销毁了,还不回收吧?其实ThreadLocal并不存储这个looper的。存储这个Looper的是线程的成员变量threadLocals(注意是threadLocals,多了一个s),threadLocals是一个ThreadLocalMap。线程销毁时threadLocals就会被销毁。此时looper就可以被回收了。而threadLocal只不过是获取的key。也就是我们只缓存了一个通用的key。这么说可能太抽象,我们用伪代码举个例子:

private ThreadLocal key = new ThreadLocal();

class Thread{
    Map<ThreadLocal,Object> map = new HashMap();
}

class ThreadLocal{
    void set(Object value){
       Thread thread = getCurrrentThread()
       thread.map.put(this,value)
    }
}

不知道看懂没,从始至终ThreadLocal是不存储任何东西的(为了学这个,把ThreadLocal的源码也看了一遍,我真是个人才)。

loop()

    public static void loop() {
        //就是sThreadLocal.get()
        final Looper me = myLooper();
        if (me == null) {
            //还记得子线程不调用prepare()的报错吗?
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        
        final MessageQueue queue = me.mQueue;
        Binder.clearCallingIdentity();
        
        ···省略代码···

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // 没有消息直接return
                return;
            }

            ···省略代码···
            
            try {
                //msg.target就是handler
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if (observer != null) {
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            
            ···省略代码···
            
            //回收消息,还记得Message的时候我们讲解回收后会怎样吗?
            msg.recycleUnchecked();
        }
    }
    
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

注释已经很清楚了,省略的是一些日志打印相关的。这里有几个知识点;

  • 为什么子线程不调用prepare()会崩溃?
    • 因为不调用prepare(),sThreadLocal.get()为null。
  • loop()做了什么?
    • 不断的取消息
  • 没有消息时也一直取消息吗?
    • 是在不断的取。只不过queue.next(); // might block。当取下一个时,可能会阻塞。具体如何我们MessageQueue分析。

总结

解决问题

  • prepare()为什么使用ThreadLocal来缓存创建的Looper?
  • 为什么子线程不调用prepare()会崩溃?
  • loop()做了什么?没有消息时也一直取消息吗?

留下疑问

MessageQueue.next如何阻塞?