2018年8月1日以前谢绝全文转载(已授权除外)
本文作者:@怪盗kidou
本文链接:https://www.jianshu.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

我认为更好的回答:
这是一个假象,举个例子

public static void main(String[] args){    while(true){        // do work in while    }    doSomeThingOutWhile();}

对于从整个main方法来看,while(true) 确实阻塞了 doSomeThingOutWhile() 这个方法的执行,对于这样看,好像确实是卡住了,因为我们在 doSomeThingOutWhile 方法中想要做的事没法做了,但是如果我们把我们要做的事情通过队列放到 while 里面去做,那么是不是你就不会觉得卡了,你想要做的事情都完成了,虽然有个死循环但并不影响你想要做什么,而Android中 Looper.loop() 就是这样的原理,因为所有让我们会觉得卡住的都被放到 MessageQueue 里,然后通过Looper取出并交给 Handler执行了。

PS:不仅仅是Android,几乎所有和UI操作的都有一个类似Android Handler机制的事件循环处理机制

-----分割线-------

下面是原始回答,会让人觉得卡是因为死循环之后的代码无法执行,如果没有理解到其实我们的代码都是执行在死循环里面的话,还是没有办法理解为什么不会卡。

如果说操作系统是由中断驱动的,那么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. Instant Run详解
  2. Android中的Service详解
  3. Android系列教程(1):Notification 与状态栏信息
  4. Android(安卓)framework层实现实现wifi无缝切换AP
  5. Android(安卓)SQLiteOpenHelper源码解读
  6. android的消息队列机制
  7. Handler进阶知识
  8. android IPC机制讲解(三)
  9. Android(安卓)bitmap图片处理

随机推荐

  1. Android列表布局专题(二)ListView
  2. Android 事件传递机制(简要总结)
  3. Android(安卓)平板分辨率适配
  4. 快速转化J2ME程序为Android程序
  5. Android中继承View的研究(一) -- 一个小D
  6. Android px,dp,pt,sp的区别
  7. Android\OPhone动画分析之翻转效果
  8. 避免android程序开发内存泄漏
  9. Android —— 禁止转向、简单外部样式的
  10. 我勒个去,你们疯了吧?webOS比Android好多了