从几个面试题来深入了解Handler机制

Handler机制是面试官非常喜欢问的知识点,有关Handler的面试题也是五花八门,举几个例子: Looper的loop()方法中死循环为什么不会造成ANR?死循环会造成cpu资源浪费吗?一个线程中Handler、Looper、MessageQueue的数量?它们的数量源码中怎么实现的?MessageQueue的数据结构是什么样子?为何主线程可以使用 Handler?如果想要在子线程中使用 Handler 要做些什么?这些题目从各个角度考察对handler源码的理解。只有对Handler的源码深入理解,才能够轻松应对。下面列举一些常见的handler面试题(包括上面几个例子)并结合源码解析

1.Looper的loop()方法中死循环为什么不会造成ANR?死循环会造成cpu资源浪费吗?

Android是事件驱动的,在Loop.loop()中不断接收事件、处理事件,而Activity的生命周期都依靠于主线程的Loop.loop()来调度。整个Android就是在一个Looper的loop循环的,整个Android的一切都是以Handler机制进行的,即只要有代码执行都是通过Handler来执行的,而所谓ANR便是Looper.loop没有得到及时处理。真正会卡死主线程的操作是在回调方法onCreate/onStart等操作时间过长,会导致掉帧,甚至发生ANR,looper.loop本身不会导致应用卡死。(有兴趣可以了解ANR相关源码,了解ANR发生原因)。
死循环会造成cpu资源浪费吗?
安卓在这个问题上使用了Linux 中的pipe/epoll机制进行优化,looper的loop()方法最终会调用MessageQueue的next()方法出队, next方法中会调用nativePollOnce()方法,这是一个nativie方法对应安卓C++层android_os_MessageQueue.cpp的android_os_MessageQueue_nativePollOnce(),最终会调用到Looper.cpp的一些方法完成阻塞操作,此时主线程会释放CPU资源进入休眠状态。Handler的sendMessage()方法最终会调用MessageQueue的enqueueMessage()方法消息入队,而其中会调用nativeWake()方法来唤醒。当没有事件需要处理时,主线程就会阻塞;当子线程往消息队列发送消息,并且往管道文件写数据时,主线程就被唤醒。所以,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。


Message next() {
       ......
        for (;;) {
            ......

			// nativePollOnce  native方法  这里进入等待唤醒状态
            nativePollOnce(ptr, nextPollTimeoutMillis);

            	synchronized (this) {
             
	              //出队操作
          	 }
		}
             ......
    }
boolean enqueueMessage(Message msg, long when) {
......
        synchronized (this) {
	......
           //入队操作
      }

            if (needWake) {
            //这是一个nativie方法,这里唤醒 nativePollOnce 
                nativeWake(mPtr);
            }
        }
        return true;
    }

2.一个线程中Handler、Looper、MessageQueue的数量?它们的数量源码中怎么实现的?

答:一个线程中只有1个Looper,1个MessageQueue,可以有多个Handler。我们知道要在子线程创建handler需要先调用Looper.prepare()来创建looper,否则会报错。源码里面已经做了说明。

 public Handler(Callback callback, boolean async) {
   ......
        mLooper = Looper.myLooper();
        if (mLooper == null) {//判断Looper是否被创建
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

再来看一下Looper.prepare()做了什么

private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

ThreadLoal 是线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。ThreadLocal在当前线程维护一份Looper的实例。所以一个线程只有一个Looper,而MesssageQueue会随Looper创建时一起创建。

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

所以也只有一个MesssageQueue,而handler则没有限制可以有多个。

3.MessageQueue的数据结构是什么样子?

查看源码可以发现MessageQueue并不是队列的数据结构,其中的元素都是通过next引用连接,是一个单链表结构。
在这里插入图片描述

4.为什么在子线程使用handler报错,在主线程就可以使用 Handler?如果想要在子线程中使用 Handler 要做些什么?

为什么在子线程使用handler报错,在主线程就可以使用 Handler。可以再在Handler的构造方法中找到答案

public Handler(@Nullable Callback callback, boolean async) {
......
mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
  ......
}        

可以看到是因为没有调用 Looper.prepare()创建looper。那主线程为什么不报错?查看在程序的入口ActivityThread的main 方法 里边调用了Looper.prepareMainLooper()来创建looper和Looper.loop()来开启循环。子线程如何使用handler?自然是需要再子线程调用Looper.prepare和Looper.loop()

5.hanlder会造成内存泄漏?

handler造成的内存泄漏可以归为内部类造成的内存泄漏,我们通常使用匿名内部类的形式来使用Handler,内部类就会持有外部类引用,当内部类有消息未处理完时,内部类无法回收就会造成外部类也无法回收造成内存泄漏。

6.handler机制如何保证线程安全?

可以看第一个问题中贴出的源码,message的入队操作都是在同步代码块中进行的。

你可能感兴趣的:(安卓)