Handler的意义
Handler不仅Android独创的,在Spring里也有Handler,web开发中的ajax也跟Handler的思想差不多。
Handler使得Android开发难度大大降低,在android中几乎看不到多线程死锁问题,是因为Handler为我们解决的了线程间的通信。
其实Handler不仅仅是为了线程间的通信。线程间的通信可以说只是Handler提供的一个辅助功能。
Handler的本质是 - 消息管理机制,他管理的安卓上所有的消息(事物),而android里的一切都会封装成消息(事物),(如点击屏幕,显示页面,刷新屏幕等)。
或者说 - Android中所有的代码都是跑在Handler上的。
线程间的通信
android中线程间的通信主要就是Handler,Handler通信实现的方案实际上是内存共享的方案。
Message其实就是一块内存,而内存是没有属于线程之说的,内存在线程间是共享的,通过A线程将Message存到一个MessageQueue的容器中,B线程在这个容器里,将Message(内存)取出来,实现线程间的通信。
线程间通信在android中还有EventBus/RxBus等。
在java里主要是wait/notify。而这部分在Android里的handler已经将需要这部分功能进行了Linux层的封装。
所以不需要我们自己wait/notify去实现线程间的通信。
从开天辟地到app启动
从手机开机
(有一回面试被问过,手机启动经历了什么事。)
Android系统会在开机时由Native层创建第一个进程init进程,随后init进程会解析一个叫init.rc的本地文件创建出Zygote进程
Zygote(受精卵)的职责就是孵化进程(一切应用进程均由Zygote进程孵化)。当孵化出的第一个进程SystemServer进程后退居幕后,通过Socket静等创建进程的呼唤。
SystemServer进程是Android系统的核心之一,大部分Android提供的服务都在该进程中,SystemServer中运行的进程公共有六十多种,主要包括:ActivityManagerService(AMS),WindowManagerService(WMS),PackagManagerService(PMS)等;这些系统服务都是以一个线程的方式存在Systemserver进程中。
开机 -> init进程 -> Zygote进程 -> SystemServer进程 ->(AMS)/ (WMS)/(PMS)...
点击桌面app图标
1.手机的桌面其实也是一个app,叫lancher,所以点击app图标就等于在activity中点击了一个button。
2.如果对应的应用进程还没有创建则会通过Binder IPC通知到AMS创建应用进程
3.AMS会发送消息通知Zygote创建应用进程
4.Zygote会创建应用,给他分配一个JVM
5.java程序要启动,就得启动一个main()函数,而这个main()函数就是ActivityThread.main()
点击桌面图标 (Binder IPC)-> AMS -> Zygote -> JVM -> ActivityThread.main()
补充:为什么要给每个应用分配一个jvm,因为这样每个app都是独立的,一个崩了不会影响到另一个,好管理,隔离等。
app启动 - ActivityThread.main()
看源码可知,main里面就是Looper.prepareMainLooper();、 Looper.loop();
就是创建Looper,然后去启动它。
所以Android中的所有代码都是跑在Handler上的,包括系统的,自己写的
Handler的结构
Handler的结构图大概是这样的。
每一个Thread里都有一个ThreadLocalMap内部的变量,
ThreadLocal里存了一个Looper,
Looper里有一个唯一的MessageQueue,
MessageQueue是一个单链表实现的优先级队列,里面存了Message链表
Handler的原理
像一个传送带送货物。
handler.sendMessage(message);不断的往传送带上放message
只要给Looper通上电(Looper.loop),传送带就启动了
在传送带的末端,handler.dispatchMessage()不断的取message
Handler源码解析
Handler的使用
发送消息:在线程中handler.sendMessage(message);
接收消息:new Handler,然后在匿名内部类里,重写方法handleMessage,接收message。
在接收的地方,new Handler,其实是要将Handler给绑定到Looper上的,在主线程中能直接用是因为ActivityThread.main()里给我们做了。如果要子线程中new Handler接收消息,那么需要在子线程中
Looper.prepare()、 Looper.loop();
从sendMessage发送消息开始
现在已知情况:
发送消息:handler -> sendMessage() ->....
接收消息: ... -> handler.handleMessage()
- 发送消息的方法有很多,sendMessage/post等等,但是最终都会走到sendMessageAtTime
- handler.enqueueMessage又会调到messasgeQueue.enqueueMessage。
- 重点!!!messasgeQueue.enqueueMessage这个方式是往消息队列里放消息,也就是真正发送消息的地方
便于理解:
消息队列 - 单链表实现的优先级队列
messasgeQueue数据结构: 单链表实现的优先级队列
消息队列是这样的结构:
MessageQueue里存了一个Message
Message里有一个next是Message类型的
所以总结:
MessageQueue里有一个Message(mMessages),而mMessages中的next里存有一个Message1,
Message1里有一个next,next里存有一个Message2...
所以这就是(单链表实现的队列):
Messge-》next-》Message -》Next(Message)...
优先级
所谓的优先级其实是Message执行的时间,每个Message里都存有Message应该执行的时间when。
创建Message的时候可以设置执行时间,然后插入队列的时候,会根据执行时间,按顺序插入到消息队列里。
这里的代码就是之前的for(;;)
如果下个节点为空(到尾巴了) 或者 下个节点的时间>我的时间,break;(将msg插入到这个位置)
接收消息handleMessage
现在已知情况:
发送消息:handler -> sendMessage() -> messasgeQueue.enqueueMessage
接收消息: ... -> handler.handleMessage()
- 接收消息的起点在Looper.loop,给传送带通上电,无限循环取消息
- 重点!!!messasgeQueue.next()对应前面messasgeQueue.enqueueMessage存消息,这个是在消息队列里取出消息
补充:
所以要退出looper死循环,停止handler的唯一方法就是msg=null
而让msg=null的唯一方法是mQuitting=true
而mQuitting=true的唯一方法是messasgeQueue.quit
- handler.dispatchMessage()发送msg
在Looper.loop里发送消息的代码msg.target.dispatchMessage(msg);
而这个msg.target就是Handler
在 handler.sendMessage(message);的时候最终会调到handler.enqueueMessage,而在这里
msg.target = this;将自己给赋值给msg,所以每一个message都有一个handler的引用,这样又会引发内存泄露,后面讲
- dispatchMessage里都会调到handleMessage,也就是自己写的实现
总结调用流程
发送消息:handler -> sendMessage() -> messasgeQueue.enqueueMessage
接收消息: looper.loop()-> messasgeQueue.next()-> handler.dispatchMessage()- -> handler.handleMessage()
各部分详解
Message
Message可以看成是一块内存。handler机制通过对这块内存的共享实现了线程间的通信。
Message是一个单链表实现的优先级队列,Message里存了一个Message,里存了一个Message,这样实现的链表。而优先级是由Message的先后顺序决定的,而决定其先后顺序的是Message里存的一个long when代表的是Message执行的时刻,插入的时候根据when来前后插入。
Message的创建
- 直接new,但是无限new的话会让内存爆炸
Message.obtain()建议使用
缓存的池子:msg缓存的池子是当msg执行完事需要回收的时候,将数据清空然后放到缓存池子里,没有去销毁。
这种操作在Android中其实很常见,比如RecyclerView四级缓存的缓存池,也是清空itemView然后复用,不是销毁在重新new。Glide等等都是这样用的。
好处:JVM知识:jvm使用的是标记回收算法,如果一直创建、回收,这样会使得内存碎片化很严重,完整的长的内存越来越少,造成频繁GC、内存抖动。
Message的销毁
这里的缓存池跟Message next是一样的操作,只不过池子里的Message是空的。
MessageQueue
虽然说handler机制是永动机,但是其实他还是会有阻塞休眠的时候。
比如当.next取不到消息的时候,或者执行时间还没到的时候
quit
停下永动机的唯一方法,上面说了
同步屏障
handler机制不仅给我们用的,整个系统都用。而Message链表都是在那排队的,当遇到十万火急的事件需要处理怎么办,就得插队执行,就像救护车在路上,别的车都得让出位置。
这就是同步屏障。
具体代码:
设置同步屏障
系统一些紧急的事件会先跑。比如刷新UI,60赫兹的屏幕刷新频率,1秒钟刷新60次,也就是1/60=16.67ms。
Looper
初始化
为了保证一个线程只能有一个Looper,所以Looper不能随便new,所以其构造函数是私有的
只能通过Looper.prepare才能初始化Looper
因为MessageQueue是final的,只能赋值一次
ThreadLocal
可以看到Looper其实是存在ThreadLocal里的,判断有没有是sThreadLocal.get()
而这个sThreadLocal是 static final的,也就是每个线程有且仅有一个,并且是赋值以后就不能更改的。
ThreadLocal的详解请看之前的文章:juejin.cn/post/695650…
简单的说ThreadLocal里有一个ThreadLocalMap
ThreadLocalMap里有一个内部类Entry
Entry数组存了<key,value>的键值对
而这个key就是ThreadLocal,value就是存的值,也就是looper
每个Thread里都有一个全局变量ThreadLocalMap,线程的用来存上下文变量和一些别的值(比如现在的looper)
所以唯一的sThreadLocal造就了唯一的Looper。(prepare里sThreadLocal.get() != null就抛异常,空才sThreadLocal.set(new Looper(quitAllowed)))。唯一的Looper造就了唯一的MessageQueue(在私有构造函数Looper里new,MessageQueue的final)多方面原因造就了 一个线程只有一个Looper、MessageQueue
面试题
1:一个线程有几个 Handler?
无数个,随便new,但是Handler机制只有一个
2:一个线程有几个 Looper?如何保证?
一个looper,看上面looper初始化
3:Handler 内存泄漏原因? 为什么其他的内部类没有说过有这个问题?
Handler new的时候标黄
翻译
因为这样直接new出来的是匿名内部类,而内部类如果要访问外部的对象、方法。会自动带上this.
而由于Handler与Activity生命周期不一样,所以会导致内存泄露。
生命周期不一样的原因:message可以设置执行的时间的,如设置20分钟后执行。
而在handler.sendMessage();的时候会将handler给msg持有
也就是message 持有-> handler 持有-> Activity.this 所以会内存泄露
解决:
1.将handler写成静态内部类的形式,因为static内部类不会持有外部引用;
2.使用弱引用包裹外部对象;比如要更新textView.settext();用WeakReference.activity.tv.setText();
4:为何主线程可以new Handler?如果想要在子线程中new Handler 要做些什么准备?
放在
Looper.prepare(); -> threadLocal.set(new Looper)
获取Looper, Looper.myLooper(); -> threadLocal.get()
将Looper与Handler绑定 new Handler(Looper);
Looper.loop(); -> 启动无限循环
中间new,看上面。最好是直接用 HandlerThread
5:子线程中维护的Looper,消息队列无消息的时候的处理方案是什么?有什么用?
必须MessageQueue.quit,见上文
6:既然可以存在多个 Handler 往 MessageQueue 中添加数据(发消息时各个 Handler 可能处于不同线程),那它内部是如何确保线程安全的?
锁,MessageQueue里添取消息都加锁
7:我们使用 Message 时应该如何创建它?
Message.obtain()见上文
8:Looper死循环为什么不会导致应用ANR
这其实是一个无关的话题。
ANR指应用无响应,比如input事件在5S没处理,或者前台Broadcast10s未处理完成等都会导致ANR。
子线程的Handler死循环会导致CPU空跑,也会卡死。但不会ANR
甚至连ANR闪退的消息都是由Handler传递的。