美文网首页Android进阶之路Android开发Android技术知识
Android Handler相关面试题你能答对多少?子线程和主

Android Handler相关面试题你能答对多少?子线程和主

作者: Z_萧晓 | 来源:发表于2020-03-10 17:28 被阅读0次

    Handler机制是面试官非常喜欢问的知识点,源码我看完几遍,还是会觉得不清晰,里面的代码非常绕。后来我决定放弃探究细节,先把相关的类和调用的方法画一个草图,然后理清互相调用的关系,再结合关于Handler的高频面试题,去寻找答案,这样一轮下来,会对Handler有更深的认识。现在把这个过程,面试题寻找的答案以及相关的解释整理成这篇文章,如有不对,欢迎大家予以指正。

    高频面试题

    1.获取Message实例的方式有哪些?哪一种更好?

    获取Message实例的方法主要有两种,一种是直接创建,Message msg = new Message。另一种是通过Message.obtain()或者Handler.obtatinMessage()来得到一个Message对象。更推荐使用后一种方式,这种方式得到的对象是从对象回收池中得到,复用已经处理完的Message对象,而不是重新生成一个新对象。Message.obtain()Handler.obtatinMessage()最终都是调用了Message类的obtain()方法,查看方法内容,就知道是从对象回收池里得到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();
        }
    

    2.当Activity有多个Handler的时候,Message消息是否会混乱?怎么样区分当前消息由哪个Handler处理?

    不会混乱,哪个Handler发送的消息,到时候也是这个handler处理。在发送消息的时候,会绑定target,这个target就是Handler本身,当需要handler调用dispatchMessage(msg)处理消息的时候,这个Handler就是发送消息时绑定的handler。
    无论用哪一种方法发送消息,最终都会调用enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)来发送消息

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

    这里的this,就是当前的handler。在来看需要Handler处理消息的时候,取的是哪一个handler,下面贴出主要源码。

     public static void loop() {
      ......
            for (;;) {
                Message msg = queue.next(); // might block
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
    
                // This must be in a local variable, in case a UI event sets the logger
             ......
                if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                    Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
                }
    
                final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
                final long dispatchEnd;
                try {
                    msg.target.dispatchMessage(msg);
                    dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
                } finally {
                    if (traceTag != 0) {
                        Trace.traceEnd(traceTag);
                    }
                }
               ......
    
                msg.recycleUnchecked();
            }
        }
    

    这是循环消息时的部分代码,处理消息代码是msg.target.dispatchMessage(msg);,这里的target就是当时发送消息的handler。

    3.在子线程发送消息,却能够在主线程接收消息,主线程和子线程是怎么样切换的?

    子线程用handler发送消息,发送的消息被送到与主线程相关联的MessageQueue,也是主线程相关联的Looper在循环消息,handler所关联的是主线程的Looper和MessageQueue,所以最后消息的处理逻辑也是在主线程。只有发送消息是在子线程,其它都是在主线程,Handler与哪个线程的Looper相关联,消息处理逻辑就在与之相关的线程中执行,相应的消息的走向也就在相关联的MessageQueue中。所以子线程切换到主线程是很自然的过程,并没有想象中的复杂。

    4.能不能在子线程中创建Handler?

    可以,但是在创建前先调用prepare()方法创建Looper。Handler创建的时候,会去检查是否有创建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;
        }
    

    所以我们在子线程需要先调用prepare()方法创建Looper。这里还多提一点,在主线程创建就不需要自己创建Looper,因为在ActivityTread类里面,已经为我们创建好了,相关源码如下:

     public static void main(String[] args) {
         ......
            Looper.prepareMainLooper();// 为主线程创建looper
    
            // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
            // It will be in the format "seq=114"
            long startSeq = 0;
            if (args != null) {
                for (int i = args.length - 1; i >= 0; --i) {
                    if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                        startSeq = Long.parseLong(
                                args[i].substring(PROC_START_SEQ_IDENT.length()));
                    }
                }
            }
            ActivityThread thread = new ActivityThread();
            thread.attach(false, startSeq);
    
            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();
    
            throw new RuntimeException("Main thread loop unexpectedly exited");
        }
    

    Looper.prepareMainLooper();这句代码就是为主线程创建Looper。 所以在主线程中直接创建一个Handler,就直接可以循环消息,因为安卓的主线程已经为我们准备好了Looper。

    5.一个线程可以有几个Handler?几个Looper?

    一个线程可以有多个Handler,但是只有一个Looper。创建Handler之前,需要创建Looper,否则会报错。源码里面已经做了说明。

     public Handler(Callback callback, boolean async) {
       ......
            mLooper = Looper.myLooper();
            if (mLooper == null) {//判断Looper是否被创建
                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的创建,是在prepare()方法里。

    // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<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是否存在,存在就会抛出Only one Looper may be created per thread异常,这是在告诉我们一个线程只能有一个Looper。而TreadLocal的作用就是线程间隔离,确保一个线程对应一个Looper。还可以看看Looper构造方法的源码

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

    MessageQueue的初始化是在Looper的构造方法里。不管一个线程有多少个Handler,相关联的都是同一个Looper和MessageQueue。

    关于Handler,可以问的问题有很多,以上只是抽出一些我认为比较重要的问题。在寻找答案以后,我将Handler机制的整个过程在脑海中过了一遍,并且画了个草图。

    Handler机制中重要类的相互关联图

    Handler机制原理涉及几个重要的类:Handler、Message、MessageQueue、Looper。
    就用子线程向主线程发送消息来说明整个过程。
    首先在主线程创建一个Handler,在Handler类里面会创建Looper以及MessageQueue的对象,并且在Handler构造方法里面赋值

     final Looper mLooper;
     final MessageQueue mQueue;
     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;
        }
    

    mLooper = Looper.myLooper();得到的Looper是主线程的Looper
    mQueue = mLooper.mQueue;得到的MessageQueue就是在Looper构造方法里面创建的MessageQueue。
    创建好了Handler实例,我们就会在子线程调用handler.sendMessage(msg);发送消息,将message放到MessageQueue里面。在enqueueMessage()里面就给每个message设置target,这个target就是当前的handler。

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

    再然后调用Looper.loop();(在ActivityThread的main()里面已经帮我们写好)开始循环消息,拿到消息以后就会用handler取出消息进行处理,重点代码是msg.target.dispatchMessage(msg);,这里的handler就和一开始我们为message设置的Handler对应。

    /**
         * Handle system messages here.
         */
        public void dispatchMessage(Message msg) {
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                handleMessage(msg);
            }
        }
    

    最后我们在Handler的handleMessage(msg)里面去处理我们的消息。
    这就是子线程给主线程发送消息的整个过程,源码很多,我只是截取了部分重点。这里多提一点就是线程处理消息。 在线程中处理消息需要做三件事情

    1. 先创建一个Looper (Looper.prepare()) 2. 再创建Handler,默认是和当前线程的Looper关联起来 3. 循环消息(Looper.loop()

    这三个步骤的顺序不能调换。因为主线程已经帮我们创建了Looper,所以我们不需要写,如果是在子线程创建Looper就需要了。

    Handler机制的理解要靠自己去琢磨,不断的看源码,去理解,理清它们之间的互相调用。只有比较深入的理解了Handler,才能在面试中回答面试官的问题,靠死记硬背是不可取的。多花时间去看,多动脑,不偷懒,总能把Handler拿下。

    最后

    文章到这里就结束了,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。

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

    最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司19年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

    还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

    Android学习PDF+架构视频+面试文档+源码笔记

    【Android开发核心知识点笔记】

    【Android思维脑图(技能树)】

    【Android核心高级技术PDF文档,BAT大厂面试真题解析】

    【Android高级架构视频学习资源】

    Android精讲视频领取学习后更加是如虎添翼!进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

    【Android进阶学习视频】、【全套Android面试秘籍】关注我【主页简介】或者【简信我】查看免费领取方式!

    相关文章

      网友评论

        本文标题:Android Handler相关面试题你能答对多少?子线程和主

        本文链接:https://www.haomeiwen.com/subject/ixlgdhtx.html