Android的Handler常见的面试问题

本文是对常见面试问题的分析,关于Handler的运行机制详细分析见:Android Handler的源码分析

常见问题包括:

1、Handler是怎么实现切换线程的?
2、handler.sendMessage()与handler.post()的区别?
3、MessageQueue是怎么增删消息的?
4、一个线程可以有几个Handler?几个Looper?几个MessageQueue?
5、A Handler发送的消息为什么不会跑到B Handler的handleMessage()方法中?
6、简述ThreadLoacal的原理?
7、Handler引起的内存泄漏?
8、在UI中创建的Handler,通过post方式发送的消息在run方法中可以进行UI更新吗?

1、Handler是怎么实现切换线程的?

  • 创建Handler:在创建时需要先获取该线程(一般为UI线程)对应的Looper信息;
  • 发送消息:在子线程数据处理完成后,发送消息,在发送消息前将该Handler作为target,将消息发送至MessageQueue中;
  • 消息处理:通过Looper的loop方法,不断获取消息信息,根据消息属性中的target分发消息,从而完成了子线程至主线程的消息处理。

2、handler.sendMessage()与handler.post()的区别?
查看Handler可以发现,最终发送至MessageQueue的方法都是Handler中的enqueueMessage(),但是对于Message的处理,在分发时,会将消息的结果返回至其callback 中(如果不为空)。

public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
 private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

3、MessageQueue是怎么增删消息的?
增加:通过enqueueMessage方法,通过对消息添加时间和之前消息的判断,将新添加的消息放置在栈顶

msg.next = p; // invariant: p == prev.next
prev.next = msg;

删除:通过next方法取出消息,在loop()中进行消息的分发处理,在next返回消息前对消息属性进行修改

// MessageQueue中的next()方法
msg.next = null;
msg.markInUse();
// Message中的markInUse()方法,表明消息已经使用
void markInUse() {
        flags |= FLAG_IN_USE;
    }

4、一个线程可以有几个Handler?几个Looper?几个MessageQueue?
多个Handler?
**多个:**上述描述中,知道之所以消息可以准确的分发处理,是由于通过Message的target方法在添加和分发时标注Handler,实现准确的分发,因此同一个线程可以创建多个Handler对象,例如常见的UI线程中:

 private Handler mHandler= new Handler(Looper.myLooper()){...}

几个Looper?
一个:在2.2中主线程发送至子线程中,需要先调用Looper.prepare()方法,否则会抛出异常,那如果一个线程中包含多个Looper会怎样?
查看源码可以发现,如果包含多个Looper对象会抛出异常。

 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));
    }

几个MessageQueue?
一个:在调用Looper.prepare()时,会判断该线程的Looper是否为空,只有为空的情况才会调用Looper的构造方法,创建MessageQueue,因此只有一个。

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

5、A Handler发送的消息为什么不会跑到B Handler的handleMessage()方法中?
参考问题1和4可知,一个线程中包含多个Handelr对象,而在消息的添加分发时,通过Message的target(Handler)标注。

6、简述ThreadLoacal的原理?
在3.1中新建Handler时,已对ThreadLoacal简述,通过set和get方法,其内部维护这一个Map集合,其中key是当前的线程,value为需要存储的值。

7、Handler引起的内存泄漏?
可参考Android中使用Handler为何造成内存泄漏?中相关的介绍,主要是由于在消息处理时,当前Activty已经关闭,但是仍有消息处理,导致当前Activty无法正常消耗出现内存泄漏的情况,解决方案:

  1. Activity销毁时及时清理消息队列;
  2. 自定义静态Handler类+软引用。

详细分析参考:Android Handler之原理解析

8、在UI中创建的Handler,通过post方式发送的消息在run方法中可以进行UI更新吗?
可以:通过查看源码可以发现,其最终调用的还是sendMessage相关的方法,但是如果传入的Runnable形式,其内部会对其进行了封住成Message类。

 private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

在消息处理结束后,其回调至Runnable 的run方法中,需要注意的是这里的仍然是在UI线程中,因为我们创建的Handler是在UI线程中,且Handler将Runnable内部封住成Message的形式,在消息分发时首先检测callback是否为空,如下所示:

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            ...
        }
    }

如果不为空则调用handleCallback方法,但是该方法实际上是调用message.callback.run(),而message.callbac是我们在添加消息时赋值的Runable,所以最终调用的是Runable中的run()方法,因此还是回到UI线程中,可以更新UI界面。

private static void handleCallback(Message message) {
        message.callback.run();
    }
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值