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

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

5.当我们用Handler发送一个消息发生了什么?


扯得有点远了,拉回来,刚讲到 ActivityThreadmain函数中调用 Looper.prepareMainLooper 完成主线程 Looper初始化,然后调用 Looper.loop() 开启消息循环 等待接收消息


嗯,接着说下 发送消息,上面说了,Handler可以通过sendMessage()和 post() 发送消息,上面也说了,源码中,这两个最后调用的其实都是 sendMessageDelayed()完成的:


image


第二个参数:当前系统时间+延时时间,这个会影响「调度顺序」,跟 sendMessageAtTime()


image


获取当前线程Looper中的MessageQueue队列,判空,空打印异常,否则返回 enqueueMessage(),跟:


image


这里的 mAsynchronous异步消息的标志,如果Handler构造方法不传入这个参数,默认false: 这里涉及到了一个「同步屏障」的东西,等等再讲,跟:MessageQueue -> enqueueMessage


image


如果你了解数据结构中的单链表的话,这些都很简单。 不了解的可以移步至【面试】数据结构与算法(二) 学习一波~


6.Looper是怎么拣队列的消息的?


MessageQueue里有Message了,接着就该由Looper分拣了,定位到:Looper → loop函数


// Looper.loop()
final Looper me = myLooper();           // 获得当前线程的Looper实例
final MessageQueue queue = me.mQueue;   // 获取消息队列
for (;;) {                              // 死循环
        Message msg = queue.next();     // 取出队列中的消息
        msg.target.dispatchMessage(msg); // 将消息分发给Handler
}


queue.next() 从队列拿出消息,定位到:MessageQueue -> next函数


image


这里的关键其实就是:nextPollTimeoutMillis,决定了堵塞与否,以及堵塞的时间,三种情况:


等于0时,不堵塞,立即返回,Looper第一次处理消息,有一个消息处理完 ;大于0时,最长堵塞等待时间,期间有新消息进来,可能会了立即返回(立即执行);等于-1时,无消息时,会一直堵塞;


此处没有用java中的wait/notify堵塞,而是通过Linux的**epoll机制**来堵塞,原因是需要处理 native侧 的事件。


没有消息时堵塞并进入休眠释放CPU资源,有消息时再唤醒线程。


对epoll机制感兴趣的可移步至下述网站查阅:


Linux IO模式及 select、poll、epoll详解


7.分发给Handler的消息是怎么处理的?


通过MessageQueuequeue.next()拣出消息后,调用msg.target.dispatchMessage(msg)把消息分发给对应的Handler,跟到:Handler -> dispatchMessage


image


到此,关于Handler的基本原理也说的七七八八了~


8.IdleHandler是什么?


评论区有小伙子说:把idleHandler加上就完整了,那就安排下吧~


MessageQueue 类中有一个 static 的接口 IdleHanlder


image


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


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


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


image


输出结果如下


image


看下源码,了解下具体的原理:MessageQueue,定义了一个IdleHandler的列表和数组


image


定义了添加和删除IdleHandler的函数:


image


next() 函数中用到了 mIdleHandlers 列表:


image


原理就这样,一般使用场景:绘制完成回调,例子可参见:《你知道 android 的 MessageQueue.IdleHandler 吗?》也可以在一些开源项目上看到IdleHandler的应用:useof.org/java-open-s…


0x4、一些其他问题


1.Looper在主线程中死循环,为啥不会ANR?


答:上面说了,Looper通过**queue.next()**获取消息队列消息,当队列为空,会堵塞,


此时主线程也堵塞在这里,好处是:main函数无法退出,APP不会一启动就结束!

你可能会问:主线程都堵住了,怎么响应用户操作和回调Activity生命周期相关的方法?


答:application启动时,可不止一个main线程,还有其他两个Binder线程ApplicationThreadActivityManagerProxy,用来和系统进程进行通信操作,接收系统进程发送的通知。


