Android面试——Handler 机制

一、Handler机制

Handler机制围绕的三个点
1.Handler负责消息的发送和接受
发送给消息MessageQueue和接受Looper返回的消息并且处理。
2.Looper负责管理MessageQueue
Looper不断从MessageQueue中取出消息,并且交给Handler处理。
3.MessageQueue负责存放Handler发送过来的消息
下面会根据一些博客和源码的角度来分析Handler原理,也是从面试被问到的一些问题进行反思为了更深入的理解进行一次总结。

二、Handler创建

Handler面试中可以遇到的问题,带着这些问题去看源码会加深对代码的理解,这些问题我们都可以在源码中找到答案。

问题一:一个线程可以有几个 Handler和Looper?
问题二:为何主线程可以直接使用Handler?子线程使用Handler应该如何创建?

Handler.java ,Looper.java 代码中只提取我们需要的部分,看下上面问题在代码中是什么样的流程,我们就能够找到答案,Handler构造函数中可以看到:
1.handler创建时会获取当前looper,如果为空会抛出异常。
2.myLooper()方法会获取ThreadLocal对应Looper,那在get之前我们肯定需要做set操作,在prepare()方法中我们看到了set Looper的操作。所以在子线程中创建handler,我们需要先调用Looper.prepare,prepare()方法如果获取ThreadLocal对应的Looper不为空则抛出异常,由此可知当前线程只能对应一个Looper,然后Looper.loop开启消息循环。这个是在子线程中创建,那么主线程为什么不需要创建Looper,因为在ActivityThread已经帮我们做好了,ActivityThread—>main()方法中会调用Looper.prepareMainLooper()来创建Looper。
拓展:
1.ActivityThread即UI线程,这里不做拓展,感兴趣的可以自行百度。
2.ThreadLocal,可以在不同的线程之中互不干扰地存储并提供数据,内部是使用ThreadLocalMap存储线程为key,值为value。ThreadLocal并不是线程,不要按照字面意思理解。
3.其实google官方提供了HandlerThread类,该类实现了子线程创建Handler,HandlerThread实际是一个Thread,在run()方法中初始化Looper,Looper.prepare,开启消息循环Looper.loop。

 public Handler() {this(null, false);}public Handler(@Nullable Callback callback, boolean async) {......//handler创建时会获取当前looper,如果为空会抛出异常mLooper = Looper.myLooper();if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread " + Thread.currentThread()+ " that has not called Looper.prepare()");}//获取looper成功以后创建MessageQueue对象mQueue = mLooper.mQueue;//这边对应的是Handler.Callback,后面Handler执行优先级会讲到mCallback = callback;mAsynchronous = async;}
 public static @Nullable Looper myLooper() {return sThreadLocal.get();}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));}public static void prepareMainLooper() {prepare(false);synchronized (Looper.class) {if (sMainLooper != null) {throw new IllegalStateException("The main Looper has already been prepared.");}sMainLooper = myLooper();}}/*** Returns the application's main looper, which lives in the main thread of the application.*/public static Looper getMainLooper() {synchronized (Looper.class) {return sMainLooper;}}

三、 Handler发送消息

问题三:Handler的发送方式有哪些方式。

3.1send方案发送消息(需要回调才能接收消息)

1、sendMessage(Message) 立即发送Message到消息队列
2、sendMessageAtFrontOfQueue(Message) 立即发送Message到队列,而且是放在队列的最前面
3、sendMessageAtTime(Message,long) 设置时间,发送Message到队列
4、sendMessageDelayed(Message,long) 延时若干毫秒后,发送Message到队列**

3.2post方案 立即发送Message到消息队列

1、post(Runnable) 立即发送Message到消息队列
2、postAtFrontOfQueue(Runnable) 立即发送Message到队列,而且是放在队列的最前面
3、postAtTime(Runnable,long) 设置时间,发送Message到队列
4、postDelayed(Runnable,long) 在延时若干毫秒后,发送Message到队列

3.3 sendMessage的流程

问题四:Handler优先处理消息需要怎么做?

注意看上面代码Handler在初始化的时候创建的MessageQueue,handler执行sendMessage(),通知MessageQueue插入消息enqueueMessage()。最后调用MessageQueue的enqueueMessage()方法。
重点看下sendMessageAtFrontOfQueue,sendMessageAtFrontOfQueue 立即发送消息到Message队列的头部,在Handler.java—>enqueueMessage()传递参数when = 0;MessageQueue.java—> enqueueMessage()方法
// New head, wake up the event queue if blocked.
判断如果when = 0时,会在队列头部创建一个Message ,如果当前是阻塞状态立即唤醒!

