V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
nnegier
V2EX  ›  Android

Handler(异步消息处理机制)所在的主线程里面既然是无限循环为什么不阻塞?

  •  
  •   nnegier · 2022-04-22 22:17:01 +08:00 · 10474 次点击
    这是一个创建于 972 天前的主题,其中的信息可能已经有所发展或是发生改变。
    这个在成都面试感觉问到的有点多呀,所以发上来讨论一下。

    首先我个人没有直接回答这个问题,我个人觉得这个是会阻塞的,就是 ANR ,在一些方法里做了耗时操作(主线程),所以我觉得这个问题怪怪的。后面我也有补充,屏幕亮着,每隔一段时间 16ms (现在应该不是固定值了,看屏幕刷新率)就会刷新一次屏幕(当然还有很多其它类型的消息,触摸消息按键消息以及我们自己的消息等),意思就是一直都会有消息进来处理的意思。说到这儿,貌似已经不言自明了,这个问题就被放一边儿了,换个问题问了。

    只是这个问题真的是问题吗?都说问一个好问题很重要。这个感觉有些问题。
    21 条回复    2022-07-07 12:25:04 +08:00
    lait
        1
    lait  
       2022-04-22 22:52:55 +08:00 via Android   ❤️ 2
    无限循环着代表 app 运行着,循环结束 app 就停止了呀。

    这个循环里如果有消息就处理,没有消息的时候就等待消息。

    等待消息的阻塞,和你在处理消息时耗时太长而引起 anr 不是一回事。
    你需要补充下基础概念,先看看 anr 的定义,然后再逐步深入理解等待消息的阻塞的基本解释和底层原理。
    nnegier
        2
    nnegier  
    OP
       2022-04-22 23:12:46 +08:00
    @lait 对于你回复的第一行,如果循环结束,应用就是停止了。ActivityThread#main{ ... Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited");}。 可以好好看看源码。 关于剩下的解释我过会儿尝试解答下
    zagfai
        3
    zagfai  
       2022-04-22 23:17:50 +08:00
    事件触发
    nnegier
        4
    nnegier  
    OP
       2022-04-23 01:03:49 +08:00
    @lait 除开第一行(你第一行是错的),貌似和我的又有什么冲突呢,它没消息肯定就等待呀进入 sleep 状态不占用 CPU 时间片嘛,有消息了就唤醒嘛,所谓的消息驱动模式。你要是仔细看的话,我的意思就是这个。关于 anr ,我倒还没有关注过它的源码,但你可以看 https://developer.android.com/topic/performance/vitals/anr ,“如果 Android 应用的界面线程处于阻塞状态的时间过长,会触发“应用无响应”(ANR) 错误。”
    lait
        5
    lait  
       2022-04-23 02:37:51 +08:00 via Android
    那您是对的,我理解有偏差吧。也许可以再一起研究研究 epoll 机制。
    GeruzoniAnsasu
        6
    GeruzoniAnsasu  
       2022-04-23 07:57:06 +08:00   ❤️ 2
    1. 循环和阻塞根本就是两个维度的概念,循不循环跟一个调用阻塞不阻塞半毛钱关系都没有

    2. 我看不懂原问题想表达什么

    3. 世界上的 GUI ,基本原理全都是一样的,全都有一个处理事件的循环

    4. 世界上所有的程序,如果一个调用「阻塞了」,那么背后的原理也全都是一样的,caller 线程 hang 在了一个等待区,内核或调度器不会让 CPU 执行到「等待结束」后面的代码







    虽然我真的没法看懂你想讨论的原问题是什么,但下面这个问题:

    如果消息循环收不到任何消息,会阻塞在「取消息」操作上吗

    的答案是,取决于实现。从节省 cpu 时间的角度考虑完全可以让它阻塞住。


    下面这个追加问题:

    那么既然阻塞住了,为什么不会被检测出死循环 /无响应

    的答案是,调度器或者卡死检测器完全可以检测这个线程是否停在了等待区上,是则不认为线程无响应,这个线程 /routine 仍然是随时可调度的,只是没机会调度而已。
    卡死 /无响应的表现是这个线程超过了阈值时间仍然没有返回等待区。
    比如「取消息」就可以包含 renew 无响应超时的操作。假设它不阻塞,那么反正返回 caller 前刷新超时时间就好,如果它是阻塞的,那么执行流会回到调度器或内核里,调度器知道这个线程已经「重回可调度状态」,则不对其设置无响应定时器。
    如果调度器把执行流交给一个线程前先设置一个定时中断,等来的是中断触发而非线程主动交还执行流,说明被调度线程无响应 /超时了








    ps. 所有观点来自 OS 基本原理,我根本没研究过 androi 系统机制
    Cabana
        7
    Cabana  
       2022-04-23 11:20:13 +08:00 via iPhone
    基础有点差,就去补补
    Cabana
        8
    Cabana  
       2022-04-23 11:26:21 +08:00 via iPhone
    @Cabana “个人觉得”、“我觉得”,“应该不是”,“感觉有些问题”。搞技术的不要想当然,Android 从 java 到 native 源码都是公开,不懂就多看看。
    StrorageBox
        9
    StrorageBox  
       2022-04-23 11:42:15 +08:00   ❤️ 1
    你是想说的 block 机制吧


    looper.loop()方法中开始循环调用 loopOnce()方法
    loopOnce()调用,messageQueue.next()方法,这方法是会被 block 的。

    - block 的原因呢
    有两种,其 1 是消息队列是空的,其 2 是开启了同步屏障,而消息队列中没有异步消息。

    - 解除 block 的原理也是这样,在 messageQueue.enqueueMessage() 中 1.添加新消息,2.添加异步消息(异步消息有两种添加方式,你可以从源码去了解一下)

    - 解除 block 的方法或者叫唤醒的方法,走 native wake(),本质上呢是通过 pipe 向指定 fd 进行写入一个 char(代码在 native Looper 中 ,同时可以了解一下 pipe 的原理)

    - 写入之后发生了什么,在 messageQueue.next()中首先调用 nativePollOnce()进入到 native Looper.poll_once(),进入到 native Looper.poll_inner() ,里面就能看到通过 epoll 去监听 fd 了(除了上面的指定 fd ,还含有多个 request 对应的 fd),如果有写入,就会有监听结果返回,Done 之后,block 便解除了。

    这个过程细节很多,感兴趣可以了解一下 epoll ,select/poll ,requst/response queue ,native handler 。

    结果就是,在 block 的时候,其实是有 epoll 去监听的。wake 之后就继续执行了。无限循环是为了保证线程不被结束,和 anr 是没有关系的。

    谈到 anr ,还是要重新理解一下概念。anr 发生的根本原因,不是线程什么都不做,而是 dispatchMessage()的执行时间,确切的说是上一个 message 的 disptch 时间过长导致现在的 message 不能被及时处理。

    我们看看 google 的定义"ANR 是一个问题,因为负责更新界面的应用主线程无法处理用户输入事件或绘制操作,引起用户的不满"。我觉得这个定义虽然没有把所有发生 anr 的情况罗列出来,但是很好的表达了 anr 的目的。

    回到你的描述上,为什么不卡屏幕刷新。作为应用程序的屏幕刷新时机有两种,1.requestlayout 刷新,2.重绘刷新,这两种刷新的原理都是通过 Choreographer 发送异步消息(在 Choreographer 就能看到两种异步消息的添加方式了)。

    纯手打,可能有些函数名有点出入。


    那么问题来了,屏幕上的图像到底是谁绘制的?
    nnegier
        10
    nnegier  
    OP
       2022-04-23 12:23:06 +08:00
    @Cabana 我是发上来讨论,我也怕打脸呀
    nnegier
        11
    nnegier  
    OP
       2022-04-23 12:50:10 +08:00
    @Cabana 我基础差?不知你对基础的定义是什么,好吧,反正我的确有被一些初级开发难倒过,他们喜欢问一些库怎么用,希望你不是。Android 源码的确是公开的,但那也太多了吧,你都看了吗,不过笨蛋才会全看吧,那我们聚焦于 ANR 的具体实现吧,你能回答吗?我自己在看,可以给你聚焦一下,在 ActivityManagerService 里面能找到。另外你回复了两条,但什么都没回答,期待你可能的最新回复是有价值的
    Cabana
        12
    Cabana  
       2022-04-23 14:55:24 +08:00 via iPhone
    @nnegier 这问题不是 Android 八股文么?一般问出来就是想看你有没有了解过过 epoll 机制。至于详细解释楼上都说的很详细了,你不听就没办法了。
    Cabana
        13
    Cabana  
       2022-04-23 14:57:35 +08:00 via iPhone
    @Cabana 最后还是提醒你,搞混淆的一点:无限循环不是阻塞!这一点楼上的朋友也多次指出了。
    Cabana
        14
    Cabana  
       2022-04-23 15:03:20 +08:00 via iPhone
    @nnegier 哦哦,我第一句话的表述可能不太清楚。有可能误会我在说你简单的基础知识差,这里我想表达的是是底层的基础知识,如有误会 sorry 。v 站本来就比较少人讨论 android 技术,希望不要给你留下不太好的印象。
    zpxshl
        15
    zpxshl  
       2022-04-23 20:29:21 +08:00
    循环和阻塞完全是 2 个概念。
    unco020511
        16
    unco020511  
       2022-04-24 09:33:04 +08:00
    gui 程序本来就是一个事件 /消息处理模型,没有消息时,相当于你的应用没有需要处理的界面信息,又何来阻塞一说呢?
    Geele
        17
    Geele  
       2022-04-24 11:21:20 +08:00   ❤️ 1
    如果 Android 应用的界面线程处于阻塞状态的时间过长,会触发“应用无响应”(ANR) 错误。”

    4L 的这句话理解不准确,应该是一些指定的事件处理时间超过其阈值会触发 ANR
    nothingistrue
        18
    nothingistrue  
       2022-04-24 13:46:40 +08:00
    线程阻塞状态,与同步任务的阻塞性,是两个概念。二者在某些情况下是相似的,但更可能是对立的。无线循环,是自己主动阻塞并让出资源,以便于整体上的不阻塞。

    再详细点我不想说,反正楼主也不是来问问题的。
    shiguiyou
        19
    shiguiyou  
       2022-04-24 17:20:19 +08:00
    经典安卓面试题(狗头
    fromzero
        20
    fromzero  
       2022-04-26 14:11:25 +08:00
    八股文 搜一下网上都是答案。-_-||
    verzqli
        21
    verzqli  
       2022-07-07 12:25:04 +08:00
    ANR 和无限循环是两回事,ANR 是系统设置的,是为了提升用户体验产生的,如果说不为了用户体验,给 ANR 等待时间设个无限时间,那你的手机永远不会崩溃
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1356 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 17:43 · PVG 01:43 · LAX 09:43 · JFK 12:43
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.