一、Handler是什么

Android定义的一套 子线程与主线程间通讯的消息传递机制 。


二、Handler有什么用

把子线程中的UI更新信息,传递给主线程(UI线程),以此完成UI更新操作。


三、为什么要用Handler,不用行不行

不行,Handler是android在设计之初就封装了的一套消息创建、传递、处理机制。

Android要求我们在主线程中更新UI,是要求,建议,不是规定,硬要在子线程中更新UI,也是可以的!
比如,在一个子线程中,创建一个对话框:
Handler问题总结_第1张图片
而我们平时在子线程中更新UI报错是这样的:
在这里插入图片描述
子线程更新UI也行,但是只能更新自己创建的View!Android的UI更新被设计成了单线程。

再说一个网上很常见的主线程更新UI的例子:
Handler问题总结_第2张图片
上面这段代码直接在子线程中更新了UI,却没有报错:
Handler问题总结_第3张图片
但如果在子线程中加句线程休眠模拟耗时操作的话:
Handler问题总结_第4张图片
程序就崩溃了,报错如下:
Handler问题总结_第5张图片
前面说了Android的UI更新被设计成单线程,这里妥妥滴会报错,但却发生在延迟执行后?
原因是:ViewRootImp在onCreate()时还没创建;在onResume()时,即ActivityThread的 handleResumeActivity()执行后才创建,调用requestLayout(),走到checkThread()时就报错了。


四、真的不能在主线程中做网络操作吗?

Handler问题总结_第6张图片
上述代码运行直接闪退,日志如下:
Handler问题总结_第7张图片
在 onCreate() 的 setContentView() 后插入下面两句代码:
在这里插入图片描述
运行下看看:
Handler问题总结_第8张图片
StrictMode(严苟模式)
Android 2.3 引入,用于检测两大问题:ThreadPolicy(线程策略) 和 VmPolicy(VM策略)
Handler问题总结_第9张图片
把严苟模式的网络检测关了,就可以在主线程中执行网络操作了,不过一般是不建议这样做的:
在主线程中进行耗时操作,可能会导致程序无响应,即 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、ContentProviderstatic 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 进程。

五、Handler底层原理解析

Handler问题总结_第10张图片
在我们使用Handler前,Android系统已为我们做了一系列的工作,其中就包括了创建「Looper」和「MessageQueue」对象。

ActivityThread 的 main函数是APP进程的入口,定位到 ActivityThread → main函数
Handler问题总结_第11张图片
定位到:Looper → prepareMainLooper函数
在这里插入图片描述
定位到:Looper → prepare函数
在这里插入图片描述
定位到:Looper → Looper构造函数
Handler问题总结_第12张图片
另外这里的 quitAllowed 变量,直译「退出允许」
Handler问题总结_第13张图片
用来防止开发者手动终止消息队列,停止Looper循环。

创建了Looper与MessageQueue对象,接着调用Looper.loop()开启轮询。
定位到:Looper → loop函数
Handler问题总结_第14张图片
接着有几个问题,先是这个 myLooper() 函数:
Handler问题总结_第15张图片在这里插入图片描述
这里的 ThreadLocal → 线程局部变量 → JDK提供的用于解决线程安全的工具类。
作用:为每个线程提供一个独立的变量副本 → 以解决并发访问的冲突问题。

每个Thread内部都维护了一个ThreadLocalMap,这个map的key是ThreadLocal,value是set的那个值。get的时候,都是从自己的变量中取值,所以不存在线程安全问题。

主线程和子线程的Looper对象实例相互隔离的!!!意味着:主线程和子线程Looper不是同一个!!!

为什么子线程中不能直接 new Handler(),而主线程可以?
主线程与子线程不共享同一个Looper实例,主线程的Looper在启动时就通过prepareMainLooper() 完成了初始化,而子线程还需要调用 Looper.prepare()和 Looper.loop()开启轮询,否则会报错。

既然说Handler用于子线程和主线程通信,试试在主线程中给子线程的Handler发送信息,修改一波代码:
Handler问题总结_第16张图片
运行,直接报错:
Handler问题总结_第17张图片
原因:多线程并发的问题,当主线程执行到sendEnptyMessage时,子线程的Handler还没有创建。
一个简单的解决方法是:主线程延时给子线程发消息,修改后的代码示例如下:
Handler问题总结_第18张图片
其实Android已经给我们封装好了一个轻量级的异步类「HandlerThread」

HandlerThread
HandlerThread = 继承Thread + 封装Looper

使用方法很简单,改造下我们上面的代码:
Handler问题总结_第19张图片
用法挺简单的,源码其实也很简单,跟一跟:
Handler问题总结_第20张图片
Handler问题总结_第21张图片
Handler问题总结_第22张图片
剩下一个quit()和quitSafely()停止线程,就不用说了,所以HandlerThread的核心原理就是:

  • 继承Thread,getLooper()加锁死循环wait()堵塞;
  • run()加锁等待Looper对象创建成功,notifyAll()唤醒;
  • 唤醒后,getLooper返回由run()中生成的Looper对象;

Java中所有类的父类是 Object 类,里面提供了wait、notify、notifyAll三个方法;
Kotlin 中所有类的父类是 Any 类,里面可没有上述三个方法!!!
所以你不能在kotlin类中直接调用,但你可以创建一个java.lang.Object的实例作为lock,去调用相关的方法。

