Android中为什么主线程不会因为Looper.loop()里的死循环阻塞?

标题是伪命题

参考资料 Android中为什么主线程不会因为Looper.loop()里的死循环卡死? 知乎
之前对这个概念一直处于比较模糊的状态,也是一直被自己忽略了,认为可能涉及的东西过于复杂,所以不敢对自己问,为什么?
这两天状态不错,生活还是code比较有趣,简单而真实,所以曾经被忽略的问题不经意间又开始出现在脑海.
这个问题在我理解看来可以分为两个问题

为什么主线程需要阻塞

何谓主线程,和其他线程有什么不同之处

  • 何谓主线程
    主线程,通常称之为UI线程,也就是APP进程被创建的时候所处的线程,和其他的线程一样都是一个普通的线程.
  • 不同之处
    从app角度来说,不同之处主要集中在UI界面更新上面,普通线程不能更新UI界面,如此设计也是为了程序的健壮性,毕竟不同线程同时对对象操作时为了保证其准确性都要进行加锁,而界面更新不能像对象那样仅仅保证准确性,还要保证其连贯性,一个按钮在同一段时间同步(假同步)发生了向左又向右的滑动自然是不可取的.

线程的生命周期

一个进程或线程在CPU看来无非就是一段的可执行代码,代码执行完毕,线程的生命也就到头了.

APP的生命周期

从使用手机的角度来看,从点开APP图标开始,到完全退出APP结束.

主线程在哪里进行了阻塞

