Android——主线程的loop为什么不会造成死循环?

线程是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出。

MainThread的loop为什么不会造成死循环?
因为这个loop处理一切的事件,包括绘制,点击事件等等,它停止了,那整个android程序就退出了。

但这里可能又引发了另一个问题,既然是死循环又如何去处理其他事务呢?通过创建新线程的方式。(这里的其他事务指的是什么?后面会讲到)

我们先看一下ActivityThread的main方法:

public static void main(String[] args) {
        ...
        //创建Looper和MessageQueue对象,用于处理主线程的消息
        Looper.prepareMainLooper();

        //创建ActivityThread对象
        ActivityThread thread = new ActivityThread();

        //建立Binder通道 (创建新线程)
        thread.attach(false);

        //消息循环运行
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

thread.attach(false);便会创建一个Binder线程(具体是指ApplicationThread,Binder的服务端,用于接收系统服务AMS发送来的事件),该Binder线程通过Handler将Message发送给主线程

ActivityThread的内部类H继承于Handler,通过handler消息机制,简单说Handler机制用于同一个进程的线程间通信。

Activity的生命周期都是依靠主线程的Looper.loop,当收到不同Message时则采用相应措施:
在H.handleMessage(msg)方法中,根据接收到不同的msg,执行相应的生命周期。

比如收到msg=H.LAUNCH_ACTIVITY,则调用ActivityThread.handleLaunchActivity()方法,最终会通过反射机制,创建Activity实例,然后再执行Activity.onCreate()等方法;
再比如收到msg=H.PAUSE_ACTIVITY,则调用ActivityThread.handlePauseActivity()方法,最终会执行Activity.onPause()等方法。 真正该过程远比这复杂。

主线程的消息又是哪来的呢?当然是App进程中的其他线程通过Handler发送给主线程:

图片来源:https://www.zhihu.com/question/34652589/answer/90344494

system_server进程是系统进程,java framework框架的核心载体,里面运行了大量的系统服务,比如这里提供ApplicationThreadProxy(简称ATP),ActivityManagerService(简称AMS),这个两个服务都运行在system_server进程的不同线程中,由于ATP和AMS都是基于IBinder接口,都是binder线程,binder线程的创建与销毁都是由binder驱动来决定的。

 

App进程则是我们常说的应用程序,主线程主要负责Activity/Service等组件的生命周期以及UI相关操作都运行在这个线程; 另外,每个App进程中至少会有两个binder线程 ApplicationThread(简称AT)和ActivityManagerProxy(简称AMP),除了图中画的线程,其中还有很多线程,比如signal catcher线程等,这里就不一一列举。

Binder用于不同进程之间通信,由一个进程的Binder客户端向另一个进程的服务端发送事务,比如图中线程2向线程4发送事务;而handler用于同一个进程中不同线程的通信,比如图中线程4向主线程发送消息。

结合图说说Activity生命周期,比如暂停Activity,流程如下:

  1. 线程1的AMS中调用线程2的ATP;(由于同一个进程的线程间资源共享,可以相互直接调用,但需要注意多线程并发问题)
  2. 线程2通过binder传输到App进程的线程4;
  3. 线程4通过handler消息机制,将暂停Activity的消息发送给主线程;
  4. 主线程在looper.loop()中循环遍历消息,当收到暂停Activity的消息时,便将消息分发给ActivityThread.H.handleMessage()方法,再经过方法的调用,最后便会调用到Activity.onPause(),当onPause()处理完后,继续循环loop下去。

一个线程可能有一个Looper,它包含一个MessageQueue。为了使用这个功能,您必须通过在当前线程上调用(静态的)Looper.prepare()创建一个Looper.prepare(),然后通过调用(同样是静态的)Looper.loop()来启动循环。这些是静态的,因为每个线程应该只有一个Looper。


对loop()的调用通常不会返回,它会从MessageQueue中获取消息并单独处理它们(例如,调用Message中包含的Runnable)。当队列中没有剩余的消息时,线程将阻塞,直到有新的消息为止。要停止Looper,您必须在它上调用quit()(它可能不会立即停止循环,而是设置一个私有标志,该标志定期从循环中检查,向Looper发出停止的信号)。

 

在某些时候(可能在创建任何活动或类似的东西之前),framework已经设置了一个Looper(包含MessageQueue)并启动了它。从那时候开始,UI线程上发生的所有事情都通过这个循环。这包括活动生命周期管理等等。覆盖(onCreate()、onDestroy()…)的所有回调都至少是间接地从循环中发出的。您可以在异常的堆栈跟踪中看到这一点。

 

实际上,在主线程中的Looper是允许绘图的。当视图无效(例如,调用invalidate)时,将向主Looper发送一条消息,告诉它请求重绘。当Looper处理该消息时,重绘就发生。


为什么不立即绘制而是发送消息呢?答案是性能。绘画是缓慢的。如果您对一个事件做了多个更改,您不希望为每个更改重新绘制屏幕。如果用发送消息的机制,就可以将所有需要重新绘制的事件打包成一个重新绘制的事件。例如,如果您设置一个视图的文本和另一个视图的图像,它们将同时被重绘,而且只会被重绘一次,而不是两次。

 

关于IdleHandler:https://blog.csdn.net/tencent_bugly/article/details/78395717

借鉴:https://www.zhihu.com/question/34652589/answer/90344494

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章