Handler

Posted gitzzp

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Handler相关的知识,希望对你有一定的参考价值。

碎碎念

内存泄露

一、发生在延时消息、子线程阻塞后发过来的消息、以及网络请求结果回来后的消息,此时如果activity退出了,就会出现内存泄露。
解决办法:

  1. 延时消息(handler.sendMessageDelayed)
    handler.removeAllMessage();当activity销毁时,清空handler中所有的消息
  2. 子线程阻塞或者网络请求回来后的消息
    这种情况下,就不能使用上边的方法来处理,因为当activity销毁的时候,可能消息还没有添加到消息队列中,所以不会被清空。
    这里就需要先清空消息队列,然后将handler置空,在发送消息的时候,对handler进行判空,当handler为空时,不再添加消息。

二、在Activity中直接创建handler并进行消息处理,当activity销毁时,如果消息还没有处理完成,此时,handler会持有外部activity的引用,造成内存泄露。
解决办法:

  1. Handler使用静态内部类创建,且对外部activity的引用使用弱引用

子线程为什么不能创建handler

一、 子线程创建handler会发生什么

  1. 当我们在子线程直接 new Handler的时候,会抛出异常
    Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()
    
    handler在创建的时候,构造方法中会调用Looper.myLooper()方法来获取当前Handler的looper对象,如果looper对象为空,则会抛出上述异常。
    在Looper类中,有一个静态属性sThreadLocal,这是一个ThreadLocal对象,在ThreadLocal中保存了线程和looper对象的关系,Looper.myLooper()方法就是调用了ThreadLocal对象的get方法。
    当我们在new Handler之前没有调用Looper.prepare()方法为当前线程创建一个Looper对象的时候,就会报错。

二、为什么主线程可以直接创建Handler
应用在启动时会调用ActivityThreadmain方法,在main方法中会调用Looper.prepareMainLooper();在这个方法里边为主线程创建了一个Looper对象,所以在主线程中我们可以直接new Handler而不会报错。

为什么Handler不会造成死循环

当没有消息需要处理的时候,会调用native方法,释放当前时间片

Handler怎么发送异步消息,将runnable对象放入消息队列的

在handler的post()方法中,调用了getPostMessage(r)方法将runnable对象进行了封装,生成了一个message对象,并将runnable设置为了message的callback属性, 放入了消息队列中,在handler的dispatchMessage方法中,会优先对message的callback属性进行判空,如果不为空,则调用message.callback.run()方法,如果为空,则会调用handler中callback的dispatchMessage方法或者handler的dispathMessage方法进行处理

Handler中延时消息是如何处理的

使用Handler发送消息时,无论使用哪种方式发送延时消息,最终都会将延时时间转换为绝对时间,调用到Handler中的sendMessageAtTime()方法,然后调用消息队列的enqueueMessage将消息压入到消息队列,在压入消息队列时,会遍历消息队列对比消息的时间大小,将延时消息插入到合适的位置中

取消息时,调用messageQueue的next方法时,会判断取出最新消息时间和当前时间,如果当前时间小于消息时间,则不返回消息,同时计算出下次poll时间,调用native方法,释放时间片。

Handler中消息队列是什么数据结构

单向链表,先进先出

Handler中new Message和Message.obtain有什么区别

new Message:直接创建一个Message对象

Message.obtain:Message中做了缓存机制,最大缓存50个Message对象,使用Message.obtain方法会优先从缓存中获取Message对象

缓存是怎么建立的:
:Message类中有个全局变量sPool对象,当Message对象被回收时,调用recycler方法时,会清空消息所有标记,同时判断当前缓存池是否达到最大数量,如果未达到,会将当前消息赋值给sPool对象,将原有sPool对象赋值给当前消息的next属性,将他们连起来
:使用Message.obtain方法获取时,会判断sPool对象是否为空,如果为空,表示当前缓存池被消耗完或者未建立,会直接创建一个Message返回,如果不为空,则将当前sPool对象返回,同时将sPool对象的next取出赋值给sPool

Handler如何切换线程

子线程持有主线程Handler的引用,调用主线程handler的post方法,将一个runnable对象封装成message放入主线程Handler的消息队列中,当主线程handler的looper对象将消息从消息队列中取出来时,执行message中runnable的run方法,此时,就将子线程中写的代码切换到了主线程中来执行了。

ThreadLocal

ThreadLocal中保存了线程和存入属性的关系,在每个线程中,都有一个threadLocalMap的对象,来保存对应关系,我们可以从ThreadLocal的get方法入手查看

    public T get() 
        Thread t = Thread.currentThread();
        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;
            
        
        return setInitialValue();
    

    ThreadLocalMap getMap(Thread t) 
        return t.threadLocals;
    

    private T setInitialValue() 
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    

    void createMap(Thread t, T firstValue) 
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    

当我们调用ThreadLocal的get方法时,首先会获取到当前线程对象,然后调用getMap方法,返回对应线程中的ThreadLocalMap对象,如果map为空,那么会调用setInitialValue创建一个map,获取到ThreadLocalMap对象后,赋值给ThreadthreadLocals属性

    public void set(T value) 
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    

在Handler中,ThreadLocal用于存储每个线程中的Looper对象,所以赋值都是在Looper.prepare方法中调用set方法,set方法的逻辑与init方法基本类似。

ThreadLocal的基本逻辑为:

  1. 在Thread中有一个threadLocals属性,用来存放ThreadLocalMap对象
  2. 我们在使用ThreadLocal对象的set或者get方法时,都会获取到我们的当前线程对象,然后获取到当前线程的ThreadLocalMap对象
  3. ThreadLocalMap是一个类似于HashMap的数据结构,里边会存储ThreadLocal对象和ThreadLocal调用set方法传入值的关联关系
  4. 一个Thread只有一个ThreadLocalMap对象,每个ThreadLocalMap对象可能会存储多个ThreadLocal键值对,同理,一个ThreadLocal对象在不同的线程中,也会获取到不同的ThreadLocalMap对象
  5. 当我们调用ThreadLocal对象的get方法时,会根据当前线程对象获取到map,然后根据当前ThreadLocal对象,获取到存储的具体值。
  6. 所以我们在不同线程调用同一个ThreadLocal对象的get方法时,可以获取到不同的值

Handler中ThreadLocal的基本关系

Handler简单流程

以上是关于Handler的主要内容,如果未能解决你的问题,请参考以下文章

自定义无内存泄漏的Handler内部类

什么时候可以在android中使用强引用,这个代码是否泄漏?

不频繁的 Handler.post 导致大量丢帧和崩溃

什么是生命周期感知方式来实现重复的AsyncTask?

Android之Handler源代码深入解析

无限循环 - 延迟 - 单独的线程