Handler常见面试问题

从源码解释Handler常见面试问题

Tip:问题均从网上收集,参照了部分解答,在此谢过。

Q1:消息机制Hander是什么?有什么作用?有哪些要素?流程是怎样的?

1、Handler是什么?

是Android中一套在应用内使用的消息机制。

2、有什么作用

通过该机制,我们得以方便的发送消息以及收消息,我们常见的UI更新机制也是基于Handler运行。

3、有哪些要素

Handler的核心有这“三大件”:

Handler:提供给使用者的入口(发送、收消息)

MessageQueue:消息(Message)的队列,存储消息顺序

Looper:消息分发的驱动器,loop()一直在轮询(while(true))是否有消息,并分发消息

4、流程是怎么样的?

使用知乎大佬的图解释一下:

其他元素的职责在上面介绍过,这里说下Message的分类大概有哪些:

Message:消息,分为硬件产生的消息(如:按键、触摸、绘制等)和软件产生的消息

事实上,在整个消息循环的流程中,并不只有Java层参与,很多重要的工作都是在C++层来完成的。我们来看下这些类的调用关系。(copy from :https://zhuanlan.zhihu.com/p/38373877)

注:虚线表示关联关系,实线表示调用关系。

在这些类中MessageQueue是Java层与C++层维系的桥梁,MessageQueue与Looper相关功能都通过MessageQueue的Native方法来完成,而其他虚线连接的类只有关联关系,并没有直接调用的关系,它们发生关联的桥梁是MessageQueue。

Q2:为什么系统不建议在子线程访问UI?

避免多线程更新造成的一系列问题。

Q3:一个Thread可以有几个Looper?几个Handler?

1、一个Thread只有一个Looper,这个是通过ThreadLocal来保证的。

2、可以有多个Hander,Handler和Looper的关系是多对一的。

Q4:如何将一个Thread线程变成Looper线程?Looper线程有哪些特点?

调用Loop.prepareMainLooper,使线程的Looper变成主线程。

调用Looper.prepare(),就可以为当前线程创建一个Looper副本。

Q5:可以在子线程直接new一个Handler吗?那该怎么做?

不可以的,因为在子线程,所以这个Handler一定需要指定由哪个Looper分发消息;

所以在创建之前一定需要调用Looper.prepare()方法。

主线程在ActivityThread已经调用了,创建了mainLooper。

Q6:Message可以如何创建?哪种效果更好,为什么?

Message创建的时候android官方提倡使用Handler.obtainMessage()。

因为Message中保存了一个静态的Message对象链表,使用享元模式,减少了消息内存的占用

从复用的链表中获取一个对象,如果没有就创建。

public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

什么时候加入到对象链表中呢?

public void recycle() {
    if (isInUse()) {
        if (gCheckRecycle) {
            throw new IllegalStateException("This message cannot be recycled because it "
                    + "is still in use.");
        }
        return;
    }
    recycleUnchecked();
}

消息使用完成调用recycle就可以回收该对象。

Q7:这里的ThreadLocal有什么作用?

ThreadLocal为每个线程保存了一个Looper副本。

ThreadLocal可以看这里:https://www.cnblogs.com/jasongj/p/8079718.html

Q8:主线程中Looper的轮询死循环为何没有阻塞主线程?

主线程中的Looper是保证应用一直运行的关键,我们常说的ANR实际上是占用主线程时间过长。

ActivityThread中初始化MainLooper

public static void main(String[] args) {
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
    SamplingProfilerIntegration.start();
    //...
    Looper.prepareMainLooper();

    ActivityThread thread = new ActivityThread();
    thread.attach(false);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();//mainLooper一直循环,保证应用进程一直存活

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

Q9:使用Hanlder的postDealy()后消息队列会发生什么变化?

调用了postDealy之后,MessageQueue会设置Message.when(保存消息应该什么时候执行)的值,

通过该值,MessageQueue会该消息插入到自己保存的消息队列(链表实现)中。

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }

    synchronized (this) {
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            Log.w(TAG, e.getMessage(), e);
            msg.recycle();
            return false;
        }

        msg.markInUse();
        msg.when = when;//设置应该什么时候执行
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // Inserted within the middle of the queue.  Usually we don't have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            //在链表中插入当前消息,根据执行的时间
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

Q10:如何解决Handler导致的泄漏问题

Handler导致泄漏的根本原因是Handler创建的时候为应用的内部类,持有了外部类的引用,

这个时候遇到有消息执行比较久(如:网络请求等),就会导致已经退出Activity了但是其引用还被Handler持有。

解决方案有两个建议:

1、实例化Handler的时候尽量创建一个静态内部类

2、保存外部Context的引用的时候使用弱引用