问题五:既然可以存在多个 Handler 往 MessageQueue 中添加数据(发消息时各个 Handler 可能处于不同线程),那它内部是如何确保线程安全的?

MessageQueue.java—>enqueueMessage(),在往消息队列里面存储消息时,会拿当前的 MessageQueue 对象作为锁对象,这样通过加锁就可以确保操作的原子性和可见性了。
消息的读取也是同理(MessageQueue.java—>next()),也会拿当前的 MessageQueue 对象作为锁对象,来保证多线程读写的安全性。
Handler.java

    public final boolean sendMessage(@NonNull Message msg) {return sendMessageDelayed(msg, 0);}public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {if (delayMillis < 0) {delayMillis = 0;}return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);}public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {MessageQueue queue = mQueue;if (queue == null) {RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");Log.w("Looper", e.getMessage(), e);return false;}return enqueueMessage(queue, msg, uptimeMillis);}public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) {MessageQueue queue = mQueue;if (queue == null) {RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");Log.w("Looper", e.getMessage(), e);return false;}return enqueueMessage(queue, msg, 0);}private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {msg.target = this;msg.workSourceUid = ThreadLocalWorkSource.getUid();if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);}

MessageQueue.java

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.nextprev.next = msg;}// We can assume mPtr != 0 because mQuitting is false.if (needWake) {nativeWake(mPtr);}}return true;}

3.4 post(Runnable r)方式的流程

在handler.java中可以看到post方式,除了getPostMessage以外与send流程是一致的,getPostMessage()最终会把callback封装进Message,callback变成Message的参数,后面dispatchMessage会用到callback。

    public final boolean post(@NonNull Runnable r) {return  sendMessageDelayed(getPostMessage(r), 0);}private static Message getPostMessage(Runnable r) {Message m = Message.obtain();m.callback = r;return m;}public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {if (delayMillis < 0) {delayMillis = 0;}return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);}

三、Looper循环

问题六 :主线程 Looper 与子线程 Looper 有什么不同?

主线程中在ActivityThread—>main() ,调用Looper.loop(),在Looper—>prepareMainLooper(),
主线程prepare(boolean quitAllowed),quitAllowed 默认值为false,不允许退出。
需要注意的是子线程如果创建的是当前线程的Looper,调用Looper.prepare ,Looper.loop,由于Looper.loop是一个无限循环,子线程中在明确不需要使用Handler时,需要退出循环。
1.Looper.quit
实际上执行了MessageQueue中的removeAllMessagesLocked方法,该方法的作用是把MessageQueue消息池中所有的消息全部清空,无论是延迟消息,还是非延迟消息。
2.Looper.quitsafely
实际上执行了MessageQueue中的removeAllFutureMessagesLocked方法,该方法只会清空MessageQueue消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理,quitSafely相比于quit方法安全之处在于清空消息之前会派发所有的非延迟消息。
无论是调用了quit方法还是quitSafely方法只会,Looper就不再接收新的消息。即在调用了Looper的quit或quitSafely方法之后,消息循环就终结了。

这里我们截取部分代码,Looper.java—>loop()是不断循环消息,queue.next(),并且 使用Handler来分发消息msg.target.dispatchMessage(msg),这里msg.target就是Handler,在Handler.java—>enqueueMessage()中赋值,msg.target = this;
Looper.java

