handler源码学习(3) — Looper

5 篇文章 0 订阅
4 篇文章 0 订阅

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如何阻塞?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值