我们知道APP的入口是在ActivityThread,一个Java类,有着main方法,而且main方法中的代码也不是很多.

    public static void main(String[] args) {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");SamplingProfilerIntegration.start();// CloseGuard defaults to true and can be quite spammy.  We// disable it here, but selectively enable it later (via// StrictMode) on debug builds, but using DropBox, not logs.CloseGuard.setEnabled(false);Environment.initForCurrentUser();// Set the reporter for event logging in libcoreEventLogger.setReporter(new EventLoggingReporter());AndroidKeyStoreProvider.install();// Make sure TrustedCertificateStore looks in the right place for CA certificatesfinal File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());TrustedCertificateStore.setDefaultUserDirectory(configDir);Process.setArgV0("<pre-initialized>");Looper.prepareMainLooper();ActivityThread thread = new ActivityThread();thread.attach(false);if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}if (false) {Looper.myLooper().setMessageLogging(newLogPrinter(Log.DEBUG, "ActivityThread"));}// End of event ActivityThreadMain.Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);Looper.loop();throw new RuntimeException("Main thread loop unexpectedly exited");}

这就是main方法的全部代码了,23的源码.在其中Looper进行了初始化,但是并不是常规的prepare(),而是prepareMainLooper(),其实差别不大,只不过是给静态成员对象成员sMainLooper进行了初始化赋值,并不准予同进程中sMainLooper初始化第二次而已.
然后在代码末尾Looper.loop进行阻塞.

标题为什么是伪命题

我们都知道主线程是随着APP的启动而启动,随着APP的结束而结束的(多进程对应多主线程的情况我就将其看做一个统一的主线程).
APP要一直运行直到用户退出,那么主线程就必然不能代码运行完毕而终止,所以需要进行阻塞,直到用户退出了APP,才能停止阻塞,让CPU执行完剩下的代码,尔后代码执行完毕,主线程从而寿终正寝.

为什么主线程阻塞还能更新UI

既然线程是在Looper中阻塞了,那么与Looper配合着出现的Handler肯定是少不了的.
至于Handler是如何进行线程切换不了解的同学请戳这

ActivityThread.H

很容易就在Activity中找到了继承自Handler的内部类H,并且重写了handleMessage方法,代码就不列出了.

ActivityThread.H怎么和Looper交互的

光有Handler是不行的,关键要有调用Handler的地方,然后Handler才能去处理,才会在主线程调用一个又一个方法.
答案在这!

ActivityThread thread = new ActivityThread();thread.attach(false);

进一步来看attach()方法

    private void attach(boolean system) {...if (!system) {...final IActivityManager mgr = ActivityManagerNative.getDefault();try {mgr.attachApplication(mAppThread);} catch (RemoteException ex) {// Ignore}...} else {...}...}

恩,想必你们都知道我想看什么了,这里传入了对象mAppThread,我只关心mAppThread对象,而他作为参数最终通过IPC传递到哪里去,能力有限就不再继续跟进了.

ApplicationThread

上面我们说到了mAppThread对象,那么这个对象是哪个对象呢?就是ApplicationThread的实例化对象,代码不多,也就500来行,我就截取一点点来示例一下

        public final void schedulePauseActivity(IBinder token, boolean finished,boolean userLeaving, int configChanges, boolean dontReport) {sendMessage(finished ? H.PAUSE_ACTIVITY_FINISHING : H.PAUSE_ACTIVITY,token,(userLeaving ? 1 : 0) | (dontReport ? 2 : 0),configChanges);}public final void scheduleStopActivity(IBinder token, boolean showWindow,int configChanges) {sendMessage(showWindow ? H.STOP_ACTIVITY_SHOW : H.STOP_ACTIVITY_HIDE,token, 0, configChanges);}public final void scheduleCreateService(IBinder token,ServiceInfo info, CompatibilityInfo compatInfo, int processState) {updateProcessState(processState, false);CreateServiceData s = new CreateServiceData();s.token = token;s.info = info;s.compatInfo = compatInfo;sendMessage(H.CREATE_SERVICE, s);}public final void scheduleBindService(IBinder token, Intent intent,boolean rebind, int processState) {updateProcessState(processState, false);BindServiceData s = new BindServiceData();s.token = token;s.intent = intent;s.rebind = rebind;if (DEBUG_SERVICE)Slog.v(TAG, "scheduleBindService token=" + token + " intent=" + intent + " uid="+ Binder.getCallingUid() + " pid=" + Binder.getCallingPid());sendMessage(H.BIND_SERVICE, s);}

聪明的同学已经明了,主线程阻塞之后生命周期等方法是如何启用的.
这一个个形似各种声明周期的方法,最终还调用了sendMessage()方法,让我们再来看看sendMessage方法的是怎么操作的

    private void sendMessage(int what, Object obj) {sendMessage(what, obj, 0, 0, false);}private void sendMessage(int what, Object obj, int arg1) {sendMessage(what, obj, arg1, 0, false);}private void sendMessage(int what, Object obj, int arg1, int arg2) {sendMessage(what, obj, arg1, arg2, false);}private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {if (DEBUG_MESSAGES) Slog.v(TAG, "SCHEDULE " + what + " " + mH.codeToString(what)+ ": " + arg1 + " / " + obj);Message msg = Message.obtain();msg.what = what;msg.obj = obj;msg.arg1 = arg1;msg.arg2 = arg2;if (async) {msg.setAsynchronous(true);}mH.sendMessage(msg);}

可以看到最终调用了mH.sendMessage()方法,而mH是谁呢?ActivityThread的成员变量是ActivityThread.H的实例化对象.

总结

到此想必各位也就明了,主线程确实是阻塞的,不阻塞那APP怎么能一直运行,所以说主线程阻塞是一个伪命题,只不过是没有弄明白既然阻塞了,为什么还能调用各种声明周期而已.
调用生命周期是因为有Looper,有MessageQueue,还有沟通的桥梁Handler,通过IPC机制调用Handler发送各种消息,保存到MessageQueue中,然后在主线程中的Looper提取了消息,并在主线程中调用Handler的方法去处理消息.最终完成各种声明周期.

Android系统核心机制之APP启动的程序入口ActivityThread的简单介绍相关推荐

  1. android系统核心机制 基础(01)智能指针wp sp

    该系列文章总纲链接:android 系统核心机制基础 系列文章目录 本章关键点总结 & 说明: 以上是本模块的导图,整体概括了智能指针的几个要点,引用计数,弱转强,flag标志意义以及Ligh ...

  2. APP手机应用程序软件UI设计界面介绍AE模板

    APP手机应用程序软件UI设计界面介绍AE模板下载,具有时尚的设计,现代文本动画和时尚的过渡效果.简单易用,只需编辑文本,拖放到新媒体中并点击渲染即可.推广和宣传新手机应用程序的好方法等等.用这个令人 ...

  3. Android ImageLoader(Android-Universal-Image-Loader)【1】概述及使用简单介绍

     Android ImageLoader(Android-Universal-Image-Loader)[1]概述及使用简单介绍 一,前言:为什么要引入Android-Universal-Imag ...

  4. 【Android】之【App启动】

    一.启动方式 Android 应用的启动方式大概分为热启动.冷启动.温启动三种,关于冷启动.热启动.温启动三者启动方式对比可以参考下面的流程图学习. 1.1 冷启动 冷启动具有耗时最多,衡量标准的特征 ...

  5. android+闪屏启动优化,Android分享笔记(2) APP启动时闪屏

    App在启动时,即在欢迎界面.老是出现白屏或黑屏,闪一下然后才出现欢迎界面. 我欢迎界面原先是这样的:<?xml  version="1.0" encoding=" ...

  6. android源码学习- APP启动流程(android12源码)

    前言: 百度一搜能找到很多讲APP启动流程的,但是往往要么就是太老旧(还是基于android6去分析的),要么就是不全(往往只讲了整个流程的一小部分).所以我结合网上现有的文章,以及源码的阅读和调试, ...

  7. android核心机制之Zygote启动流程

    1.先说一下android中的服务,一种是系统服务,系统服务通过getSystemService方法获得,所有的系统服务运行在一个进程中. 2.还有一种是自定义Service,也就是通过startSe ...

  8. QT信号与槽-启动系统程序以及相关控件介绍

    1.1 创建一个继承至QWidget的项目. 1.2 设计界面,分析界面采用的布局方式. 首先,每行控件采用水平布局方式,第三行为了使控件靠右,前面添加一个占位控件. 三行采用垂直平局: 选中控件,通 ...

  9. 为知更新Android下一个apk安装多个程序入口图标

    Android开发中,一个工程对应一个AndroidManifest.xml文件,这个文件中包含有该项目的一些设置,如权限.SDk版本,Activity.Service信息等.一般而言,这个文件中会有 ...

最新文章

  1. 深入探讨Varnish缓存命中率
  2. 元素的高度(基于vue)
  3. MySQL 命令行下执行.sql脚本
  4. 用法 the_【课堂】a、an、the的用法
  5. 设计师更高效_如何丢掉我的工作使我成为一名更好的设计师
  6. 服务器集成显卡性能,Win8.1与Ubuntu 14.10:集成显卡性能PK
  7. Fleury算法 求欧拉回路
  8. 高保真原型、动画引导、用户登录、巡检任务、维保任务、用户中心、历史记录、帮助中心、清除缓存、任务抽检、扫描二维码、待办事项、账号设置、客服信息、交互说明、手机端、小程序、app原型、BIM信息综合管理
  9. Linux 命令(4)—— declare/typeset 命令(builtin)
  10. php输入一个字符串 输出所有组合,C++_C语言实现输入一个字符串后打印出该字符串中字符的所有排列,本文实例讲述了C语言实现输入 - phpStudy...
  11. 一起talk C栗子吧(第九十五回:C语言实例--使用共享内存进行进程间通信一)...
  12. 什么是Photoshop的Alpha通道(详细图解)
  13. CSDN送你一份春节压岁钱,请在 24H 内领取!
  14. 如何使用Elasticsearch构建强大的搜索和分析应用程序(2023年最新ES新手教程)
  15. SAP物料清单MM60中如何统计输出条目数量
  16. 智能时代为什么需要区块链技术?
  17. 《JAVA程序性能优化》总结
  18. PS教程:用ps制作梦幻紫色星空文字
  19. 通过星座获取日期月份
  20. 微波射频学习笔记21-------三极管

热门文章

  1. autocad 如何摆正显示_CAD如何快速显示并居中图形
  2. 狮子、豹、狼-----看管理
  3. verilog generate 生成语句
  4. 万向区块链蜂巢学院 | 大硕:加密世界的NFT和游戏——从碎片化时间到碎片化价值
  5. python基础——魔术方法
  6. 可以提高复试成绩的app、网站推荐!
  7. 华为鸿蒙智慧屏有b站吗,鸿蒙、智慧屏,是华为的“阳谋”
  8. 行业调研:中国TOP10互联网公司业务布局分析
  9. up and UP……
  10. Mac 安装 Maven 并配置环境