罗列下本文提及的知识点,按需阅读即可:
- 1、Handler问题三连:是什么?有什么用?为什么要用,不用行不行?
- 2、Android UI更新机制(GUI) 为何设计成了单线程的?
- 3、真的只能在主(UI)线程中更新UI吗?
- 4、真的不能在主(UI)线程中执行网络操作吗?
- 5、Handler怎么用?
- 6、为什么建议使用Message.obtain()来创建Message实例?
- 7、为什么子线程中不可以直接new Handler()而主线程中可以?
- 8、主线程给子线程的Handler发送消息怎么写?
- 9、HandlerThread实现的核心原理?
- 10、当你用Handler发送一个Message,发生了什么?
- 11、Looper是怎么拣队列里的消息的?
- 12、分发给Handler的消息是怎么处理的?
- 13、IdleHandler是什么?
- 14、Looper在主线程中死循环,为啥不会ANR?
- 15、Handler泄露的原因及正确写法
- 16、Handler中的同步屏障机制
- 17、Android 11 Handler相关变更
0x1、Handler问题三连
1.Handler是什么
答:Android Framework 架构中的一个 基础组件,用于 子线程与主线程间的通讯,实现了一种 非堵塞的消息传递机制。
2.Handler有什么用
答:把子线程中的 UI更新信息传递 给主线程(UI线程),以此完成UI更新操作。
3.为什么要用Handler,不用行不行
答:不行,Handler是android在设计之初就封装的 一套消息创建、传递、处理机制。
Android要求:在主线程(UI线程)中更新UI,注意,是要求,并不是规定,你不听,硬是要:在子线程中更新UI,也是可以的,比如在子线程中创建Dialog:
运行后:
没有报错,对话框正常弹出,而我们平时在 子线程中更新UI 的报错信息是这样的:
- 异常翻译:只有创建视图层次结构的原始线程才能触摸其视图;
- 引起原因:在子线程中直接更新主线程创建的UI;
- 也就是说:子线程更新UI也行,但是只能更新子线程创建的View;
- 换句话说:Android的UI更新(GUI)被设计成了单线程;
你可能会问,为啥不设计成多线程?
答:多个线程同时对同一个UI控件进行更新,容易发生不可控的错误!
那么怎么解决这种线程安全问题?
答:最简单的处理方式——加锁,不是加一个,是每层都要加锁(用户代码→GUI顶层→GUI底层...)但这样也意味着更多的 耗时,从而导致UI更新效率降低,界面卡顿等。
而如果每一层共用一把锁的话,其实就是 单线程,所以,最后的结论是:
Android没有采用「线程锁」,而是采用「单线程消息队列机制」,实现了一个「伪锁」
这个疑问解决了,再说一个网上很常见的主线程更新UI的例子:
上面这段代码 直接在子线程中更新了UI,却没有报错:
打脸?莫慌,在子线程代码块中加一句休眠模拟耗时操作:
程序就崩溃了,报错日志如下:
前面说了 Android的UI更新被设计成单线程,但为何在添加休眠后才报错,限于篇幅,不跟源码,直接说原因:
ViewRootImp
在onCreate()
调用时还没创建;
- 在
onResume()
时,即 **ActivityThread.handleResumeActivity()
**执行后才创建;
- 调用
View.requestLayout()
,最终调到ViewRootImpl.requestLayout()
,走到**checkThread()
**报错;
可以打个日志简单的验证下:
加上休眠:
行吧,以后去面试别人问「子线程是不是一定不可以更新UI」别傻乎乎地点头说是了。
4.引生的另一个问题
说到「只能在主线程中更新UI」我又想到另一个问题「不能在主线程中进行网络操作」
上述代码运行直接闪退,日志如下:
NetworkOnMainThreadException:网络请求在主线程进行异常。
em... 真的不能在主线程中做网络操作吗?
在 onCreate() 的 setContentView() 后插入下面两句代码:
运行下看看:
这...又打脸?先说下 StrictMode(严苟模式)
Android 2.3 引入,用于检测两大问题:ThreadPolicy(线程策略) 和 VmPolicy(VM策略)
相关方法如下:
把严苟模式的网络检测关了,就可以 在主线程中执行网络操作了,不过一般是不建议这样做的:
在主线程中进行耗时操作,可能会导致程序无响应,即 ANR (Application Not Responding)。
至于常见的ANR时间,可以在对应的源码中找到:
// ActiveServices.java → Service服务 static final int SERVICE_TIMEOUT = 20*1000; // 前台 static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10; // 后台 // ActivityManagerService.java → Broadcast广播、InputDispatching、ContentProvider static final int BROADCAST_FG_TIMEOUT = 10*1000; // 前台 static final int BROADCAST_BG_TIMEOUT = 60*1000; // 后台 static final int KEY_DISPATCHING_TIMEOUT = 5*1000; // 关键调度 static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10*1000; // 内容提供者
时间统计区间:
- 起点:
System_Server
进程调用startProcessLocked()
后调用AMS.attachApplicationLocked()
- 终点:
Provider
进程的 **installProvider()
**及publishContentProviders()
调用到AMS.publishContentProviders()
超过这个时间,系统就会杀掉 Provider 进程。