Handler和ThreadLocal的原理剖析

藏宝库编辑 2024-9-7 08:30:57 87 0 来自 中国
前言

    说到Handler机制不得不提的就是这几上将:Handler,Looper,MessageQueue,Message。延伸重点ThreadLocal!!!
    UI主线程初始化第一个Handler时会通过ThreadLocal创建一个Looper,该Looper与UI主线程一一对应。利用ThreadLocal的目标是包管每一个线程只创建唯一一个Looper。之后其他Handler初始化的时间直接获取第一个Handler创建的Looper。Looper初始化的时间会创建一个消息队列MessageQueue。至此,主线程、消息循环、消息队列之间的关系是1:1:1。Handler、Looper、MessageQueue的初始化流程如下图所示:Hander持有对UI主线程消息队列MessageQueue和消息循环Looper的引用,子线程可以通过Handler将消息发送到UI线程的消息队列MessageQueue中。
    我们写代码时主线程为啥不消初始化looper?由于主线程looper早在ActivityThread初始化的时间就声明好了,可以直接拿来用的。分析源码我们知道MessageQueue在Looper中,Looper初始化后作为对象丢给了Handler,而且又存在了ThreadLocal内里,ThreadLocal对象的话作为Key又存在了ThreadLocalMap,ThreadLocalMap对象是Thread内里的一个属性值,也就是说Looper作为桥梁毗连了Handler与Looper所在的线程,下面我们细品一下代码。
要想深入明白Handler的机制,我们的须要需明白ThreadLocal:
    ThreadLocal叫做线程变量,意思是ThreadLocal中添补的变量属于当火线程,该变量对其他线程而言是隔离的,也就是说该变量是当火线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
ThreadLocal的原理


    要看原理那么就得从源码看起。
    我们先从set方法看起:
2.png     从上面的代码不丢脸出,ThreadLocal  set赋值的时间起首会获取当火线程thread,并获取thread线程中的ThreadLocalMap属性。如果map中属性不为空,则直接更新value值,如果map中找不到此ThreadLocal对象,则在threadLocalMap创建一个,并将value值初始化。显然ThreadLocal对象存的值是根据线程走的!
    那么ThreadLocalMap又是什么呢,尚有createMap又是怎么做的,我们继承往下看。
起首第一步我们得要知道这个东西
每个Thread有一个属性,范例是ThreadLocalMap,从代码不丢脸出ThreadLocalMap是ThreadLocal的内部静态类。它是与线程所绑定接洽在一起的,可以看成一个线程只有一个ThreadLocalMap,知道这一点我们再往下看。
4.png ThreadLocalMap的构成重要是用Entry来保存数据 ,而且还是继承的弱引用。在Entry内部利用ThreadLocal对象作为key,利用我们设置的value作为value。
5.png get的话就比力简朴了就是获取当火线程的ThreadLocalMap属性值,在获取Map中对应ThreadLocal对象的value值并返回。
   ThreadLocal总结一下就是:每个线程Thread自身有一个属性ThreadLocalMap,然后ThreadLocalMap是一个键值对,它的key值是ThreadLocal对象,它的value值则是我们想要保存处理的数据值。getMap是找到对应线程的ThreadLocalMap属性值,然后通过判定可以初始化大概更新数值。
ThreadLocal分析完了我们接着来看Handler吧。
   由于主线程在ActivityThread的main方法中已经创建了Looper,以是主线程利用Handler时可以直接new;子线程利用Handler时须要调用Looper的prepare和loop方法才气举行利用,否则会抛出非常。以是我们从Looper的prepare来分析。
    Looper 提供了 Looper.prepare() 方法来创建 Looper ,而且会借助 ThreadLocal 来实现与当火线程的绑定功能。Looper.loop() 则会开始不绝实行从 MessageQueue 中获取 Message , 并分发给对应的 Handler,也就是说 Handler 跟线程的关联是靠 Looper 来实现的。
