美文网首页每日一题-面试指导
每日一题: Handler源码

每日一题: Handler源码

作者: 林锐波 | 来源:发表于2017-07-24 17:18 被阅读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) 区别:

    • while区别根据编译器不同情况有所不同,例如写死循环while(true)有的编译器会傻傻的每次都把true做一下判断.
    • for(;;)写死循环比较好,减少了判断
      编译前 编译后
      while (true){todo}; mov eax,true
      test eax,eax
      je foo+23h
      jmp foo+18h

    编译前 编译后
    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的泄漏?

    • 问题分析
      Handler泄露的关键点有两个:
      1. 内部类
      2. 生命周期和Activity不一定一致

    看看下面代码

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

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

    • 解决方式:
      如果Handler中有延迟的任务或者是等待执行的任务队列过长,都有可能因为Handler继续执行而导致Activity发生泄漏。
      此时的引用关系链是:
      Looper -> MessageQueue -> Message -> Handler -> Activity

    解决方案

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

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

    • Activity的生命周期都是运行在 Looper.loop() 的控制之下,当收到不同Message时则采用相应措施,如果它停止了,应用也就停止了.
    • Android的消息是一种事件机制,而looper.loop() 不断地接收事件、处理事件,只要每次处理的事件不被looper堵塞那么,就不会爆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】

    相关文章

      网友评论

        本文标题:每日一题: Handler源码

        本文链接:https://www.haomeiwen.com/subject/dhgbkxtx.html