搞懂Handler去面试!

如果你没有听说过或者使用过Handler,那么你一定不是一个合格的Android开发。Handler作为我们最常使用的跨线程UI工具,你可能只知道它的使用,却没有了解过它的实现原理。可能有人会想,原理这种东西理解与不理解它,并不影响我们对它的使用。话是没错,可能原理这种东西对于大部分人最直接的作用就是面试了,对于Handler,它可是面试中的常客,与之相关提问最多的就是Handler的原理了。有人会说,这我熟悉啊,然后张口就来:

“Handler机制由四部分组成,分别是Handler、Looper、MessageQueue、Message。Handler负责消息的发送和处理,Looper负责不断从MessageQueue中取出消息,MessageQueue负责Message的存储与管理,Message是消息的载体,可以承载需要的数据”

这要是搁几年前面试的时候,你这么说没什么问题,毕竟那时移动端很火,各行各业急需懂得开发App的人才,标准和要求没那么高,可能面试你的人都不懂它的原理甚至都不懂技术。但是,随着市场对移动端人才需求的不断饱和与移动开发市场的降温,这样的回答在今天可就不再那么管用了,一是对人才标准和要求方面的提高,二是如今的面试官不可能再出现像几年前那样的情况。当再往下问深点时,可能就触到你的知识盲区,GG了。

所以,就算是为了面试需要,请你务必搞懂这些原理。对于想在技术上有所进阶的人,就更该了。

那么本篇博文就带着以下对于Handler的疑问进行展开(下面都是针对Handler原理问题的变种,本质上还是离不开原理)

1、Handler如何与线程相关联?

2、消息是如何存储和管理的?

3、消息又是如何分发与处理的?

4、如果一个线程存在多个Handler,那么消息是怎样分发到对应的Handler而不出错的?

5、Handler处理消息的方式都有哪些?

6、线程的切换是怎么回事?

上面这些问题可能是你在使用Handler的过程中会产生的疑问,又或是在面试的时候被提问过,今天我们就Handler的使用过程搞懂这些问题背后的原理。

一个Handler是如何使用的呢?

 class MyHandler:Handler(){
        override fun handleMessage(msg: Message?) {
            super.handleMessage(msg)
            /.../
        }
    }
myHandler.sendEmptyMessage()
myHandler.post()

继承Handler,复写handleMessage方法处理消息,再调用send系列或post系列的方法发送一个消息,这应该是Handler最常见的使用方式了,然而你会发现看不到Looper、MessageQueue的身影,因为这些细节都被Handler封装了起来,让我们只需关注它如何使用而不需要知道背后的细节,今天我们就反其道而行之,从它的创建入手,看看Handler机制是怎么实现的。

一、Handler的创建

Handler一共有七个构造方法,但是最后都会指向其中两个,一个带Looper参数的,一个不带的。

那么我们先看看不带Looper参数的那个

public Handler(Callback callback, boolean async) {
        
        /.../

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

可以看到,在这个构造方法内通过Looper.myLooper()方法获得一个Looper对象,对其进行为空判断,并抛出一个异常提示当前线程不能创建Handler,因为在此之前没有调用Looper.prepare()方法。

那prepare()这个方法是干什么的呢

public static void prepare() {
        prepare(true);
    }

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

prepare()方法调用了私有的prepare方法并转递了一个true参数,这个参数表示当前Looper是否可退出,在这个私有的prepare()方法内它首先去当前线程的本地存储区去获得一个Looper对象,如果对象不为空,则提示异常并告知一个线程只能存在一个Looper对象,如果为空则创建一个Looper对象并设置到当前线程的本地存储区。

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

Looper的构造方法里创建了一个MessaQueue并标记了当前线程。

那么再来看看Handler带Looper参数的构造函数

public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

这个构造方法只是把传进来的参数赋值给本地,并没有什么特别的。

ok,通过Handler的构造函数可以得知,Handler的构建离不开Looper,而Looper是与线程绑定且一个线程只能拥用有一个Looper,所以Handler与线程是通过Looper来关联的,Looper在那个线程创建,Handler就在哪个线程执行。

二、消息的存储与管理

通常我们使用Handler发送一个消息,会调用send系列或post系列的方法,但其实它们最后调用的都是同一个方法。

而这个enqueueMessage()方法又会调用MessageQueue的enqueueMessage()方法将消息入队。

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

到这里,显而易见消息存储与管理是通过MessageQueue来完成。

三、消息的分发与处理

在子线程中使用Handler不仅需要Looper.prepa(),还需调用Looper.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;

        /.../

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            /.../
            
            try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            /.../

            msg.recycleUnchecked();
        }
    }