Looper.loop() 负责对消息的分发,也是和prepare配套利用的方法,两者缺一不可。
msg.target是个啥呢,我们追到Message内里不难发现实在它就是我们发送消息的Handler,这写法是不是很聪明,当从MessageQueen中捞出Message后,我们就能直接调用Handler的dispatchMessage,然后就会走到我们的Handler的handleMessage了。直接上源码:
11.png     处理分析完了,我们看个简朴点的,消息发送吧。Handler 提供了一些列的方法让我们来发送消息,如 send()系列 post()系列 。不外不管我们调用什么方法,终极都会走到 MessageQueue的enqueueMessage(Message,long) 方法。也就是实现了消息的发送,将Message插入到我们的MessageQueue中。
注意:dispatchMessage() 方法针对 Runnable 的方法做了特殊处理,如果是 ,则会直接实行 Runnable.run() 。(判定依据是上述msg.callback !=null这句)
 MessageQueue是个单链表。

MessageQueue里消息按时间排序
MessageQueue的next()是个堵塞方法
总结分析:Looper.loop() 是个死循环,会不绝调用 MessageQueue.next() 获取 Message ,并调用 msg.target.dispatchMessage(msg) 回到了 Handler 来分发消息,以此来完成消息的回调。



Handler什么会出现内存走漏标题呢?


分析
    Handler利用是用来举行线程间通信的,以是新开启的线程是会持有Handler引用的,如果在Activity等中创建Handler,而且优劣静态内部类的情势,就有大概造成内存走漏。
    非静态内部类是会隐式持有外部类的引用,以是当其他线程持有了该Handler,线程没有被烧毁,则意味着Activity会不绝被Handler持有引用而无法导致采取。
MessageQueue中如果存在未处理完的Message,Message的target也是对Activity等的持有引用,也会造成内存走漏。
办理的办法
    利用静态内部类 + 弱引用的方式: 静态内部类不会持有外部类的的引用,当须要引用外部类相干操纵时,可以通过弱引用还获取到外部类相干操纵,弱引用是不会造成对象该采取采取不掉的标题,不清楚的可以查阅JAVA的几种引用方式的详细阐明。
    在外部类对象被烧毁时,将MessageQueue中的消息清空。
Looper死循环为什么不会导致应用卡死,会斲丧大量资源吗?


    对于线程便是一段可实行的代码,当可实行代码实行完成后,线程生命周期便该停止了,线程退出。而对于主线程,我们是绝不盼望会被运行一段时间,自己就退出,那么怎样包管能不绝存活呢?简朴做法就是可实行代码是能不绝实行下去的,死循环便能包管不会被退出,比方,binder线程也是接纳死循环的方法,通过循环方式差别与Binder驱动举行读写操纵,固然并非简朴地死循环,无消息时会休眠。但这里大概又引发了另一个标题,既然是死循环又怎样行止理其他事件呢?通过创建新线程的方式。真正会卡死主线程的操纵是在回调方法onCreate/onStart/onResume等操纵时间过长,会导致掉帧,乃至发生ANR,looper.loop自己不会导致应用卡死。
    主线程的死循环不绝运行是不是特殊斲丧CPU资源呢? 实在否则,这里就涉及到Linux pipe/epoll机制,简朴说就是在主线程的MessageQueue没有消息时,便壅闭在Loop的queue.next()中的nativePollOnce()方法里,此时主线程会开释CPU资源进入休眠状态,直到下个消息到达大概有事件发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里接纳的epoll机制,是一种IO多路复用机制,可以同时监控多个形貌符,当某个形貌符停当(读或写停当),则立即关照相应步调举行读或写操纵,本质同步I/O,即读写是壅闭的。 以是说,主线程大多数时间都是处于休眠状态,并不会斲丧大量CPU资源。
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-12-4 16:23, Processed in 0.205127 second(s), 36 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表