/*** Run the message queue in this thread. Be sure to call* {@link #quit()} to end the 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 blockif (msg == null) {// No message indicates that the message queue is quitting.return;}try {msg.target.dispatchMessage(msg);if (observer != null) {observer.messageDispatched(token, msg);}dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;} catch (Exception exception) {if (observer != null) {observer.dispatchingThrewException(token, msg, exception);}throw exception;} finally {ThreadLocalWorkSource.restore(origWorkSource);if (traceTag != 0) {Trace.traceEnd(traceTag);}}}}

四、Handler处理消息

问题七:Handler收到消息执行优先级

Handler分发消息并且处理消息,这里涉及到Handler收到消息执行优先级,有三种方式。

Message的回调方法:message.callback.run(),优先级最高;
Handler的回调方法:Handler.mCallback.handleMessage(msg),优先级仅次于1;
Handler的默认方法:Handler.handleMessage(msg),优先级最低。

1.如果msg.callback != null 就直接处理消息,还记得上面Handler post方式发送消息吗?post实际是把Runnable封装进Message对象也就是msg.callback;
2.mCallback 这里的mCallback指的是Handler.Callback();一般在handler初始化的时候创建处理,内部实现的也是handleMessage()方法;
3.handleMessage Handler的默认方法,优先级最低。

Handler.java

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

五、总结

我们从Handler创建,发送消息,到Looper初始化开始消息循环,再通过Looper 把消息分发给Handler,最后到Handler处理消息的大致流程梳理了一遍。

Android面试——Handler 机制相关推荐

  1. 自己写个C++版本Handler来理解Android的Handler机制

    由于日常工作不需要经常写android上层应用,对Android的Handler机制一直处于模模糊糊的状态.使用Handler之后,回去写c++代码时,时刻怀念Android里面的Handler,希望 ...

  2. android面试 handler,Android面试之Handler相关学习

    8种机械键盘轴体对比 本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选? ###Android面试之Handler相关学习 1.Android消息机制之Looper.java源代码学习 1). ...

  3. (转)Android笔记--handler机制

    一.重要参考资料 [参考资料] 目前来看,下面的几个网址中的内容质量比较不错,基本不需要再读别的网址了. 1.android消息机制一 http://xtfncel.javaeye.com/blog/ ...

  4. 【Android】Handler 机制 ( Handler | Message | Looper | MessageQueue )

    文章目录 I . Handler 机制简介 II . Handler 机制 Handler Message Looper MessageQueue 四组件对应关系 III . Handler ( 消息 ...

  5. android中handler机制,如何使用?,Android中的Handler机制

    一.Handler概述 二.Handler发送消息的方法 三.MessageQueue的enqueueMessage() 四.Message的when字段 五.子线程中使用Handler 六.Loop ...

  6. [转]Android中handler机制的原理

    Andriod提供了Handler 和 Looper 来满足线程间的通信.Handler先进先出原则.Looper类用来管理特定线程内对象之间的消息交换(MessageExchange). 1)Loo ...

  7. android handler的机制和原理_第一百八十回:Android中的Handler机制九

    各位看官们大家好,上一回中咱们说的是Android中Handler机制的例子,这一回咱们继续说该例子.闲话休提,言归正转.让我们一起Talk Android吧! 看官们,由于时间的原因我们在上一回中只 ...

  8. Android消息机制(Handler机制) - 线程的等待和唤醒

    我们都知道,Android的Handler机制,会在线程中开启消息循环,不断的从消息队列中取出消息,这个机制保证了主线程能够及时的接收和处理消息. 通常在消息队列中(MessageQueue)中没有消 ...

  9. 996页阿里Android面试真题解析火爆全网,分享面经!

    导语 学历永远是横在我们进人大厂的一道门槛,好像无论怎么努力,总能被那些985,211 按在地上摩擦! 不仅要被"他们"看不起,在HR挑选简历,学历这块就直接被刷下去了,连证明自己 ...

最新文章

  1. php 归并排序,详解PHP归并排序的实现
  2. lvm扩张与收缩小结
  3. 波特率与比特率的关系
  4. 美参议员敦促SEC就雅虎黑客案信息披露义务展开调查
  5. no signatures that match those in shared user android.uid.system; ignoring!
  6. ASP.NET Core中的OWASP Top 10 十大风险-跨站点脚本攻击 (XSS)
  7. 排位重要还是媳妇儿重要?
  8. 1313. 解压缩编码列表
  9. 软件测试工程师如何编写一篇杀手级简历?
  10. 415 http请求 hutool_HTTP请求返回415错误码定位解决方法
  11. 2019山东省赛总结
  12. UI设计需要用到哪些软件工具呢?
  13. html图片撑开盒子,css背景图撑开盒子高度
  14. Macbook上如何调整Windows分区大小,NTFS-FAT-FAT32
  15. uniapp苹果打包(需使用苹果电脑)
  16. 疫情之下裸辞后的一些感悟和面试心得
  17. 移动端REM响应式模板及相应规范
  18. 全球与中国货船维修保养市场深度研究分析报告
  19. openlayers地图旋转_Openlayers实现地图的基本操作
  20. 全球与中国休闲外套市场深度研究分析报告

热门文章

  1. Hyperspectral Unmixing论文泛读(一)
  2. matlab中linprog函数不能用,matlab中linprog函数
  3. Golang几种连接字符串方法
  4. 关于coursera上Learning How to Learn课程的读书笔记2
  5. 想换个手机,目前最值得入手的手机有哪些推荐?
  6. 自组织神经网络:Kohonen网络训练算法
  7. TOP100summit分享实录 | JFrog高欣:Kubernetes is hard!JFrog的Kubernetes实践 1
  8. 小程序wx.onLocationChange向下兼容wx.getLocation
  9. 基于二维四参数模型的坐标转换
  10. 如何统计一天24小时每个小时的数据量