概述
Handler是 安卓面试的必问点。在安卓开发中,handler经常用于子线程执行异步任务,然后通知到主线程更新UI。 本文将从源码分析开始,一步步了解到 handler 核心知识。
从 new Handler() 开始
Handler有一个无参构造函数。如下:
上图中 无参构造函数,实际上调用了 Hanlder(Callback callback,boolean async)
,而在这个有参构造器中,两个关键点
- 创建了一个 Looper对象,赋值给了 mLooper
- 将 mLooper中的.mQueue 赋值 给了 本类中的mQueue
说明 mLooper 和 mQueue 是 handler这个消息处理体系中的重要组成部分。
Looper是什么? 它何时被初始化?
启动一个Java程序人入口是一个 main函数,如果main函数执行完毕,那么,程序就会停止运行,app进程就会终止。但是我们打开一个App之后,除非我们按下返回键或者home键回到桌面,不然它就是一直处于运行状态。s
也就是说:Looper维护了一个无限循环,保证app进程一直在运行,这是一个重要概念,必须理解,并且记清楚。
在安卓的app中,启动app的入口函数实际上是 ActivityThread.java的main函数。 如下图: s
图中1处,创建出当前线程的Looper对象;
图中2处,启动这个looper对象的无限循环。
可是说起来是 创建了 当前线程的Looper对象,但是貌似并没有在创建时指定线程对象啊? 请看下图:
1处的 prepare(false)
的实际上执行到了 prepare(boolean quitAllowed)
, 创建出了一个不允许退出的Looper
对象,并且将该looper设置到了 sThreaLocal
中。 这使得创建出来的Looper
与当前线程发生了绑定
。 并且注意,图中2处,是为了保证每一个线程的 Looper只会被创建一次,当有第二次来时,就抛出RuntimeException异常。
Lopper
的构造方法如下:
其中创建了一个MessageQueue消息队列,也指定了 Looper的所属线程为 currentThread。
然后,myLooper方法,其实也就是从 sThreadLocal中取出了当前线程的Looper对象。
Looper多次初始化会导致程序崩溃
上图中的Looper.prepare();
会导致程序崩溃
,这是因为:该代码运行在主线程中,而主线程的Looper在ActivityThread.java的 main函数中已经 执行了一次。
这也就意味着:
- Lopper的prepare方法只能在一个线程中执行一次
- Looper的构造函数只能在一个线程中执行一次
- 一个线程中的 MessageQueue 只能被初始化一次
Lopper的职责
它的职责很简单,就是 不停地从 MessageQueue
中取出 Message
并且执行 Message
指定的任务。
上图的main
函数中,有一个 Looper.loop()
开启了无限循环,以保证app进程持续运行。(必须是死循环)
图中1处,从 queue中取出下一个message,
图中2处,取得message.target,并且执行 dispatchMessage.
那么这个target是什么呢?从Message的源码可以看到:
它其实就是Handler
,而 Handler的 中有一个空方法 handlerMessage()
,这也就是 我们在创建handler需要重写的方法。
那么Handler是何时成为 一个Message的target的呢?
看下图:
handler的一个重要方法就是发送消息的 sendMessage
。 它有一些名称类似的方法,但是作用都是大同小异。基本都走到了 enqueueMessage
, 看下图:
图中1处,当发送一个 message
时,顺便将 自身this
赋值给了msg.target
。
图中2处,如果一个message
没有设置 target
,那么直接抛出异常。
图中3处,按照 message
的 when
(执行延时) 来决定插入的位置。
可以看出,MessageQueue
其实是一个按照时间顺序来排列的有序队列(数据结构为单链表)。
常见面试题
Handler的 post(Runnable)和sendMessage() 的区别
从 post的源代码分析开始:
将 runnable对象指定为了 message的callback
而在 处理message时,会优先执行 message的callback回调,也就是执行了 Runnable的run方法。
SendMessage时,则没有指定 message的callback。处理 message时,优先执行 Handler的mCallback对象,重写的handlerMessage次之。
所以。一句话,根本上的区别是post(Runnable)和sendMessage()
的 message的处理方式设置的位置不一样
。前者就是 runnable本身,后者是 handlerMessage函数,或者是 mCallback的handlerMessage。
Looper.loop()为什么不会阻塞主线程
首先这种提问方式其实是一个陷阱,app主线程能够持续运行的前提,就是 Looper的loop函数中的无限循环能够持续运行。
其次,如果 该应用没有 Message需要处理(也就是暂时没有任何message进入时),app会如何暂时释放CPU资源,而不是持续占用CPU。
此图中1处,queue.next() 后面有一句官方的 注释might block
,虚拟语气,说明可能会阻塞。
其实看看内部源码:
这里存在一个 nativePollOnce
方法。它是一个native方法。当执行时,主线程会暂时释放CPU,并进入到休眠状态,直到下一条消息到达,或者有事务发生,通过往 pipe管道写端写入数据来唤醒主线程工作。
这里采用的是 c++层的epoll机制。
Handler的sendMessageDelayed 或者 postDelayed 是如何实现的
Handler的两个核心逻辑,一个是 线程间数据共享,一个是 发送以及执行延时任务。
上述两种发送消息的方式,都可以通过设置延时时间来 达成业务目的。
具体做法:
向 MessagQueue
中插入消息时,会根据Message
的执行时间进行排序。 而在 处理Message
时,核心逻辑在 MessageQueue
的next
方法中。
蓝色框框中,进行了当前时间
与 消息执行时刻
的对比。
- 如果当前时间已经超出了 消息的执行时刻,那么会马上返回这个
message
对象给到Looper
去处理。 - 如果当前时间小于 消息的执行时刻,那么就会计算出一个 时间差,这个时间差就是CPU需要休眠的时间,而由于这是一个
for死循环
内的过程,所以nativePoolOnce
会马上执行休眠,休眠完成之后再次对比当前时间与 消息的执行时刻。
其实仔细想想,这种方式只能保证 message在when之前不被执行,而不能保证一定在when当前时刻执行。
总结
-
应用的启动是从 ActivityThread 的 main函数开始,先是执行了 Looper.prepare(),该方法先是 创建了一个Lopper对象,然后在私有方法中又创建了一个 MessageQueue作为Looper的成员变量,Looper对象通过ThreadLocal绑定在了主线程上。
-
当创建Handler对象时,构造方法中通过ThreadLocal获取绑定的Looper对象,并获取它的MessageQueue作为Handler的成员变量
-
在子线程中 使用 上一步创建的 Handler子类对象的 sendMessage方法时,将 message的target设置为 自身。同时调用了成员变量的MessageQueue 的 enqueueMessage 方法,将 message放到 MessageQueue中
-
主线程创建好之后,会执行Looper.loop方法,该方法中获取与线程绑定的Looper对象,继而获取 MessageQueue,并开启一个会阻塞(释放CPU)的死循环,只要MessageQueue中还有message,就会获取该message,并执行 message.target.dispatchMessage处理消息。