Android学习笔记:Looper.loop 死循环为什么不会阻塞掉 UI 线程导致 ANR ?

ActivityThread 是应用程序的入口,在 ActivityThread 中的 main 方法是整个java程序的入口。ActivityThread 的 main 方法主要就是做消息循环,一旦退出消息循环,那么你的程序也就可以退出了。

public static void main(String[] args) {
    ...
    Looper.prepareMainLooper();
    ActivityThread thread = new ActivityThread();
    initPCArgs(thread, args);
    thread.attach(false);
    ...
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

在 main 方法中会创建一个主线程的 Looper 对象,而loop源码中是一个死循环在遍历消息队列来取消息。

其实整个 Android 系统就是在一个Looper.loop() 循环的,整个Android的一切都是以Handler 机制进行的,即只要有代码执行都是通过Handler来执行的,而所谓ANR便是Looper.loop没有得到及时处理,一旦没有消息需要处理时,主线程就会被阻塞休眠,当子线程往消息队列发送消息,并且往管道文件写数据时,主线程就被唤醒。

这里涉及到了Linux的 pipe/epoll 机制,在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce() 方法里,此时主线程会释放CPU的资源进入休眠状态,直到下一个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立即通知相应程序进行读或写操作。

在Handler消息处理机制中有每个activity生命周期状态所对应的case,通过switch生命周期状态,loop循环里会分发消息给Handler,从而执行对应的代码,并不会导致loop的卡死。

public void handleMessage(Message msg) {
    if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
    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, "LAUNCH_ACTIVITY");
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        } break;
        case RELAUNCH_ACTIVITY: {
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
            ActivityClientRecord r = (ActivityClientRecord)msg.obj;
            handleRelaunchActivity(r);
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        } break;
        case PAUSE_ACTIVITY: {
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
            SomeArgs args = (SomeArgs) msg.obj;
            handlePauseActivity((IBinder) args.arg1, false, (args.argi1 & USER_LEAVING) != 0, args.argi2, (args.argi1 & DONT_REPORT) != 0, args.argi3);
            maybeSnapshot();
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        } break;
        case PAUSE_ACTIVITY_FINISHING: {
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
            SomeArgs args = (SomeArgs) msg.obj;
            handlePauseActivity((IBinder) args.arg1, true, (args.argi1 & USER_LEAVING) != 0,
            args.argi2, (args.argi1 & DONT_REPORT) != 0, args.argi3);
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        } break;
        case STOP_ACTIVITY_SHOW: {
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
            SomeArgs args = (SomeArgs) msg.obj;
            handleStopActivity((IBinder) args.arg1, true, args.argi2, args.argi3);
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        } break;
        ...

 

总结:

ActivityThread的 main 方法的主要作用就是做消息循环,一旦退出消息循环,主线程运行完毕,那么你的应用也就退出了。Android是事件驱动的,在Looper.loop()中不断接收事件、处理事件,而Activity的生命周期都依靠于主线程的 Loop.loop() 来调度,所以可想而知它的存活周期和 Activity 也是一致的。当没有事件需要处理时,主线程就会阻塞;当子线程往消息队列发送消息,并且往管道文件写数据时,主线程就被唤醒。真正会卡死主线程的操作是在执行回调方法 onCreate/onStart/onResume 等操作的时间过长,导致掉帧,甚至发生ANR,looper.loop() 本身不会导致应用卡死。

主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值