Handler 原理
Handler
被设计出来的原因?有什么用?提供一种方便线程间通信的解决方案
Handler
的基本原理外部通过
Handler
往MessageQueue
里插入任务Looper
在做死循环,一直从MessageQueue
中获取任务
如果此时任务为空或当前没有需要执行的任务,则先判断IdleHandler
,IdleHandler
为空则阻塞子线程中怎么使用
Handler
,为什么我们在主线程不用做准备操作Looper.prepare()
创建Looper
并添加到ThreadLocal
中Looper.loop()
开始死循环从MessageQueue
获取消息
程序起来时在ActivityThread.main()
方法以经初始化过,所以直接使用就行为什么建议使用
Message.obtain()
来创建Message
实例对
Message
有效复用,可以有效的解决频繁创建Message
实例问题MessageQueue
获取消息是怎么等待通过
epoll
进行等待和唤醒
在next
方法汇中,如果当前没有需要执行的任务,则调用nativePollOnce
进行阻塞nextPollTimeoutMillis
三种情况:等于0,不阻塞立即返回
大于0,阻塞等待的时间
等于-1,无消息时会一直阻塞
epoll
机制是一种IO多路复用机制,具体逻辑是一个进程可以监视多个描述符,当某个描述符就绪(一般是读就绪或写就绪),能够通知程序进行相应的读写操作,这个读写操作是阻塞的,在Android
中,会创建一个Linux
管道来处理阻塞和唤醒- 当消息队列为空,管道的读端等待管道中有新内容可读,就会通过
epoll
机制进入阻塞状态 - 当有消息要处理,就会通过管道的写端写入内容,唤醒主线程
为什么不用
wait
而用epoll
在 Android 2.2 及以前,使用的就是 wait/notify 进行等待,之后为了同时处理
native
侧的消息,改用了pipe/epoll
机制关于 select 、poll 、epoll,参考:https://mp.weixin.qq.com/s/YdIdoZ_yusVWza1PU7lWaw
【备注】在兼容
native
侧消息时,早期使用的是select
,后面才改成epoll
,参考commit:https://android.googlesource.com/platform/frameworks/base/+/46b9ac0ae2162309774a7478cd9d4e578747bfc2%5E%21/#F16线程和
Handler
Looper
MessageQueue
的关系一个线程对应一个
MessageQueue
、一个Looper
、多个Handler
多个线程给
MessageQueue
发消息,如何保证线程安全在
enqueueMessage
里进行了加锁Handler
消息延迟是怎么处理的在插入任务时会将时间转换成距离开机时间的毫秒数,然后在
MessageQueue
中按时间顺序插入在调用
MessageQueue.next()
中获取任务,如果还没有到达执行时间,先处理IdleHandler
,处理完后再nativePollOnce
阻塞View.post
和Handler.post
的区别View.post
时会判断有没有AttachInfo
,如果有则直接调用里面的Handler
处理
如果没有则等待dispatchAttachedToWindow
,然后在通过Handler
进行处理dispatchAttachedToWindow
函数是在ViewRootImpl
的performTraversals
中调用,
这也是为什么View.Post
能够获取到View
的宽高的原因,因为已经执行过performTraversals
了Handler
导致的内存泄漏匿名内部类持有外部类,Handler有延迟消息造成无法回收,这样就造成了外部类无法回收
方法一:静态内部类 + 弱引用
方法二:将该Handler的消息全部移除非
UI
线程真的不能操作View
吗大部分情况是不可以的,因为在
ViewRootImpl
中的requestLayout
会判断线程是否为创建线程,该创建线程默认就是UI
线程
其它情况如:ViewRootImpl
未创建修改View,子线程创建ViewRootImpl
,或者修改View
不会触发requestLayout
的场景Looper
在主线程死循环,为什么不会ANR
ANR
的原因是有任务在进行耗时操作,让本该执行的任务无法在合适的时间内执行
在于任务本身,而非死循环同步屏障和异步消息是怎么实现的
- 同步消息:就是普通消息
- 异步消息:通过
setAsynchronous(true)
设置的消息 - 同步屏障消息:通过
postSyncBarrier
方法添加的消息,特点是target
为空,即没有对应的handler
三者关系
- 正常情况下,同步消息和异步消息都是根据
when
来取消息,处理消息 - 当遇到同步屏障消息时,就开始从消息队列里找异步消息,找到了再根据时间决定阻塞还是返回消息
同步屏障和异步消息存在的意义就是用于处理 “加急消息”
在
ViewRootImpl.scheduleTraversals()
中有具体应用Looper
安全退出和非安全退出有什么区别- 非安全退出:
removeAllMessagesLocked
,会直接清空所有未执行的消息 - 安全退出:
removeAllFutureMessagesLocked
,只清空延迟消息,非延迟消息继续处理
- 非安全退出:
Message
如何实现插队sendMessageAtTime(msg, 0)
,时间传0即可- 如果不一定要插入队头,可以通过异步消息,也能实现一定的插队效果
msg.setAsynchronous(true)
利用
HanIder
机制设计一个不崩溃的App
第一步:设置自己的异常捕捉处理者
1
Thread.setDefaultUncaughtExceptionHandler()
此时子线程出现异常已经不会崩溃了
但是
UI
线程触发异常会让Looper.loop
死循环退出,接下来的消息无法处理同样会让app卡死第二步:对主线程异常进行捕获
1
2
3
4
5
6
7
8Handler(Looper.getMainLooper()).post {
while (true) {
try {
Looper.loop()
} catch (e: Throwable) {
}
}
}这样,
UI
线程触发异常会被捕捉并重新Looper.loop()
,这样后续的消息又能继续处理,从而解决app崩溃异常【注意:】经过上面两步确认解决了app崩溃的问题,但是如果在Activity的生命周期内触发异常,如
onCreate
中,则会出现黑屏白屏的情况,原因是生命周期内抛出异常,会导致界面无法完成,Activity
无法正确启动,就会出现黑屏白屏的情况。由于Activity
的生命周期都是通过主线程的Handler
进行处理的,可以通过反射ActivityThread
中的Handler
进行Activity
生命周期异常捕获,然后进行finishActivity
或者杀死进程的操作【说明】第二步中可以理解为外部
loop
一直在等待post
里的loop
结束【参考二:】https://github.com/android-notes/Cockroach/blob/master/%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90.md