阅读 34

Android面试题精选——聊一聊Android Handler机制

前言

每次面试,handler基本上被问到的概率超过百分之八九十。同学们有咩有想过为什么面试官特别喜欢问handler?

这是因为handler能延伸出很多的面试点。不信的话,我们来看:

请说说Handler、Looper、MessageQueue

Handler机制在Android中是一个非常重要的知识点,在我们的平常开发中也是经常使用到的。在Android的面试中Handler机制更是必考的题目,而且题目也很单一:请说说Handler、Looper、MessageQueue之间的关系。这个问题无论是我去面试还是我面试别人,都会问到的一个问题。如果你遇到了这个问题,你只是简单的说一下它们是什么什么关系,那是远远不够的。这道题考察的无非就是你对Handler机制的实现和它的工作原理的了解。下面我们就通过Handler机制的工作流程图和源码来详细分析Handler实现细节和工作原理。

下面想看Handler的工作流程图:(第一次画图,有点丑,凑合着看吧)

因为Handler的主要作用就是线程切换,所以在图中我把Handler线程变化也画了出来。从这张图我们能看出几点信息:

1、Handler负责消息的发送和处理:Handler发送消息给MessageQueue和接收Looper返回的消息并且处理消息。

2、Looper负责管理MessageQueue:Looper会不断地从MessageQueue取出消息,交给Handler处理。

3、MessageQueue是消息队列(实时上它是用链表实现的),负责存放Handler发送过来消息。

4、一个Looper对应一个线程(自己所在的线程,如:线程B)。Looper的loop()方法运行在自己所在的线程(线程B)中,当Handler在线程A发送一条消息存放到MessageQueue时,Looper的loop()方法在线程B把消息取出来,并交给Handler处理,所以Handler的处理消息的方法是运行在Looper所在的线程(线程B)的。由于多个线程之间共享内存空间,所以Handler可以在线程A把消息存放到MessageQueue,Looper可以在线程B把消息取出来,一存一取之间就实现了线程的切换。

现在我们了解了Handler的工作流程和线程切换原理。那么它在源码中又是如何去实现的呢?

从使用的角度看,我们要使用Handler首先要得到一个Handler对象,那么我们就从最简单的new Handler()作为入口,来分析它的源码。

    public Handler() {
        this(null, false);
    }

    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()");
        }
        //获取Looper对象的mQueue属性,mQueue 就是MessageQueue对象。
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
复制代码

在Handler的构造方法中,首先通过Looper.myLooper()方法获取当前线程的Looper对象,如果Looper对象为空,就抛出异常,说当前线程还没有调用Looper.prepare()方法。如果Looper不为空,Handler就会持有Looper的MessageQueue对象mQueue。

我们再看Looper.myLooper()和Looper.prepare()两个方法:

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<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));
    }

    //获取当前线程的Looper对象
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
复制代码

这里有一个很关键的类:ThreadLocal,它一个线程内部的数据存储类,通过它存储的数据只有在它自己的线程才能获取到,其他线程是获取不到的。所以sThreadLocal.get()获取的就是当前线程的Looper对象。在Looper.prepare()方法中我们看到了如果当前线程已经有Looper对象,就会抛出异常,说一个线程只能创建一个Looper对象,所以Looper对象与自己所在的线程是相对应的。 再看Looper的构造方法:

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

Looper的构造方法是私有的,外界不能直接创建Looper对象,只能通过Looper.prepare()方法创建对象并且通过Looper.myLooper()获取对象,这就保证了一个线程只能有一个Looper对象。Looper.prepare()不能调用两次。

在Looper的构造方法中会创建一个MessageQueue对象,这个就是负责存放消息的消息队列,也就是Handler所持有的mQueue 对象。它是由Looper创建和管理的。

看完了Handler、Looper和MessageQueue对象的创建,接着看消息的发送:

Handler发送消息的方法有很多,但无论你是send一个Message还是post一个Runnable;无论你是延时发送还是不延时发送,最终都会调用Handler的enqueueMessage()方法。

   private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        //把this赋值给msg的target属性,this就是Handler对象。
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        //把消息存放到MessageQueue 
        return queue.enqueueMessage(msg, uptimeMillis);
    }
复制代码

这里直接把消息存放到MessageQueue 就完事了。那么消息又是从哪里被取出来的呢? Looper里有一个Looper.loop()方法,我们看一下它的源码。

public static void loop() {

        final MessageQueue queue = me.mQueue;
        //一个死循环
        for (;;) {
            //从MessageQueue中取出一条消息
            Message msg = queue.next(); 
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            //把消息交给Handler处理。
            msg.target.dispatchMessage(msg);
        }
    }
复制代码

从上面的代码中我们看到loop()会开启一个死循环,不断地从MessageQueue中取出消息并交给Handler处理。在前面的enqueueMessage()方法中我们知道了msg.target就是发送消息的Handler对象。

这里有同学可能会有疑问:上面的代码中明明如果(msg == null),就退出方法,为什么我还说loop()里面是个死循环呢?这是因为MessageQueue的next()方法取出消息的时候,如果没有消息,next()方法会阻塞线程,直到MessageQueue有消息进来,然后取出消息并返回。所以queue.next()一般不会返回null,除非调用Looper的quit()或者quitSafely()方法结束消息轮询,queue.next()才会返回null,才会结束循环。

    public void quit() {
        mQueue.quit(false);
    }
    public void quitSafely() {
        mQueue.quit(true);
    }
复制代码

最后我们来看 一下消息的处理:Handler的dispatchMessage(msg)方法。

public void dispatchMessage(Message msg) {
        //如果Message有自己的callback,就由Message的callback处理
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
             //如果Handler有自己的mCallback,就由Handler的mCallback处理
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //默认的处理消息的方法
            handleMessage(msg);
        }
    }
复制代码

处理消息的方法有三个:

1、优先级最高的是Message自己的callback,这是一个Runnable对象,我们用Handler post一个Runnable的时候,其实就是把这个Runnable赋值个一个Message对象的callback,然后把Message对象发送给MessageQueue。

2、优先级第二的是Handler自己的mCallback,在我们创建一个Handler对象的使用可以传一个Handler.Callback对象给它并实现Callback里的handleMessage(msg)方法,作为我们的消息处理方法。

3、优先级最低的是Handler自己的handleMessage(msg)方法,这也是我们最常用的消息处理方法。

到这里我们的分析就结束了,现在你对Handler机制是不是有了深刻的认识呢。

不管怎么样,不论是什么样的大小面试,要想不被面试官虐的不要不要的,只有刷爆面试题题做好全面的准备,当然除了这个还需要在平时把自己的基础打扎实,这样不论面试官怎么样一个知识点里往死里凿,你也能应付如流啊~

今天讲的只是Handler机制一方面,我自己一路学习以来收集整理理出来的各个大厂面试题资料,其中有些面试题是我自己多次面试整理出来的面试题,在这里免费分享给我的粉丝们以及读者们,需要的伙伴们快来领取吧~

面试:如果不准备充分的面试,完全是浪费时间,更是对自己的不负责!

金九银十面试季,赶快去为自己的面试做足准备吧!

最后,祝愿大家都能找到一份好的工作,拿到心仪的offer!

这里是我的简书号,欢迎大家关注~

如果你觉得还算有用的话,不妨把它们推荐给你的朋友~