( 转载请务必标明出处:http://www.cnblogs.com/linguanh/, 本文出自:【林冠宏(指尖下的幽灵)的博客】)

前序

  本文将会把一下三个问题阐述清楚以及一个网上的普遍观点的补充:

    1,安卓 APP 启动过程,对于Activity 的 onCreate 等生命周期的函数为什么不会因为 Looper.loop()里的死循环卡死而永无机会执行。

    2,在 1 的基础上,View 的绘制到底是怎样完成的,它又为什么不会因为 Looper.loop()里的死循环卡死而永无机会刷新。

    3,网传的观点大概如下:

        1.handler机制是使用pipe来实现的

        2.主线程没有消息处理时阻塞在管道的读端

        3.binder线程会往主线程消息队列里添加消息,然后往管道写端写一个字节,这样就能唤醒主线程从管道读端返回,也就是说queue.next()会调用返回

        4.dispatchMessage()中调用onCreate, onResume

 
    4,子线程真的不能刷新 UI ?
  
  其次,最终的内容我将放到两张图片上面去展示出来,源码的分析这里将不再累赘去说。第一部分网上很多,第二部分网上零散,我是通过源码分析书籍总结出来的。

  下面的阐述中,将采用:先告知答案,再放直观图片,最后文字辅助解析的顺序。

 
解答第一个问题
 
  此部分的源码分析,网上很多,搜索关键字,ActivityThread,Activity 的 onCreate 在哪调用等。
 
  总结:Activity 的 生命周期函数都是在 Looper 里面的死循环中被 ActivityThread 内部的 Handler 的 handleMessage 入口调用的,本身在循环里面调用,也就不会被阻塞,在 onCreate 等函数里面发送一个 message 也是会到这里被处理掉,仍然互不影响。
 
  先上图,看不懂,结合后面文字看,或者源码。

   文字解析,仅描述重点:
  APP 的启动过程很复杂,但是最终的入口会在 ActivityThread 类里面的 main 函数,在这个函数里面,首先会调用 Looper.prepare 目的是实例化一个 looper 对象,方便后续的主线程中的 handler 实例化获取并使用。
 
  因为 Handler 的消息发送和处理机制是基于 Looper 里面 MessageQueue 的,所以得先存在looper。
  然后是实例化一个自身对象,即是 new ActivityThead(),在这里面会进行内部的两个重要变量的初始化,就是后续的mAppThread Binder实例以及一个HHandler实例,当 H 发送或处理下消息的时候,使用的Looper就是上面实例化的。然后是它自身的 attach(...)函数,在内部进行 AMS(ActivityManagerSevice)和mAppThread Binder进程通讯者的绑定,即是AMS的attachApplication(...)的调用。
 
  随后调用AMS的attachApplicationLocked(...),在这个函数里面,将会进行第一次跨进程通讯,AMS运行在系统进程,而我们的APP是另外一个进程。此时在AMS里面会调用我们上面绑定的mAppThread binder对象的bindApplication(...)方法,触发BIND_APPLICATION消息,该消息由 H 来进行发送,也就是 sendMessage(...),此时消息会在 Looper 里面的 loop() 进行处理。
 
  像 Handler 源码一样,最后会在 H.handleMessage(...) 处理, 然后就是进入到对应的函数里面进行 Context 的初始化,Application开始初始化,并且调用它的 onCreate,等其他操作。
 
  上面为第一部分,接下来是第二部分。在AMS的attachApplicationLocked(...)函数里面,在触发了第一次进程通讯后,代码接着运行,会在里面进行第二次的进程通讯,首先是Activity的栈管理者之一ActivityStackSupervisor调用它的attachApplicationLocked(...),在里面调用 realStartActivityLocked(...),然后是正式发起第二次IPC,触发 LAUNCH_ACTIVITY 消息,一样是 H 发送和处理,处理处调用 performLaunchActivity(...),在这里面,根据一直传下来的信息,将会 new 一个 Actiivity,然后就是调用它的 onCreate,onStart。
 
 
第二个问题
  

  声明:此部分的内部十分地复杂!包括下面的图与文字解析在内,仅作抛砖引玉,是个人总结的大概流程。关于源码分析,网上很零散,十分建议看源码分析类书籍。
 

  总结:View 的底层绘制是基于Binder进程通讯触发,由底层 SurfaceFlinger 的工作线程中的事件机制,包含 handler ,looper,messageQueue 来进行接收、处理,它和 ActivityThread 的 Handler 没关系,即是与 ActivityThread 几乎无关,但是如果在ActivityThread 里面调用 View的相关函数,例如 handleMessage 的一个 setText(..),最终触发到View其它底层函数,它将会将这些信息发送到 SurfaceFlinger 的事件机制中去,被对应处理,最终刷新到界面。

  点我查看 PDF 图片

  
   文字解析,里面所有函数和变量都是底层C++代码 的。Android 的GUI系统,也是图形界面系统,其依赖于OpenGL,EGL 等函数库,同时Android硬件HAL层的接口Composer的直接使用者是SurfaceFlinger,SurfaceFlinger,对于OpenGL而言,是一个很重要 ”应用“,它依赖于OpenGL,EGL的函数库,并使用他们的API来进行图形的最终绘制。
  SurfaceFlinger 在启动时会先进行自己内部的一个工作线程实例化和运行,该线程在后面承担着整个的绘制事件流程,在运行该线程时,会先进行MessageQueue内部的 looper 和 handler 的实例化,然后再 Run,Run 内部启动了事件的循环。
 
  从这一刻开始,它将进入到 waitForEvent(...)方法,这里是个死循环,并在里面调用 waitMessage(...),waitMessage 里面将会调用 looper的pollOnce(...),该方法和 ActivityThread 的 loope() 内部的 next() 里面的 queue.next() 差不多,不同的是 pollOnce(...) 会调用 MessageQueue 内部的函数 eventReceiver(...) 。
 
  eventReceiver 内部将会对进程中的消息获取,如果有收到其它进程传过来的对应的VSync 消息,那么将会对其进行下一步的分发,就是 dispatchInvalidate(...) 或 dispatchRefesh(...),最后会进入到 handler 的 handlMessage,然后回调 SurfaceFlinger 的 onMessgeReceiver(...),内部再调用 handleMessageRefresh(...)。
 
  然后是 SurfaceFlinger 的 layer层对View改变的绘制,绘制结合 NativeWindow 和 FrameBuffer 的缓存技术,最终将结果呈现到终端。代码非常复杂。
  
 
 
第三个补充
 
  网上对于博文标题的这个问题的解析普遍是:见前序的第三点,这里要补充的是,如果是 View 的 UI 刷新,不会导致阻塞的原因是本文的第二个解释,View 的绘制与 Java 代码的looper无关,而是由底层 SurfaceFlinger 自身的事件处理机制处理的。对于第一个问题的解析,那么可以参考前序第三点的内容。
 
 
第四个问题
  
   如果您有耐心看到这里,非常感谢,可能有朋友会想起 Android 的另外一句名言,子线程不能刷新UI,这样是否和上面说的冲突呢?其实不然,看下下面的代码片段,它是源码里面限制我们在子线程刷新UI的。
  

 1 void checkThread() {
2
3 if (mThread != Thread.currentThread()) {
4
5 throw new CalledFromWrongThreadException(
6
7 "Only the original thread that created a view hierarchy can touch its views.");
8
9 }
10
11 }

   代码第三行,其中 mThread 是创建 ViewRootImpl 的线程,而ViewRootImpl是在主线程中创建的,所以,我们习惯地称它为主线程,mThread和当前代码运行的线程来做了个等式运算,相同就出错,也就是说,并不是子线程不能刷新UI,准确来说,是发送进行 UI 刷新消息的消息,因为真正的底层刷新也不是当前 APP 的主线程。而是限制了,如果当ViewRootImpl是由子线程创造的,那么就可以在该子线程中发送更新UI的消息,自然地就能更新了,那么为什么限制呢?

  下面解析引自知乎

  因为不光是gui,同样的道理在几乎所有编程领域里都是这样的,这背后是线程同步的开销问题。显然两个线程不能同时draw,否则屏幕会花;不能同时insert map,否则内存会花;不能同时write buffer,否则文件会花。需要互斥,比如锁。结果就是同一时刻只有一个线程可以做ui。那么当两个线程互斥几率较大时,或者保证互斥的代码复杂时,选择其中一个长期持有其他发消息就是典型的解决方案。所以普遍的要求ui只能单线程。

(转)关于Android中为什么主线程不会因为Looper.loop()里的死循环卡死?引发的思考,事实可能不是一个 epoll 那么 简单。的更多相关文章

  1. 关于Android中为什么主线程不会因为Looper.loop()里的死循环卡死?引发的思考,事实可能不是一个 epoll 那么 简单。

    ( 转载请务必标明出处:http://www.cnblogs.com/linguanh/, 本文出自:[林冠宏(指尖下的幽灵)的博客]) 前序 本文将会把一下三个问题阐述清楚以及一个网上的普遍观点的补 ...

  2. Android中为什么主线程不会因为Looper.loop()方法造成阻塞

    很多人都对Handler的机制有所了解,如果不是很熟悉的可以看看我 如果看过源码的人都知道,在处理消息的时候使用了Looper.loop()方法,并且在该方法中进入了一个死循环,同时Looper.lo ...

  3. Android中线程间通信原理分析:Looper,MessageQueue,Handler

    自问自答的两个问题 在我们去讨论Handler,Looper,MessageQueue的关系之前,我们需要先问两个问题: 1.这一套东西搞出来是为了解决什么问题呢? 2.如果让我们来解决这个问题该怎么 ...

  4. Android之Handler用法总结/安卓中只有主线程可以修改UI

    Handler传递消息的方式可以实现实时刷新以及长按连续响应事件. 按钮响应 btnadd_fcl.setOnTouchListener(new View.OnTouchListener() { pr ...

  5. Android线程之主线程向子线程发送消息

    和大家一起探讨Android线程已经有些日子了,谈的最多的就是如何把子线程中的数据发送给主线程进行处理,进行UI界面的更新,为什么要这样,请查阅之前的随笔.本篇我们就来讨论一下关于主线程向子线程如何发 ...

  6. ThreadLocal ——android消息机制handler在非主线程创建not called Looper.prepare() 错误的原因

    引用自:https://www.jianshu.com/p/a8fa72e708d3 引出: 使用Handler的时候,其必须要跟一个Looper绑定.在UI线程可直接初始化Handler来使用.但是 ...

  7. android 4.0主线程訪问网络问题

    在4.0下面,在主线程中訪问网络,假设请求超过6s的话,就会报ANR,那么这就会带来一个问题,假设网络慢或者请求的数据过大时,界面会卡顿,造成界面灵敏性非常差,因此网络请求一般不能放在主线程中操作,g ...

  8. Android 在非主线程无法操作UI意识

    Android在应用显示Dialog是一个非常easy事儿,但我从来没有尝试过Service里面展示Dialog. 经验UI操作要在主线程,本地的服务Service是主线程里没错,可是远程servic ...

  9. [Java][Android] 多线程同步-主线程等待全部子线程完毕案例

    有时候我们会遇到这种问题:做一个大的事情能够被分解为做一系列相似的小的事情,而小的事情无非就是參数上有可能不同样而已! 此时,假设不使用线程,我们势必会浪费许多的时间来完毕整个大的事情.而使用线程的话 ...

随机推荐

  1. Hdu 5884

    hdu 5884 Sort 题意: n个有序序列的归并排序.每次可以选择不超过k个序列进行合并,合并代价为这些序列的长度和,总的合并代价不能超过T, 问k最小是多少. 解法: 1:首先想到的是二分这个 ...

  2. 【分类模型评判指标 二】ROC曲线与AUC面积

    转自:https://blog.csdn.net/Orange_Spotty_Cat/article/details/80499031 略有改动,仅供个人学习使用 简介 ROC曲线与AUC面积均是用来 ...

  3. CISCO实验记录一:路由器基本配置

    一.路由器基本配置要求 1.设置路由器名为:hehe 2.设置特权模式下password为ccna,secret为ccnp,vty线路密码为ccie 3.所有明文密码都加密 二.路由器基本配置命令 1 ...

  4. CDN之Web Cache

    1. Cache 的工作方式 Web Cache 作为一种网页缓存技术,可以在用户访问网站服务器的任何一个中间网元上实现.根据 HTTP 协议的定义,在一次网页访问中,用户从客户端发出请求到网站服务器 ...

  5. ThinkPHP数据查询与添加语句

    在ThinkPHP框架中实现数据的查询操作 function ShowAll() { //Model:数据库中每张表对应一个模型 //类名是表名,类里面的成员变量是列名 //把一张表对应一个类,其中一 ...

  6. OpenCV中出现“Microsoft C++ 异常: cv::Exception,位于内存位置 0x0000005C8ECFFA80 处。”的异常

    对于OpenCV的安装 要感谢网友空晴拜小白提供的教程 链接如下: https://blog.csdn.net/sinat_36264666/article/details/73135823?ref= ...

  7. POJ2513:Colored Sticks(字典树+欧拉路径+并查集)

    http://poj.org/problem?id=2513 Description You are given a bunch of wooden sticks. Each endpoint of ...

  8. 找不到FileProvider类怎么办?找不到R资源怎么办?APPT2错误怎么办?

    坑2: 在使用上述解决方案时,需要加入android.support.v4.content.FileProvider这个类,当时我没有这个包.但是在引入相应的依赖包后,各种异常就出现了. 先是把And ...

  9. 小D课堂-SpringBoot 2.x微信支付在线教育网站项目实战_5-2.微信扫一扫功能开发前期准备

    笔记 2.微信扫一扫功能开发前期准备         简介:讲解微信扫一扫功能相关开发流程和资料准备              1.微信开放平台介绍(申请里面的网站应用需要企业资料)          ...

  10. openstack核心组件--nova计算服务(3)

    一.nova介绍:       Nova 是 OpenStack 最核心的服务,负责维护和管理云环境的计算资源.OpenStack 作为 IaaS 的云操作系统,虚拟机生命周期管理也就是通过 Nova ...