loop()方法内部开始了一个死循环,并不断从MessageQueue中取出消息,没有时则阻塞,当Looper调用quit()方法时,MessageQueue的queue.next()返回为null,loop()方法结束死循环,Looper退出。

当有Message时,尝试执行msg.target.dispatchMessage(msg)方法,msg.target是什么?记不记得前面Handler的enqueueMessage方法,其实msg.target就是Handler对象本身

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        //将Handler引用指向msg.target
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

这样Message就被分发到对应的Handler的dispatchMessage进行处理

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

咦?这msg.callback和mCallBack又是什么,消息不是被handleMessage()处理就完了么,怎么还有其他方法会处理?别慌,其实这些东西你也见过,而且还用过。

msg.callback其实就是handler.post()方法里的Runnable,在使用post系列方法的时候,Runnable会经getPostMessage(Runnable r)

封装成一个Message对象

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

handleCallback()方法则是在Handler绑定的线程执行Runnable的run方法

 private static void handleCallback(Message message) {
        message.callback.run();
    }

那mCallback呢?mCallback是一个接口,内有一个handleMessage方法,你会注意到,这个方法有一个布尔返回值,如果不需dispatchMessage方法再往下执行时返回ture。这个接口通过Handler的构造方法传入

public interface Callback {
        
        public boolean handleMessage(Message msg);
    }

最后就是Handler的handleMessage()方法了,通常继承Handler时需要复写这个方法。

上面就是Handler处理Message的三种实现了,你会发现,这相当于三个方法拥有不同的优先执行权,Runnable最高,CallBack接口次之,最后是handleMessage()。对于这个优先级可以干什么,有兴趣的可以自行谷哥度娘。

扩展

1、Handler的内存泄漏

其实在Handler的构造方法里,就明确表示过Handler应该是静态的,否则可能会发生内存泄漏

 Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());

比如我要通过Handler的消息设置进度条

companion object{
         class MyHandler(activity: MainActivity) : Handler() {

             private var mWeakReference =WeakReference(activity)

             override fun handleMessage(msg: Message) {
                super.handleMessage(msg)
                 val activity = mWeakReference.get()
                 activity?.let { it.progress_bar.progress = msg.what }
            }
        }
    }

2、Message的最佳获取方式?

因为我们需要频繁的使用Message,所以频繁的创建会过多消耗资源,所以Message内部维护着一个消息池供我们使用,它会循环利用Message,这个消息池的大小为50。

private static final int MAX_POOL_SIZE = 50;

我们可以通过Handler.obtainMessage()或Message.obtain()方法来获取一个Message。

3、子线程吐司的正确方式

正如我们在子线程使用Handler一样,在吐司前后调用Looper.preare()、Looper.loop()方法即可(这个我没研究过,貌似是Toast的底层也是通过Handler实现的,如果有知道的可以评论告诉我)

最后

相信看完这篇文章,以后再有人问起你Handler的问题,基本不会再出现触及知识盲区的情况而卡壳了(除非真的很底层的东西),如果有人在面试遇到过关于Handler的其他问题,希望可以留言评论,和大家分享一下。

发布了45 篇原创文章 · 获赞 18 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/Ever69/article/details/94906022