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线程和
HandlerLooperMessageQueue的关系一个线程对应一个
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在主线程死循环,为什么不会ANRANR的原因是有任务在进行耗时操作,让本该执行的任务无法在合适的时间内执行
在于任务本身,而非死循环同步屏障和异步消息是怎么实现的
- 同步消息:就是普通消息
- 异步消息:通过
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