( 转载请务必标明出处: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实例以及一个H Handler实例,当 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的。
  

 void checkThread() {

         if (mThread != Thread.currentThread()) {

             throw new CalledFromWrongThreadException(

                     "Only the original thread that created a view hierarchy can touch its views.");

         }

 }

   代码第三行,其中 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. LPC1768的usb使用--硬件篇

    LPC1768芯片带有USB设备控制器,前面写的文章都是在说比较简单的设备驱动,今天来说复杂一点的 首先是硬件层的配置 #ifndef __USBHW_H__ #define __USBHW_H__ ...

  2. Spring接口编程_设值注入和构造注入

    说明: UserManagerImp是设值注入,UserManagerImp2是构造注入 接口不注入,也就是在Spring配置文件中没有接口的<bean>,但是定义的时候是用接口 priv ...

  3. 浅析IoC框架

    今日拜读了一篇关于IOC的文章,特意转载,和大家分享一下 1 IoC理论的背景    我们都知道,在采用面向对象方法设计的软件系统中,它的底层实现都是由N个对象组成的,所有的对象通过彼此的合作,最终实 ...

  4. HIDKomponente使用读写Hid设备(转)

    源:HIDKomponente使用读写Hid设备一瞥 HIDKomponente 是delphi中使用的第三方Hid控件库,可以检测.控制连接到电脑的Hid设备.一般情况下多为usb设备. HIDKo ...

  5. php连接mysql数据库(新浪云SAE)

    新浪云提供了免费的创建服务器端应用的服务.网址为:https://www.sinacloud.com/ 在上面创建好应用,然后在本地使用记事本编写应用的代码如下: <?php echo &quo ...

  6. intel Xeon(R) CPU E5-2650 v2 性能测试报告

                                          intel  Xeon(R) CPU E5-2650 v2                                 ...

  7. WPF中ContextMenu通过CommandParameter传参

    场景:ListBox中有个ContextMenu,希望点击其中一个菜单项的时候把ListBox当做CommandParameter传递给Command,但是发现无论是通过ElementName还是Re ...

  8. windows中如何查看某个端口被谁占用

    说明:本人操作系统为win7 x64,文章转自http://jingyan.baidu.com/article/3c48dd34491d47e10be358b8.html,加上本人的注释. 开始--- ...

  9. win7(windows 7)系统下安装SQL2005(SQL Server 2005)图文教程——转载

    操作系统:Microsoft Windows 7 旗舰版(32位) 数据库版本:SQL Server 2005 简体中文开发板 数据库下载链接:http://pan.baidu.com/share/l ...

  10. 2015年15+最佳的响应式HTML5网站模板

    015年最好的免费响应式HTML5模板通常用于创建新潮的网站. HTML5是HTML用于创建现代化网站的最新版本.随着这一现代标记语言的出现,网上冲浪的趋势变得越来越智能化越来越酷.几乎每个web开发 ...