一、Handler是什么

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


二、Handler有什么用

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


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

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

Android要求我们在主线程中更新UI,是要求,建议,不是规定,硬要在子线程中更新UI,也是可以的!
比如,在一个子线程中,创建一个对话框:

而我们平时在子线程中更新UI报错是这样的:

子线程更新UI也行,但是只能更新自己创建的View!Android的UI更新被设计成了单线程。

再说一个网上很常见的主线程更新UI的例子:

上面这段代码直接在子线程中更新了UI,却没有报错:

但如果在子线程中加句线程休眠模拟耗时操作的话:

程序就崩溃了,报错如下:

前面说了Android的UI更新被设计成单线程,这里妥妥滴会报错,但却发生在延迟执行后?
原因是:ViewRootImp在onCreate()时还没创建;在onResume()时,即ActivityThread的 handleResumeActivity()执行后才创建,调用requestLayout(),走到checkThread()时就报错了。


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


上述代码运行直接闪退,日志如下:

在 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、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前,Android系统已为我们做了一系列的工作,其中就包括了创建「Looper」和「MessageQueue」对象。

ActivityThread 的 main函数是APP进程的入口,定位到 ActivityThread → main函数

定位到:Looper → prepareMainLooper函数

定位到:Looper → prepare函数

定位到:Looper → Looper构造函数

另外这里的 quitAllowed 变量,直译「退出允许」

用来防止开发者手动终止消息队列,停止Looper循环。

创建了Looper与MessageQueue对象,接着调用Looper.loop()开启轮询。
定位到:Looper → loop函数

接着有几个问题,先是这个 myLooper() 函数:

这里的 ThreadLocal → 线程局部变量 → JDK提供的用于解决线程安全的工具类。
作用:为每个线程提供一个独立的变量副本 → 以解决并发访问的冲突问题。

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

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

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

既然说Handler用于子线程和主线程通信,试试在主线程中给子线程的Handler发送信息,修改一波代码:

运行,直接报错:

原因:多线程并发的问题,当主线程执行到sendEnptyMessage时,子线程的Handler还没有创建。
一个简单的解决方法是:主线程延时给子线程发消息,修改后的代码示例如下:

其实Android已经给我们封装好了一个轻量级的异步类「HandlerThread」

HandlerThread
HandlerThread = 继承Thread + 封装Looper

使用方法很简单,改造下我们上面的代码:

用法挺简单的,源码其实也很简单,跟一跟:



剩下一个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

翻译下注释:当线程将要进入堵塞,以等待更多消息时,会回调这个接口;
简单点说:当MessageQueue中无可处理的Message时回调;
作用:UI线程处理完所有View事务后,回调一些额外的操作,且不会堵塞主进程;

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

使用方法也很简单,代码示例如下:

一般使用场景:绘制完成回调


六、一些其他问题

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

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

  • 当系统受到因用户操作产生的通知时,会通过 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 函数来开启同步屏障:

这一步简单的说就是:往消息队列合适的位置插入了同步屏障类型的Message (target属性为null),接着,在 MessageQueue 执行到 next() 函数时:

遇到target为null的Message,说明是同步屏障,循环遍历找出一条异步消息,然后处理。
在同步屏障没移除前,只会处理异步消息,处理完所有的异步消息后,就会处于堵塞。
如果想恢复处理同步消息,需要调用 removeSyncBarrier() 移除同步屏障:

在API 28的版本中,postSyncBarrier()已被标注hide,但依旧可在系统源码中找到相关应用,比如:
为了更快地响应UI刷新事件,在ViewRootImpl的scheduleTraversals函数中就用到了同步屏障:

更多相关文章

  1. android中Invalidate和postInvalidate的区别
  2. 浅析Android线程模型
  3. Android——消息机制
  4. Android推送通知指南
  5. Android开发规范,性能优化
  6. Android应用程序消息处理机制(Looper、Handler)分析
  7. Android(安卓)计时器Timer用法
  8. Android应用AsyncTask处理机制详解及源码分析
  9. Android的线程和线程池

随机推荐

  1. android 获取.thumbnail文件下的小图标
  2. android 根据图片的URI获取对应的图片
  3. Android中启动另外一个Activity
  4. 2010.11.16———android Camera 拍照的
  5. 2011.07.05(3)——— android PopupWindo
  6. Android 4.0 开发环境离线安装(Linux)
  7. Android 读取手机音乐,视频,图片
  8. android 消息提醒(Toast,Notification)
  9. Xamarin.Android中使用android:onClick="
  10. Android中ActivityManager学习笔记(2)-Ru