你真的懂Handler吗?Handler问答

作者 : 开心源码 本文共7689个字,预计阅读时间需要20分钟 发布时间: 2022-05-11 共105人阅读

转载需经过本人受权
本文作者:@怪盗kidou
本文链接:https://www.songma.com/p/f70ee1765a61

周末在家有点儿无聊,不知道该干些啥,想了想开通博客这么长时间以来如同并没有些什么关于 Android 的东西,所以这次来写写Android 相关的博客 —— Handler。

为什么写 Handler

的确 HandlerAndroid 开发过程中非常非常常见的东西,讲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:HandlerLooperMessageQueueMessage,主要是这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 循环结束时 prevp 指向的样子应该是下图的

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通过它的 getset 方法来赋值和取值。

因为 ThreadLocal是与线程绑定的,所以我们只需把 LooperThreadLocal 绑定了,那 LooperThread 也就关联上了

ThreadLocal的原理在问 Handler 机制的时候也是一个比较常问的点,但是详情的博客很多,源码也没有多少,这里就不再详情了,假如有需要的话后期会写新博客。

Q:Handler 有哪些构造方法

假如你上面的问题 子线程中能创立 Handler 对象吗 没有答上的话,我一般会通过这个问题引导一下。

问这个问题主要是想问你构造方法能传那些参数,并不是要你完全说出来,但是当你知道能传哪些参数的时候,也能推算出来有几个构造方法。

先说能传那些类型(仅限开放API,被 @hide 标注的不算在内),仅两种类型:LooperHandler$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.whatmsg.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#sendMessageHandler#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问答

发表回复