换个姿势,带着问题看Handler(中)

简介: Handler,老生常谈,网上关于它的文章可谓是 "泛滥成灾",而实际开发中,我们却很少手写Handler,毕竟 RxAndroid链式调用 和 Kotlin协程同步方式写异步代码 还是挺香的。但对于我这种好刨根问底之人来说,得自己过一遍源码才踏实,而且我发现 带着问题 看源码,思考理解本质,印象更深,收获更多,遂有此文。

0x2、Handler怎么用


1.sendMessage() + handleMessage()


代码示例如下


网络异常,图片无法展示
|


黄色部分会有如下警告


网络异常,图片无法展示
|


Handler不是静态类可能引起**内存泄露**,原因以及正确写法等下再讲。


另外,建议调用 Message.obtain() 函数来获取一个Message实例,为啥?点进源码:


网络异常,图片无法展示
|


从源码,可以看到obtain()的逻辑:加锁判断Message池是否为空


  • ① 不为空,取一枚Message对象,正在使用标记置为0,池容量-1,返回此对象;


  • ② 为空,新建一个Message对象返回;


此处复用Message,可避免**避免重复创建实例对象,达到节约内存的目的,而且不难看出Message实际上是无头结点的单链表**。


网络异常,图片无法展示
|


上述获取消息池的逻辑:


网络异常,图片无法展示
|


定位到下述代码,还可以知道:池的容量为50


网络异常,图片无法展示
|


然后问题来了,Message信息什么时候加到池中?


答:当Message 被Looper分发完后,会调用 recycleUnchecked()函数,回收没有在使用的Message对象。


网络异常,图片无法展示
|


标志设置为**FLAG_IN_USE**,表示正在使用,相关属性重置,加锁,判断消息池是否满,未满,单链表头插法 将消息插入到表头。


2.post(runnable)


代码示例如下


网络异常,图片无法展示
|


跟下post():


网络异常,图片无法展示
|


实际上调用了 sendMessageDelayed() 发送消息,只不过延迟秒数为0,那Runnable是怎么变成Message的呢?跟下getPostMessage()


网络异常,图片无法展示
|


噢,获取一个新的Message示例后,把 Runnable 变量的值赋值给 callback属性


网络异常,图片无法展示
|


3.附:其他两个种在子线程中更新UI的方法


activity.runOnUiThread()


网络异常,图片无法展示
|


view.post() 与 view.postDelay()


网络异常,图片无法展示
|


0x3、Handler底层原理解析


终于来到稍微有点技术含量的环节,在观摩源码了解原理前,先说下几个涉及到的类。


1.涉及到的几个类


网络异常,图片无法展示
|


2.前戏


在我们使用Handler前,Android系统已为我们做了一系列的工作,其中就包括了

创建「Looper」和「MessageQueue」对象


上图中有写:ActivityThreadmain函数是APP进程的入口,定位到 ActivityThread → main函数


网络异常,图片无法展示
|


定位到:Looper → prepareMainLooper函数


网络异常,图片无法展示
|


定位到:Looper → prepare函数


网络异常,图片无法展示
|


定位到:Looper → Looper构造函数


网络异常,图片无法展示
|


另外这里的 mQuitAllowed 变量,直译「退出允许」,具体作用是?跟下 MessageQueue


网络异常,图片无法展示
|


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


3.消息队列的运行


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


网络异常,图片无法展示
|


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


网络异常,图片无法展示
|


网络异常,图片无法展示
|


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


每个Thread内部都维护了一个ThreadLocalMap,这个map的key是ThreadLocal,


value是set的那个值。get的时候,线程都是从自己的变量中取值,所以不存在线程安全问题。


主线程和子线程的Looper对象实例相互隔离的!!!


另外,线程为key也保证了每个线程只有一个Looper,而创建Looper对象时又会创建MessageQueue对象,所以间接保证每个线程最多只能有一个MessageQueue。


知道这个以后,有个问题就解惑了:


为什么子线程中不能直接 new Handler(),而主线程可以?


答:主线程与子线程不共享同一个Looper实例,主线程的Looper在启动时就通过 prepareMainLooper() 完成了初始化,而子线程还需要调用 Looper.prepare()  和 Looper.loop()开启轮询,否则会报错,不信,可以试试:


