Handler相关面试题你答对多少?怎样清晰表达拿下面试官?

98 篇文章 1 订阅

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。还可以看看Looperg构造方法的源码

  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机制,这个是面试必问之题。

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

在这里贡献我准备头条时的面试内容,可以全部免费分享给大家。

注意:需要Android学习PDF大全、Android进阶之光、高级Android开发强化实战、深入探索Android热修复技术原理,还有算法题的朋友,可以直接私信我【核心】

这些都是我闲暇还会反复翻阅的精品资料!

Android学习PDF大全

这份Android学习PDF大全真的包含了方方面面了,内含Java基础知识点、Android基础、Android进阶延伸、算法合集等等

Android进阶之光

1Android新特性

.第2Material Design

3View体系与自定义View

4章 多线程编程

5章 网络编程与网络框架

6章 设计模式

7章 事件总线

8章 函数响应式编程

9章 注解与依赖注入框架

10章 应用架构设计

11章 系统架构与MediaPlayer框架

高级Android开发强化实战

1.进阶基础

2高阶控件

3.项目架构

4.晌应式编程

5.炫酷功能

6.精美动画

7.Katlin SVG

8.测试与优化

深入探索Android热修复技术原理

介绍了 Android 热修复的核 技术原理 结合 ophix 热修复开发实践过程,

从代码修复、资源修复、 so 库修复 大方向进行了详细的技术剖析与解读,业内少有的深度讲解 Android 系统热修复技术的书籍,对于原理、代码讲解得非常清晰和深入,值得我们 Android工程师研读。

我的这份学习合集,可以有效的帮助大家掌握知识点。

总之也是在这里帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习

获取方式:转发+关注,私信我【核心】即可
面试:如果不准备充分的面试,完全是浪费时间,更是对自己的不负责!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值