每日一题: Handler源码

每日一题: 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】
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,176评论 5 469
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,190评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,232评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,953评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,879评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,177评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,626评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,295评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,436评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,365评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,414评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,096评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,685评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,771评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,987评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,438评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,032评论 2 341

推荐阅读更多精彩内容