你真的懂Handler吗?Handler问答
转载需经过本人受权
本文作者:@怪盗kidou
本文链接:https://www.songma.com/p/f70ee1765a61
周末在家有点儿无聊,不知道该干些啥,想了想开通博客这么长时间以来如同并没有些什么关于 Android 的东西,所以这次来写写Android 相关的博客 —— Handler。
为什么写 Handler
的确 Handler
是 Android
开发过程中非常非常常见的东西,讲Handler的博客也不胜枚举为什么我还要写关于Handler的内容?
原因是这样的,公司为了扩张业务准备做一个新的产品线所以给手机端这边分配了4个招聘名额(iOS和Android各两名),头一个星期我由于在忙着做需求并没有参加公司的面试,都是公司的另外两个同事在参加面试,后一个星期我也参加到其中,但是我发现一个很严重的问题:在我面试的几个人尽管工作经验都集中3~6年但都没有把 Handler
讲清楚。
与其余的博客有什么不同
市面上有太多讲 Handler 的博客了,那我的博客要如何做到让人耳目一新并且切实可以让大家受益呢?
我想了一下,Handler的基本原理很简单,但细节还是蛮多的,这次发现问题也是通过面试得出的,所以我决定通过模拟面试的方式告诉你关于 Handler 的那些事儿。
商定
本文的各个问题只是我自己想出来的,并不是出自真实的面试中(除了部分我面试别人时的提问),其余的均为我为了给大家详情 Handler 机制时想出的问题。
本文后面会出现的部分源码,为避免小伙伴们在 Android Studio 中看到代码与我博客中的不一致,这里先统逐个下环境:
- sdk版本:API 27
android{ compileSdkVersion 27 // ......}
- 源码版本:27_r03
深度截图_选择区域_20180623165118.png
深度截图_选择区域_20180623165324.png
Q:说一下 Handler机制中涉及到那些类,各自的功可以是什么
A:Handler
、Looper
、MessageQueue
、Message
,主要是这4个,ThreadLocal
能不算在里面毕竟这个是JDK本身自带类不是专门为Handler机制设计的。
Handler
的作使用是将 Message
对象发送到 MessageQueue
中去,同时将自己的引使用赋值给 Message#target
。
Looper
的作使用是将 Message
对象从 MessageQueue
中取出来,并将其交给 Handler#dispatchMessage(Message)
方法,这里需要主要的是:不是调使用 Handler#handleMessage(Message)
方法,具体起因后面会讲。
MessageQueue
的作使用负责插入和取出 Message
Q:Handler 有哪些发送消息的方法
我主要是看其知不知道 post 相关的方法,问了两个人两人都不知道有post方法
sendMessage(Message msg)sendMessageDelayed(Message msg, long uptimeMillis)post(Runnable r)postDelayed(Runnable r, long uptimeMillis)sendMessageAtTime(Message msg,long when)
下面的几个方法在我眼中可可以并不是那么重要
sendEmptyMessage(int what)sendEmptyMessageDelayed(int what, long uptimeMillis)sendEmptyMessageAtTime(int what, long when)
Q:MessageQueue 中的 Message 是有序的吗?排序的依据是什么
是有序的。你可可以会想这不是废话嘛,Queue
都是有序的,Set
才是无序的,这里想问你的是他的内部是基于什么进行的排序,排序的依据是 Message#when
字段,表示一个相对时间,该值是由 MessageQueue#enqueueMessage(Message, Long)
方法设置的。
// 见 MessageQueue.java:554,566~578boolean enqueueMessage(Message msg, long when) { // .... synchronized (this) { // .... msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; if (p == null || when == 0 || when < p.when) { msg.next = p; mMessages = msg; needWake = mBlocked; } else { needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) { prev = p; p = p.next; // 一致循环,直到找到尾巴(p == null) // 或者者这个 message 的 when 小于我们当前这个 message 的 when if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg; } } return true;}
假如当前插入的 message#when
是介于 5~8 之间,那么for 循环结束时 prev
和p
指向的样子应该是下图的
prev和p的关系
因为这个特性,所以当两个 Message#when
一致时插入序按先后顺序,比方两个的 when 都是7,那么第一个进入后的样子如下图(黄):
第一个 7 入队列后
第二个进入后的样子(红):
第二个 7 入队列后
Q:Message#when 是指的是什么
Message#when
是一个时间,使用于表示 Message
期望被分发的时间,该值是 SystemClock#uptimeMillis()
与 delayMillis
之和。
// Handler.java:596public final boolean sendMessageDelayed(Message msg, long delayMillis){ if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);}
SystemClock#uptimeMillis()
是一个表示当前时间的一个相对时间,它代表的是 自系统启动开始从0开始的到调使用该方法时相差的毫秒数 。
Q:Message#when 为什么不使用 System.currentTimeMillis() 来表示
System.currentTimeMillis()
代表的是从 1970-01-01 00:00:00 到当前时间的毫秒数,这个值是一个强关联 系统时间 的值,我们能通过修改系统时间达到修改该值的目的,所以该值是不可靠的值。
比方手机长时间没有开机,开机后系统时间重置为出厂时设置的时间,中间我们发送了一个推迟消息,过了一段时间通过 NTP 同步了最新时间,那么就会导致 推迟消息失效
同时 Message#when
只是使用 时间差 来表示先后关系,所以只要要一个相对时间即可以达成目的,它能是从系统启动开始计时的,也能是从APP启动时开始计时的,甚至能是定期重置的(所有消息都减去同一个值,不过这样就复杂了没有必要)。
Q:子线程中能创立 Handler 对象吗?
不能在子线程中直接调使用 Handler 的无参构造方法,由于 Handler
在创立时必需要绑定一个 Looper
对象,有两种方法绑定
- 先调使用 Looper.prepare() 在当前线程初始化一个 Looper
Looper.prepare();Handler handler = new Handler();// ....// 这一步可别可少了Looper.loop();
- 通过构造方法传入一个 Looper
Looper looper = .....;Handler handler = new Handler(looper);
Q:Handler 是如何与 Looper 关联的
上个问题应该告知了其中一种情况:通过构造方法传参。
还有一种是我们直接调使用无参构造方法时会有一个自动绑定过程
// Handler.java:192public Handler(Callback callback, boolean async) { if (FIND_POTENTIAL_LEAKS) { final Class<? extends Handler> klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, "The following Handler class should be static or leaks might occur: " + klass.getCanonicalName()); } } mLooper = Looper.myLooper(); // 就是这里 if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async;}
Q:Looper 是如何与 Thread 关联的
Looper 与 Thread 之间是通过 ThreadLocal 关联的,这个能看 Looper#prepare()
方法
// Looper.java:93private 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
中有一个 ThreadLocal
类型的 sThreadLocal
静态字段,Looper
通过它的 get
和 set
方法来赋值和取值。
因为 ThreadLocal
是与线程绑定的,所以我们只需把 Looper
与 ThreadLocal
绑定了,那 Looper
和 Thread
也就关联上了
ThreadLocal
的原理在问 Handler
机制的时候也是一个比较常问的点,但是详情的博客很多,源码也没有多少,这里就不再详情了,假如有需要的话后期会写新博客。
Q:Handler 有哪些构造方法
假如你上面的问题 子线程中能创立 Handler 对象吗 没有答上的话,我一般会通过这个问题引导一下。
问这个问题主要是想问你构造方法能传那些参数,并不是要你完全说出来,但是当你知道能传哪些参数的时候,也能推算出来有几个构造方法。
先说能传那些类型(仅限开放API,被 @hide 标注的不算在内),仅两种类型:Looper
、Handler$Callback
,那么我们即可以退算出有多少个公共构造方法了:无参、单Looper、单Callback、Looper和Handler,共4种。
public Handler() { this(null, false);}public Handler(Callback callback) { this(callback, false);}public Handler(Looper looper) { this(looper, null, false);}public Handler(Looper looper, Callback callback) { this(looper, callback, false);}
还有一个 boolean
的 async, 不过这个不是开放API,即便不知道个人觉得完全没有问题。
Q:looper为什么调使用的是Handler的dispatchMessage方法
看一下dispatchMessage方法
// Handler.java:97public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); }}
从上面的代码不难看出有两个起因:
- 当
msg.callback != null
时会执行handleCallback(msg)
,这表示这个 msg 对象是通过handler#postAtTime(Runnable, long)
相关方法发送的,所以msg.what
和msg.obj
都是零值,不会交给Handler#handleMessage
方法。 - 从上一个问题你应该看到了
Handler
能接受一个Callback
参数,相似于 View 里的 OnTouchListener ,会先把事件交给Callback#handleMessage(Message)
解决,假如返回 false 时该消息才会交给Handler#handleMessage(Message)
方法。
Q:在子线程中如何获取当前线程的 Looper
Looper.myLooper()
内部原理就是同过上面提到的 sThreadLocal#get()
来获取 Looper
// Looper.java:203public static @Nullable Looper myLooper() { return sThreadLocal.get();}
Q:假如在任意线程获取主线程的 Looper
Looper.getMainLooper()
这个在我们开发 library 时特别有使用,毕竟你不知道别人在调使用用你的库时会在哪个线程初始化,所以我们在创立 Handler
时每次都通过指定主线程的 Looper
的方式保证库的正常运行。
Q:如何判断当前线程是不是主线程
知道了上面两个问题,这个问题就好答复了
方法一:
Looper.myLooper() == Looper.getMainLooper()
方法二:
Looper.getMainLooper().getThread() == Thread.currentThread()
方法三: 方法二的简化版
Looper.getMainLooper().isCurrentThread()
Q:Looper.loop() 会退出吗?
不会自动退出,但是我们能通过 Looper#quit()
或者者 Looper#quitSafely()
让它退出。
两个方法都是调使用了 MessageQueue#quit(boolean)
方法,当 MessageQueue#next()
方法发现已经调使用过 MessageQueue#quit(boolean)
时会 return null
结束当前调使用,否则的话即便 MessageQueue
已经是空的了也会阻塞等待。
Q:MessageQueue#next 在没有消息的时候会阻塞,如何恢复?
当其余线程调使用 MessageQueue#enqueueMessage
时会唤醒 MessageQueue
,这个方法会被 Handler#sendMessage
、Handler#post
等一系列发送消息的方法调使用。
boolean enqueueMessage(Message msg, long when) { // 略 synchronized (this) { // 略 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 { // 略 } if (needWake) { nativeWake(mPtr); // 唤醒 } } return true;}
Q:Looper.loop() 方法是一个死循环为什么不会阻塞APP
假如说操作系统是由中断驱动的,那么Android的应使用在宏观上能说是 Handler 机制驱动的,所以主线程中的 Looper 不会一直阻塞的,起因如下(以下是我瞎JB猜的,欢迎补充、指正):
- 当队列中只有推迟消息的时候,阻塞的时间等于头结点的 when 减去 当前时间,时间到了以后会自动唤醒。
- 在Android中 一个进程中不会只有一个线程,因为 Handler 的机制,导致我们假如要操作 View 等都要通过 Handler 将事件发送到主线程中去,所以会唤醒阻塞。
- 传感器的事件,如:触摸事件、键盘输入等。
- 绘制事件:我们知道要想显示流畅那么屏幕必需保持 60fps的刷新率,那绘制事件在入队列时也会唤醒。
- 总是有
Message
源源不断的被加入到MessageQueue
中去,事件是一环扣一环的,举个Fragment
的栗子:
getSupportFragmentManager() .beginTransaction() .replace(android.R.id.content,new MyFragment()) .commit();
这个时候并不是立马把 MyFragment
显示出来了,而是经过层层的调使用来到了 FragmentManager#scheduleCommit()
方法,在这里又有入队列操作,
// FragmentManager.java:2103private void scheduleCommit() { synchronized (this) { boolean postponeReady = mPostponedTransactions != null && !mPostponedTransactions.isEmpty(); boolean pendingReady = mPendingActions != null && mPendingActions.size() == 1; if (postponeReady || pendingReady) { mHost.getHandler().removeCallbacks(mExecCommit); mHost.getHandler().post(mExecCommit); // 这里有入队列操作 } }}
提交后是不是紧接着又是一系列的生命周期的事件分发?所以。。。
你还有什么关于Handler的问题,评论告诉我吧
假如你还有什么在面试中遇到的和 Handler
相关的问题,该博客中没有表现出来的赶紧评论告诉我吧,我会持续补充到这篇博客当中。
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 你真的懂Handler吗?Handler问答