image


  • 当系统受到因用户操作产生的通知时,会通过 Binder 方式跨进程通知 ApplicationThread;


  • 它通过Handler机制,往 ActivityThreadMessageQueue 中插入消息,唤醒了主线程;


  • queue.next() 能拿到消息了,然后 dispatchMessage 完成事件分发;


Tips:ActivityThread 中的内部类H中有具体实现


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


2.Handler泄露的原因及正确写法


上面说了,如果直接在Activity中初始化一个Handler对象,会报如下错误:


image


原因是


在Java中,非静态内部类会持有一个外部类的隐式引用,可能会造成外部类无法被GC; 比如这里的Handler,就是非静态内部类,它会持有Activity的引用从而导致Activity无法正常释放。


而单单使用静态内部类,Handler就不能调用Activity里的非静态方法了,所以加上「弱引用」持有外部Activity。


代码示例如下


private static class MyHandler extends Handler {
    //创建一个弱引用持有外部类的对象
    private final WeakReference<MainActivity> content;
    private MyHandler(MainActivity content) {
        this.content = new WeakReference<MainActivity>(content);
    }
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        MainActivity activity= content.get();
        if (activity != null) {
            switch (msg.what) {
                case 0: {
                    activity.notifyUI();
                }
            }
        }
    }
}


转换成Kotlin:(Tips:Kotlin 中的内部类,默认是静态内部类,使用inner修饰才为非静态~)


private class MyHandler(content: MainActivity) : Handler() {
    //创建一个弱引用持有外部类的对象
    private val content: WeakReference<MainActivity> = WeakReference(content)
    override fun handleMessage(msg: Message) {
        super.handleMessage(msg)
        val activity = content.get()
        if (activity != null) {
            when (msg.what) {
                0 -> {
                    activity.notifyUI()
                }
            }
        }
    }
}


另外,还有一种情况可能会引起内存泄漏:延时消息,Activity关闭消息还没处理完,可以在Activity的onDestroy()中调用:

handler.removeCallbacksAndMessages(null) 移除Message/Runnable。


3.同步屏障机制


通过上面的学习,我们知道用Handler发送的Message后,MessageQueueenqueueMessage()按照 时间戳升序 将消息插入到队列中,而Looper则按照顺序,每次取出一枚Message进行分发,一个处理完到下一个。


这时候,问题来了:有一个紧急的Message需要优先处理怎么破?你可能或说**直接sendMessage()**不就可以了,不用等待立马执行,看上去说得过去,不过可能有这样一个情况:


一个Message分发给Handler后,执行了耗时操作,后面一堆本该到点执行的Message在那里等着,这个时候你sendMessage(),还是得排在这堆Message后,等他们执行完,再到你!


对吧?Handler中加入了「同步屏障」这种机制,来实现「异步消息优先执行」的功能。

添加一个异步消息的方法很简单:


  • 1、Handler构造方法中传入async参数,设置为true,使用此Handler添加的Message都是异步的;


  • 2、创建Message对象时,直接调用setAsynchronous(true)


一般情况下:同步消息和异步消息没太大差别,但仅限于开启同步屏障之前。可以通过 MessageQueuepostSyncBarrier 函数来开启同步屏障:


image


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


image


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


image


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


image


4.Android 11 R Handler 变更


官方文档developer.android.google.cn/reference/a…


构造函数


  • Handler() 废弃 → Handler(Looper.myLooper())
  • Handler(Handler.Callback callback) 废弃 → Handler(Looper.myLooper(), callback)


Looper.prepareMainLooper () 废弃 原因:主线程的Looper是由系统自动创建的,无需用户自行调用。


参考文献:








相关文章
|
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协程同步方式写异步代码 还是挺香的。但对于我这种好刨根问底之人来说,得自己过一遍源码才踏实,而且我发现 带着问题 看源码,思考理解本质,印象更深,收获更多,遂有此文。
141 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 ?