主线程中的Looper.loop()死循环为什么不会导致ANR?
源码的 ActivityThread 类中执行Looper.loop();
的main函数,也就是主线程的入口
public final class ActivityThread {public static void main(String[] args) {// 为主线程创建looperLooper.prepareMainLooper();// 创建ActivityThread 实例ActivityThread thread = new ActivityThread();thread.attach(false, startSeq);// 获取主线程的handlerif (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}// 开启loop循环Looper.loop();throw new RuntimeException("Main thread loop unexpectedly exited");}}
按照我们编写 java 的思维, 一个程序的 main 方法执行完成, 便代表着这个程序运行结束, 那么要使 application 一直得到运行,直到用户退出才结束程序, 那么我们势必得让这个线程一直运行下去不能结束, 否则一个APP 刚启动, main 方法结束,直接退出, 那程序也就结束了,那如何让一个线程一直运行呢?这里就用了一个无限循环的阻塞方式
接下来看看loop函数中的无限循环是如何实现的呢
public static void loop() {final Looper me = myLooper();...// 取出消息队列final MessageQueue queue = me.mQueue;...// 无限循环遍历取出消息队列里面的消息for (;;) {Message msg = queue.next(); // might block if (msg == null) {// No message indicates that the message queue is quitting.return;}...try {// 根据msg中的target(Handler),将消息发送的对应的handler中执行msg.target.dispatchMessage(msg); end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();} finally {if (traceTag != 0) {Trace.traceEnd(traceTag);}}}}
我们看到这个方法在获取到调用这个方法的线程(即主线程)的 looper 后, 再通过 looper获取了 messageQueue , 然后进入了一个死循环,我们看到官方的注释, 在 queue.next() 处, might block (当 messageQueue 为空时), 所以此时主线程就阻塞在这个地方了, 从而导致 main方法无法退出而因此避免掉 APP 一启动就结束
补充: 此处使用了pipe管道机制和epoll
- pipe机制,在没有消息时阻塞线程并进入休眠释放cpu资源,有消息时唤醒线程
- Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce() 方法里
那么问题来了, 既然阻塞了主线程,那又是如何响应用户操作和回调 activity 的生命周期的方法的呢?
这里就涉及到 Android 中的 Handler 机制原理 和 IPC 机制, 在此简单概括一下:
首先说一下, 当我们启动了一个 application 时, 此时该 application 进程中并不只有主线程一个线程,还有其他两个 Binder 线程(用来和系统进程进行通信操作,接收系统进程发送的通知),可以用下图介绍:
从系统进程system_server看:
- 当系统收到来自因用户操作而产生的通知时, 会通过 Binder 方式从系统进程跨进程的通知我们的 application 进程中的
ApplicationThread
, ApplicationThread
又通过 Handler 机制往主线程的messageQueue
中插入消息- 从而让主线程的
loop()
,Message msg = queue.next()
这句代码可捕获一条 message ,然后通过msg.target.dispatchMessage(msg)
来处理消息,从而实现了整个 Android 程序能够响应用户交互和回调生命周期方法(具体实现ActivityThread 中的内部类H中有实现)
其中ApplicationThread 是ActivityThread 的内部类,通过如下代码注入,供系统调用
// 创建ActivityThread 实例ActivityThread thread = new ActivityThread();// 注入ActivityThread thread.attach(false, startSeq);private void attach(boolean system, long startSeq) {final IActivityManager mgr = ActivityManager.getService();try {// 注入mgr.attachApplication(mAppThread, startSeq);} catch (RemoteException ex) {throw ex.rethrowFromSystemServer();}}
而至于为什么当主线程处于死循环的 Message msg = queue.next() 这句会阻塞线程的代码的时候不会产生 ANR 异常, 那是因为此时 messageQueue 中并没有消息,无需处理界面界面更新等操作。 因此主线程处于休眠状态,无需占用 cpu 资源, 而当 messageQueue 中有消息时,,系统会唤醒主线程,来处理这条消息。
那么我们在主线程中耗时为什么会造成 ANR 异常呢?
那是因为我们在主线程中进行耗时的操作是属于在这个死循环的执行过程中, 如果我们进行耗时操作, 可能会导致这条消息还未处理完成,后面有接受到了很多条消息的堆积,从而导致了 ANR 异常.
参考地址:
https://blog.csdn.net/DJH2717/article/details/82632512
https://www.jianshu.com/p/72c44d567640
https://www.zhihu.com/question/34652589
主线程中的Looper.loop()死循环为什么不会导致ANR?相关推荐
- 【Android面试】主线程中的Looper.loop()一直无限循环为什么不会造成ANR?
Android面试中,你也许会被问到题目中的问题,这里我们基于以下几点来延伸解读其中原因: 1.什么是ANR?ANR发生的原因是什么? 2.Looper为什么要无限循环? 3.线程的几种状态 4.主线 ...
- android线程卡死,Android主线程为什么不因为Loop死循环卡死
打开应用的时候都是创建一个进程,主线程采用死循环保证程序一直执行下去,这种模型是"以事件为驱动"软件系统的必然结果,几乎存在与任何操作系统和编程语言中.ActivityThread ...
- 【Android 异步操作】Handler ( 主线程中的 Handler 与 Looper | Handler 原理简介 )
文章目录 一.主线程中的 Handler 与 Looper 二.Handler 原理简介 一.主线程中的 Handler 与 Looper Android 系统中 , 点击图标启动一个应用进程 , 就 ...
- 【Android 异步操作】Android 线程切换 ( 判定当前线程是否是主线程 | 子线程中执行主线程方法 | 主线程中执行子线程方法 )
文章目录 一.判定当前线程是否是主线程 二.子线程中执行主线程方法 三.主线程中执行子线程方法 一.判定当前线程是否是主线程 在 Android 中 , 如果要判定当前线程是否是主线程 , 可以使用如 ...
- 在非主线程中创建窗口
很多朋友都会有过这样的经历,为什么在主线程中创建窗口且窗口工作很正常,但一移到非主线程(有的朋友喜欢叫它为工作线程),却无法正常工作.本文就这个问题和各位探讨,可能无法做到尽善尽美,但能抛砖引玉也算是 ...
- [转]Android限制只能在主线程中进行UI访问的实现原理
目录 Android限制只能在主线程中进行UI访问 Thread的实现 Android Thread 的构造方法 Android Thread 的start()方法 如何在我们自己的代码中去检测当前T ...
- 既然android service是运行在主线程中的,那service还有什么用?
既然android service是运行在主线程中的,那service还有什么用? 对于Android,每一个进程都有一个主线程,四大组件的处理任务都是在这个线程中进行的.每个线程都有一个Messag ...
- UnityThread子线程使用只能在主线程中调用的函数或Unity API
Unity的Socket网络编程中,为了防止程序卡死,一般使用多线程来监听端口,当收到来自客户端的消息时,需要显示在界面上.但是如果直接在子线程中操作Unity的界面或物体会报错.国外一个大神写了一个 ...
- 用Handler的post()方法来传递线程中的代码段到主线程中执行
自定义的线程中是不能更新UI的,但是如果遇到更新UI的事情,我们可以用handler的post()方法来将更新UI的方法体,直接传送到主线程中,这样就能直接更新UI了.Handler的post()方法 ...
最新文章
- python基础代码事例-学习笔记:python3,代码。小例子习作(2017)
- window.external.JavaScriptCallCpp
- 火热招募中 | PMCAFF产品经理社区志愿者计划火热开启
- 基本数据类型____字典
- 转学到斯坦福大学计算机专业,斯坦福大学转学申请条件有哪些?
- python函数——形参中的:*args和**kwargs
- OpenCV3 install tutorial for Mac
- 八大排序算法之希尔排序
- Python基础——全局变量与局部变量
- 在C ++中将String转换为Integer并将Integer转换为String
- 从零实现深度学习框架——优化反向传播相关代码
- C#6.0新特性的尝试
- 【渝粤教育】国家开放大学2018年秋季 2080T现代教育思想 参考试题
- 惠普m154a硒鼓芯片清零_打印机硒鼓芯片清零 请问一下硒鼓芯片清零是什么意思...
- 数据分析 告诉你《飞驰人生》为什么这么燃?
- 中国剩余定理证明及代码实现
- 南开大学张昊计算机,纽约南开校友会隆重举办纪念南开大学建校100周年庆典
- opencv实现图像的边缘提取
- 虚化背景(深度映射篇)
- canvas绘图 echarts 基本使用