主线程Looper.loop()里的死循环不ANR疑惑

引子:

正如我们所知,在android中如果主线程中进行耗时操作会引发ANR(Application Not Responding)异常。

当应用程序的UI线程响应超时才会引起ANR, 造成ANR的原因一般有两种:
1. 当前的事件没有机会得到处理,例如UI线程正在响应另一个事件,当前事件被阻塞了。
2. 当前的事件正在处理,但由于耗时太长没能及时完成。

一般造成ANR的场景,从本质上将, 产生ANR的原因有三种:
 1. KeyDispatchTimeout:原因就是View的点击事件或者触摸事件在5s内无法得到响应。
 2. BroadcastTimeout:原因是BroadcastReceiver的onReceive()函数运行在主线程中,在10s内无法完成处理。
 3. ServiceTimeout:原因是Service的生命周期函数在20s内无法完成处理。
 

为了避免ANR异常,android使用了Handler消息处理机制。让耗时操作在子线程运行。

因此产生了3个疑惑:

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

2.哪里有相关代码为这个死循环准备了一个新线程去运转? 

3.Activity的生命周期方法都是在主线程里执行的,那这些生命周期方法是怎么实现在死循环体外能够执行起来的?

要完全彻底理解这个问题,需要准备以下4方面的知识:Process/Thread,Android Binder IPC,Handler/Looper/MessageQueue消息机制,Linux pipe/epoll机制。

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

这里涉及线程,先说说说进程/线程,进程:每个app运行时前首先创建一个进程,该进程是由Zygote fork出来的,用于承载App上运行的各种Activity/Service等组件。进程对于上层应用来说是完全透明的,这也是google有意为之,让App程序都是运行在Android Runtime。大多数情况一个App就运行在一个进程中,除非在AndroidManifest.xml中配置Android:process属性,或通过native代码fork进程。

线程:线程对应用来说非常常见,比如每次new Thread().start都会创建一个新的线程。该线程与App所在进程之间资源共享,从Linux角度来说进程与线程除了是否共享资源外,并没有本质的区别,都是一个task_struct结构体,在CPU看来进程或线程无非就是一段可执行的代码,CPU采用CFS调度算法,保证每个task都尽可能公平的享有CPU时间片

有了这么准备,再说说死循环问题:

对于线程既然是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出,例如,binder线程也是采用死循环的方法,但不是简单地死循环。但这里可能又引发了另一个问题,既然是死循环又如何去处理其他事务呢?通过创建新线程的方式。

真正会卡死主线程的操作是在回调方法onCreate/onStart/onResume等操作时间过长,会导致掉帧,甚至发生ANR,looper.loop本身不会导致应用卡死。

主线程的死循环一直运行是不是特别消耗CPU资源呢? 其实不然,这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,详情见Android消息机制1-Handler(Java层),此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程很多时候都是处理休眠状态,并不会消耗大量CPU资源。

 

(2) 哪里有相关代码为这个死循环准备了一个新线程去运转? 

事实上,会在进入死循环之前便创建了新binder线程,在代码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线程,具体过程,可查看 startService流程分析,这里不展开说,简单说Binder用于进程间通信,采用C/S架构。关于binder感兴趣的朋友可查看为什么Android要采用Binder作为IPC机制? - Gityuan的回答

 

(3) Activity的生命周期是怎么实现在死循环体外能够执行起来的?

Activity的生命周期都是依靠主线程的Looper.loop,当收到不同Message时则采用相应措施:

看看Looper.loop()方法:

public static final void loop() {  
    Looper me = myLooper();  
    MessageQueue queue = me.mQueue;  
    // 开始了死循环
    while (true) {  
        Message msg = queue.next(); // might block  
        if (msg != null) {  
            if (msg.target == null) {  
                return;  
            }  
            ......
            msg.target.dispatchMessage(msg);  
            ......
            msg.recycle();  
        }  
    }  
}
看看H.handleMessage(msg)方法,根据接收到不同的msg,执行相应的生命周期。

public void handleMessage(Message msg) {
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;
                case PAUSE_ACTIVITY:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
                    handlePauseActivity((IBinder)msg.obj, false, (msg.arg1&1) != 0, msg.arg2,
                            (msg.arg1&2) != 0);
                    maybeSnapshot();
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;

            ...........
}
 

主线程的消息又是哪来的呢?当然是App进程中的其他线程通过Handler发送给主线程,请看接下来的内容:

主线程Looper.loop()里的死循环不ANR疑惑_第1张图片

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下去。


另外,

ActivityThread实际上并非线程,不像HandlerThread类,ActivityThread并没有真正继承Thread类,只是往往运行在主线程,该人以线程的感觉,其实承载ActivityThread的主线程就是由Zygote fork而创建的进程。

 

总结:

Looer.loop()方法可能会引起主线程的阻塞,但只要它的消息循环没有被阻塞,能一直处理事件就不会产生ANR异常。

 

你可能感兴趣的:(面试题)