每日一题-面试指导

每日一题: Handler源码

2017-07-24  本文已影响292人  林锐波

每日一题: Handler源码

深入了解handler

面试率: ★★★☆☆

面试技巧与建议

handler作为Android的主线程,是其主要的消息机制,作为的软件开发人员,掌握并熟知handler底层运行原理已经成为了Android开发的一种标配,面试必问.

面试建议

handler涉及很广,我们可以选择适合自己的深度来局部掌握该面试题,为什么局部掌握,因为全局去了解耗费精力太大,而且原理逻辑繁杂,不适合快速吸收.因此下面我给出了两种方案:
深入者如Looper、Handler、Message三者关系
浅入者如handler性能优化,常见方法使用与区别,handler与子线程等相关问题.

面试技巧

其实可以从侧面的角度去分析这个问题,如实际项目中如何正确的使用handler.使用时遇到过什么问题,如何解决该问题.

面试题

下面是从handler的源码和实际开发中提取出的一些面试问题,从实际中出发探讨handler源码,面试中最恰当不过的事情,莫过于此.

一个线程可以有几个Lopper实例,为什么?

一个线程中只有一个Looper实例.

sThreadLocal是一个ThreadLocal对象,可以在一个线程中存储变量。可以看到,在第5行,将一个Looper的实例放入了ThreadLocal,并且2-4行判断了sThreadLocal是否为null,否则抛出异常。这也就说明了Looper.prepare()方法不能被调用两次,同时也保证了一个线程中只有一个Looper实例.

源码

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

继续看Lopper的构造方法做了什么,那里面的loop()方法呢,知道finalfinal Looper me = myLooper()的作用吗?

构造方法中,创建了一个MessageQueue(消息队列)

final Looper me = myLooper()的final保证了loopr的唯一性.

构造方法源码:

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

loop()方法源码.

public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;//final了消息队列

        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {
            Message msg = queue.next(); //阻塞轮询消息
            if (msg == null) {      
                return;
            }
            
          //其他源码省略....  
          }

通过前面1,2问题可以总结下Looper的主要作用是什么?

Looper主要作用:

  1. 与当前线程绑定,保证一个线程只会有一个Looper实例,同时一个Looper实例也只有一个MessageQueue。
  2. loop()方法,不断从MessageQueue中去取消息,交给消息的target属性的dispatchMessage去处理。

首先Looper.prepare()在本线程中保存一个Looper实例,然后该实例中保存一个MessageQueue对象;因为Looper.prepare()在一个线程中只能调用一次,所以MessageQueue在一个线程中只会存在一个。

从Handler中有没学到什么好的技术,或者思想?

看过MessageQueue吗,里面的for (;;) 和while(true) 区别:

编译前 编译后
for (;;){}; jmp foo+23h

一目了然,for (;;)指令少,不占用寄存器,而且没有判断跳转,比while (true)好。

Handler如何与MsgQueue关联在一起?

这个问题可以先观察下Handler的构造方法到底做了什么?

首先得到当前线程中保存的Looper实例,进而与Looper实例中的MessageQueue想绑定关联.

public Handler(Callback callback, boolean async) {
  //部分源码省略...
  
  //获取当前线程保存的Looper实例
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
//通过上面mLooper获取了实例中保存的MessageQueue,这样两者就绑定在一起了,关联上.
        mQueue = mLooper.mQueue;  
        mCallback = callback; 
        mAsynchronous = async;  

使用handler发送Message的流程是什么?

sendMessage(hd) ->sendMessageAtTime(hd) ->
enqueueMessage(hd) ->dispatchMessage(looper) ->
handleMessage(looper)

那么在Activity中,我们并没有显示的调用Looper.prepare()和Looper.loop()方法,为啥Handler可以成功创建呢?

这是因为在Activity的启动代码中,已经在当前UI线程ActivityThread调用了Looper.prepare()和Looper.loop()方法。

子线程里可以创建handler吗?

可以的,可以在一个子线程中去创建一个Handler,然后使用这个handler实例在任何其他线程中发送消息,最终处理消息的代码都会在你创建Handler实例的线程中运行。

详情见下面实例:

class LooperThread extends Thread {
    public Handler mHandler;

    public void run() {
        Looper.prepare(); //创建Looper并与本线程绑定【第一步】

        mHandler = new Handler() {
            //定义并实现Handler.handleMessage方法【第二步】
            public void handleMessage(Message msg) {
                // process incoming messages here
            }
        };
        Looper.loop(); // 启动Looper消息循环【第三步】
    }
}

handler会不会导致Activity的泄漏?

看看下面代码

public class MainActivity extends QActivity {
//这里应该使用static否则容易泄漏
         class MyHandler extends Handler {
                ... ...
        }
}

内部类持有外部类Activity的引用,当Handler对象有Message在排队,则无法释放( 比如activity已经destory了但是MessageQueen还有消息则,looper就会在轮询,因此activity就无法被释放,因内部类有act引用),进而导致Activity对象不能释放。

解决方案

  1. 可以在UI退出之前,执行remove Handler消息队列中的消息与runnable对象。
  2. 使用Static + WeakReference的方式来达到断开Handler与Activity之间存在引用关系的目的。

主线程中的Looper.loop()一直无限循环为什么不会造成ANR?

让我们先看一遍造成ANR的原因,就明白了

造成ANR的原因一般有两种:
1. 当前的事件没有机会得到处理(即主线程正在处理前一个事件,没有及时的完成或者looper被某种原因阻塞住了)
2. 当前的事件正在处理,但没有及时完成

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

详情见

总结

看了代码之后,觉得它一点都不神秘,不就是实现了我们常用的“消息驱动机制”吗?
消息驱动机制的四要素:
1. 接收消息的“消息队列”
2. 阻塞式地从消息队列中接收消息并进行处理的“线程”
3. 可发送的“消息的格式”
4. “消息发送函数”

以上四要素在Android中实现对应的类如下:
1. 接收消息的“消息队列” ——【MessageQueue】
2. 阻塞式地从消息队列中接收消息并进行处理的“线程” ——【Thread+Looper】
3. 可发送的“消息的格式” ——【Message<Runnable被封装在Message中>】
4. “消息发送函数”——【Handler的post和sendMessage】
上一篇下一篇

猜你喜欢

热点阅读