private val lock = java.lang.Object()fun produce() = synchronized(lock) {    while(items>=maxItems) {         lock.wait()    }    Thread.sleep(rand.nextInt(100).toLong())    items++    println("Produced, count is$items:${Thread.currentThread()}")    lock.notifyAll()}fun consume() = synchronized(lock) {    while(items<=0) {        lock.wait()    }    Thread.sleep(rand.nextInt(100).toLong())    items--    println("Consumed, count is$items:${Thread.currentThread()}")    lock.notifyAll()}

IdleHandler是什么?
在 MessageQueue 类中有一个 static 的接口 IdleHanlder
Handler问题总结_第23张图片
翻译下注释:当线程将要进入堵塞,以等待更多消息时,会回调这个接口;
简单点说:当MessageQueue中无可处理的Message时回调;
作用:UI线程处理完所有View事务后,回调一些额外的操作,且不会堵塞主进程;

接口中只有一个 queueIdle() 函数,线程进入堵塞时执行的额外操作可以写这里,
返回值是true的话,执行完此方法后还会保留这个IdleHandler,否则删除。

使用方法也很简单,代码示例如下:
Handler问题总结_第24张图片
一般使用场景:绘制完成回调


六、一些其他问题

Looper在主线程中死循环,为啥不会ANR?
Looper通过queue.next()获取消息队列消息,当队列为空,会堵塞,
此时主线程也堵塞在这里,好处是:main函数无法退出,APP不会一启动就结束!

你可能会问:主线程都堵住了,怎么响应用户操作和回调Activity声明周期相关的方法?
application启动时,可不止一个main线程,还有其他两个Binder线程:ApplicationThread 和 ActivityManagerProxy,用来和系统进程进行通信操作,接收系统进程发送的通知。
Handler问题总结_第25张图片

  • 当系统受到因用户操作产生的通知时,会通过 Binder 方式跨进程通知 ApplicationThread;
  • 它通过Handler机制,往 ActivityThread 的 MessageQueue 中插入消息,唤醒了主线程;
  • queue.next() 能拿到消息了,然后 dispatchMessage 完成事件分发;
    PS:ActivityThread 中的内部类H中有具体实现

死循环不会ANR,但是 dispatchMessage 中又可能会ANR哦!如果你在此执行一些耗时操作,导致这个消息一直没处理完,后面又接收到了很多消息,堆积太多,从而引起ANR异常!!!

同步屏障机制
我们知道用Handler发送的Message后,MessageQueue的enqueueMessage()按照 时间戳升序 将消息插入到队列中,而Looper则按照顺序,每次取出一枚Message进行分发,一个处理完到下一个。

这时候,问题来了:有一个紧急的Message需要优先处理怎么破?
你可能或说直接sendMessage()不就可以了,不用等待立马执行,看上去说得过去,不过可能有这样一个情况:一个Message分发给Handler后,执行了耗时操作,后面一堆本该到点执行的Message在那里等着,这个时候你sendMessage(),还是得排在这堆Message后,等他们执行完,再到你!

Handler中加入了「同步屏障」这种机制,来实现「异步消息优先执行」的功能。
添加一个异步消息的方法很简单:

  • 1、Handler构造方法中传入async参数,设置为true,使用此Handler添加的Message都是异步的;
  • 2、创建Message对象时,直接调用setAsynchronous(true)

一般情况下:同步消息和异步消息没太大差别,但仅限于开启同步屏障之前。可以通过 MessageQueue 的 postSyncBarrier 函数来开启同步屏障:
Handler问题总结_第26张图片
这一步简单的说就是:往消息队列合适的位置插入了同步屏障类型的Message (target属性为null),接着,在 MessageQueue 执行到 next() 函数时:
Handler问题总结_第27张图片
遇到target为null的Message,说明是同步屏障,循环遍历找出一条异步消息,然后处理。
在同步屏障没移除前,只会处理异步消息,处理完所有的异步消息后,就会处于堵塞。
如果想恢复处理同步消息,需要调用 removeSyncBarrier() 移除同步屏障:
Handler问题总结_第28张图片
在API 28的版本中,postSyncBarrier()已被标注hide,但依旧可在系统源码中找到相关应用,比如:
为了更快地响应UI刷新事件,在ViewRootImpl的scheduleTraversals函数中就用到了同步屏障:
Handler问题总结_第29张图片

更多相关文章

  1. Android 框架学习4:一次读懂热门图片框架 Picasso 源码及流程
  2. 关于android中网络图片下载中oom解决开源框架Afinal的探究
  3. Android App 性能优化之图片优化
  4. 常用组件:android的图片组件ImageView
  5. Android移动图片
  6. Android Activity的setTitle,AlertDialog,Toast操作是否都必须在
  7. Android的进程和线程

随机推荐

  1. android进程等级以及startService与bouds
  2. Android(安卓)GMS module测试方法
  3. Android(安卓)studio中获取数字签名(SAH1)
  4. 【Android(安卓)开发教程】Notification
  5. 【UI交互效果】android UI效果一: coverF
  6. Android数据篇(二)
  7. 初探Android平台上的定位服务(GPS)
  8. Android开发:TabActivity中onKeyDown无法
  9. Android程序框架
  10. android 监听网络状态