腾讯Android面试:Handler中有Loop死循环,为什么没有阻塞主线程,原理是什么

本文深入探讨了Android主线程中Handler的Loop死循环为何不会阻塞主线程及其原理,涉及主线程消息循环机制、资源消耗、内存泄漏处理等关键点。解释了Looper.loop()如何利用Linux的pipe/epoll机制实现低资源消耗,并讨论了子线程中更新UI的各种方法,如Handler、runOnUiThread、View.post等。
摘要由CSDN通过智能技术生成

面试官: Handler中有Loop死循环,为什么没有阻塞主线程,原理是什么
心理分析:该问题很难被考到,但是如果一旦问到,100%会回答不上来。开发者很难注意到一个主线程的四循环居然没有阻塞住主线程

求职者:应该从 主线程的消息循环机制 与Linux的循环异步等待作用讲起。最后将handle引起的内存泄漏,内存泄漏一定是一个加分项

前言

Android的消息机制主要是指Handler的运行机制,对于大家来说Handler已经是轻车熟路了,可是真的掌握了Handler?本文主要通过几个问题围绕着Handler展开深入并拓展的了解。

站在巨人的肩膀上会看的更远。大家有兴趣的也可以到Gityuan的博客上多了解了解,全部都是干货。而且他写的东西比较权威,毕竟也是小米系统工程师的骨干成员。

先上一份整理好的面试目录

  1.  

Questions

  1. Looper 死循环为什么不会导致应用卡死,会消耗大量资源吗?

  2. 主线程的消息循环机制是什么(死循环如何处理其它事务)?

  3. ActivityThread 的动力是什么?(ActivityThread执行Looper的线程是什么)

  4. Handler 是如何能够线程切换,发送Message的?(线程间通讯)

  5. 子线程有哪些更新UI的方法。

  6. 子线程中Toast,showDialog,的方法。(和子线程不能更新UI有关吗)

  7. 如何处理Handler 使用不当导致的内存泄露?

回答一: Looper 死循环为什么不会导致应用卡死?

线程默认没有Looper的,如果需要使用Handler就必须为线程创建Looper。我们经常提到的主线程,也叫UI线程,它就是ActivityThread,ActivityThread被创建时就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。

首先我们看一段代码

  new Thread(new Runnable() {
        @Override
        public void run() {
            Log.e("qdx", "step 0 ");
            Looper.prepare();

            Toast.makeText(MainActivity.this, "run on Thread", Toast.LENGTH_SHORT).show();

            Log.e("qdx", "step 1 ");
            Looper.loop();

            Log.e("qdx", "step 2 ");

        }
    }).start();

我们知道Looper.loop();里面维护了一个死循环方法,所以按照理论,上述代码执行的应该是 step 0 –>step 1 也就是说循环在Looper.prepare();与Looper.loop();之间。

在子线程中,如果手动为其创建了Looper,那么在所有的事情完成以后应该调用quit方法来终止消息循环,否则这个子线程就会一直处于等待(阻塞)状态,而如果退出Looper以后,这个线程就会立刻(执行所有方法并)终止,因此建议不需要的时候终止Looper。

执行结果也正如我们所说,这时候如果了解了ActivityThread,并且在main方法中我们会看到主线程也是通过Looper方式来维持一个消息循环

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

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

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();

    //如果能执行下面方法,说明应用崩溃或者是退出了...
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

那么回到我们的问题上,这个死循环会不会导致应用卡死,即使不会的话,它会慢慢的消耗越来越多的资源吗?

对于线程即是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出,例如,binder线程也是采用死循环的方法,通过循环方式不同与Binder驱动进行读写操作,当然并非简单地死循环,无消息时会休眠。但这里可能又引发了另一个问题,既然是死循环又如何去处理其他事务呢?通过创建新线程的方式。真正会卡死主线程的操作是在回调方法onCreate/onStart/onResume等操作时间过长,会导致掉帧,甚至发生ANR,looper.loop本身不会导致应用卡死。

主线程的死循环一直运行是不是特别消耗CPU资源呢? 其实不然,这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值