网络异常,图片无法展示
|


直接就奔溃了~


网络异常,图片无法展示
|


加上试试?


网络异常,图片无法展示
|


可以,没有报错,程序正常运行。


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


网络异常,图片无法展示
|


运行,直接报错:


网络异常,图片无法展示
|


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


网络异常,图片无法展示
|


运行结果如下:


网络异常,图片无法展示
|


可以,不过其实Android已经给我们封装好了一个轻量级的异步类 HandlerThread


4.HandlerThread


HandlerThread = 继承Thread + 封装Looper


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


网络异常,图片无法展示
|


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


网络异常,图片无法展示
|


网络异常,图片无法展示
|


网络异常,图片无法展示
|


剩下一个quit()和quitSafely()停止线程,就不用说了,所以HandlerThread的核心原理就是:


  • 继承Thread,getLooper()加锁死循环wait()堵塞线程;


  • run()加锁等待Looper对象创建成功,notifyAll()唤醒线程


  • 唤醒后,getLooper返回由run()中生成的Looper对象


是吧,HandlerThread的实现原理竟简单如斯,另外,顺带提个醒!!!


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()
}


相关文章
|
7月前
|
安全 Android开发 开发者
【Android开发小技巧】扔掉这坑人的 Handler
【Android开发小技巧】扔掉这坑人的 Handler
80 0
测试时,请求方法一定要写对,写错照样出问题,Method Not Allowed 删除接口写错,注意Controller层中UserMapper中的写法,视频往后看看就能看到解决问题的方法了
测试时,请求方法一定要写对,写错照样出问题,Method Not Allowed 删除接口写错,注意Controller层中UserMapper中的写法,视频往后看看就能看到解决问题的方法了
|
安全 Android开发 开发者
【Android开发小技巧】扔掉这坑人的 Handler
大家都知道 Handler 特别坑,使用不当会造成各种问题,使用 Kotlin Coroutines + Lifecycle 可以很好地替代 Handler。
846 0
|
消息中间件 Android开发
Handler源码解读——handler使用时的注意事项
工作中经常会遇到从子线程发送消息给主线程,让主线程更新UI的操作,常见的有handler.sendMessage(Message),和handler.post(runnable)和handler.postDelayed(runnable, milliseconds);一直在使用这些方法,却不知道他们的原理,今天就来解释一下他们的原理。
|
消息中间件 Android开发 Kotlin
换个姿势,带着问题看Handler(上)
Handler,老生常谈,网上关于它的文章可谓是 "泛滥成灾",而实际开发中,我们却很少手写Handler,毕竟 RxAndroid链式调用 和 Kotlin协程同步方式写异步代码 还是挺香的。但对于我这种好刨根问底之人来说,得自己过一遍源码才踏实,而且我发现 带着问题 看源码,思考理解本质,印象更深,收获更多,遂有此文。
142 0
|
消息中间件 算法 Java
换个姿势,带着问题看Handler(下)
Handler,老生常谈,网上关于它的文章可谓是 "泛滥成灾",而实际开发中,我们却很少手写Handler,毕竟 RxAndroid链式调用 和 Kotlin协程同步方式写异步代码 还是挺香的。但对于我这种好刨根问底之人来说,得自己过一遍源码才踏实,而且我发现 带着问题 看源码,思考理解本质,印象更深,收获更多,遂有此文。
271 0
|
消息中间件
一个 Handler 面试题引发的血案!!!
一个 Handler 面试题引发的血案!!!
119 0
|
存储 消息中间件 安全
Handler二十七问|你真的了解我吗?(上)
对于handler,你会想到什么呢?
148 0
Handler二十七问|你真的了解我吗?(上)
|
消息中间件 安全 Android开发
Handler二十七问|你真的了解我吗?(下)
对于handler,你会想到什么呢?
131 0
|
设计模式 安全 IDE
Context都没弄明白,还怎么做Android开发?
什么是 Context? 四大组件和 Context Application 和 Context 为什么 Application 的 Context 不可以创建 Dialog ?