Handler面试题总结

1、一个线程有几个Handler?
一个线程有任意个Handler,可以new多个Handler,但最终同一线程多个Handler发的消息都在同一个Looper去处理。

2、一个线程有几个Looper?如何保证?
一个线程只有一个Looper。
首先看下Looper中有这么一个static final变量

//Looper.java
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

会去new一个ThreadLocal对象,ThreadLocal是一个线程上下文变量,用于存储线程上下文。而在ThreadLocal中会有一个静态内部类,ThreadLocalMap。并且在Thread里面

//Thread.java
ThreadLocal.ThreadLocalMap threadLocals = null;

取到threadLocals是一个HashMap,HashMap是一个<key,value>键值对,key-value是一一对应的,所以在thread中只有一个threadLocals。
在Looper.prepare时,调用sThreadLocal.set(new Looper()),set里面是这样的

    //ThreadLocal.java
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

获取当前线程,并且取到当前线程的ThreadLocalMap,也就是刚才说的threadLocals。并set一个键值对<sThreadLocal,Looper>,并且是一一对应的。而且如果sThreadLocal对应有唯一值后,不能再被set替换,这就是为什么只能prepare一次。所以,一个sThreadLocal对应一个Looper。而sThreadLocal是static final变量,唯一的,所以一个线程中只有一个Looper。

3、Handler内存泄漏的原因?为什么其他的内部类没有说过这个问题?
首先Handler是匿名内部类,持有外部类的引用。再来看看MessageQueue中有大量的Message,而在发送Message时候把Handler与Message绑定。所以,Message中有持有Handler,Handler持有外部类的引用,因此Handler持有Activity的引用,Activity中有大量的内存。当Message是延迟消息时,Activity走了onDestroy(),导致Activity无法被释放回收。
解决问题是:在onDestroy里面清除MessageQueue中所有的Message;将handler声明为静态内部类,用软引用的或者弱引用持有Activity。

4、不断往MessageQueue里面放消息,为什么不会导致OOM?
首先,OOM的原因是找不到一个连续的想要大小的空间(有空间但是没有连续的想要大小的空间),而Message的空间是非常小的,只会导致手机内存空间不足,不会导致OOM。

5、为什么主线程可以new Handler?如果想要在子线程中new Handler要做哪些工作?
主线程在ActivityThread的main()中就做了new Handler的准备工作了;而在子线程new Handler时候,一般都会这么做:

class MyThread : Thread() {
    private var mLooper:Looper? = null
    override fun run() {
        Looper.prepare()
        mLooper = Looper.myLooper()
        Looper.loop()
    }
}
//
val myThread = MyThread()
myThread.start()
val handler = myThread.mLooper?.let { Handler(it) }

看上去没啥问题,但这样做有一个问题,因为Thread是异步线程,不能保证mLooper是取到值的。所以在子线程里面new Handler时候,要通过synchronized、wait和notifyAll来配合使用,这篇文章有讲到线程间通信。或者可以看看HandlerThread,就是这么实现的。

6、子线程中维护Looper,消息队列无消息的时候的处理方案是什么?有什么用?主线程呢?
假如Looper处理完后,没有退出,会导致子线程没有消息时候一直存在,导致内存泄漏。解决办法是调用MessageQueue中的quit

    //MessageQueue.java
    void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }

把mQuitting设为true,然后把消息队列的消息全部清空,再唤醒线程。

    //MessageQueue.java
    Message next() {
    ···
     if (mQuitting) {
       dispose();
       return null;
     }
    ···
    }

返回null,Looper.loop()中取到null,退出死循环,loop循环结束,导致线程结束,线程结束内存释放。
主线程:主线程不能quit,主线程quit会报异常"Main thread not allowed to quit.",为什么?因为几乎从 App 启动,到 Activity 相关的生命周期,Services 相关的生命周期,到 App 退出等等,都是通过 Handler 来驱动的,如果把主线程的Looper退出死循环,那么后续将无法正常工作。

7、既然可以存在多个Handler往MessageQueue中添加数据(发消息时各个Handler可能处于不用线程),那它内部是如何确保线程安全的?取消息呢?
在MessageQueue中,enqueueMessage是存消息的操作,其中有synchronized来保证每次只有一个Handler来操作enqueueMessage的过程:

//MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
···
  synchronized (this) {
  ···
  }
···
}

而取消息呢,是通过next来取消息,同样里面有synchronized,但是取消息是只有在Looper.loop的时候去取,为什么要加synchronized?

//MessageQueue.java
Message next() {
···
  synchronized (this) {
  ···
  }
···
}

因为存消息的时候也用了synchronized,并且用同一个this作为监视器,所以操作的是同一个队列时,要保证存取操作不能同时发生。HashMap之所以线程不安全,是因为操作同一个链表时,存和取的操作同时发生。

8、使用Message时如何去创建?为什么?
Message中有一个消息池,使用完后的消息不会被GC回收,而是都会把消息内容都清除,并放进消息池中,可以被复用。这样做可以减少new Message的过程。每new Message,就会有一块内存分配给它;每分配一块内存,程序的可用内存也就少一块;当程序被占用的内存达到一定临界程度,GC 也就是垃圾回收器(Garbage Collector)就会出动,来释放掉一部分不再被使用的内存。被回收后,虽然内存空间变大了,但可能找不到一块连续容量大的内存,会出现OOM问题;而且,Android本身是基于Handler来处理消息的,假如每次都是new Message,在1S内会new上百个上千个Message,就会频繁的调用GC,导致内存抖动,而内存抖动会导致卡顿。

9、Handler的消息阻塞是怎么实现的?为什么主线程不会阻塞?
ANR产生的原因是Message没能在确定的时间内处理完成,而Looper是不管Message有没有处理完,只要MessageQueue里面有消息,那么就会出来去给Handler处理,当没有消息时,Looper会阻塞挂起。Looper的阻塞是因为没消息,所以ANR和Looper是死循环是两码事,没有关系。所以应用没有消息的时候,会处于睡眠状态,cpu会释放资源,而ANR是消息没及时得到处理。

最后,附上Handler源码解析

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

推荐阅读更多精彩内容