Handler是Android中的消息处理机制,是一种线程间通信的解决方案,同时你也可以理解为它天然的为我们在主线程创建一个队列,队列中的消息顺序就是我们设置的延迟的时间。
简单使用
一般是在主线程中实现一个Handler,然后在子线程中使用它。
class HandlerActivity: AppCompatActivity() {
private val mHandler = MyHandler()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 在子线程中通过自定义的 Handler 发消息
thread {
mHandler.sendEmptyMessageDelayed(1, 1000)
}
}
// 自定义一个 Handler
class MyHandler: Handler() {
override fun handleMessage(msg: Message) {
Log.i("HandlerActivity", "主线程:handleMessage: ${msg.what}")
}
}
}
或者有时候需要在子线程中创建运行在主线程中的Handler
class HandlerActivity: AppCompatActivity() {
private var mHandler: Handler? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
thread {
//获得main looper 运行在主线程
mHandler = MyHandler(Looper.getMainLooper())
mHandler!!.sendEmptyMessageDelayed(1, 1000)
}
}
// 自定义一个 Handler
class MyHandler(): Handler() {
override fun handleMessage(msg: Message) {
Log.i("HandlerActivity", "主线程:handleMessage: ${msg.what}")
}
}
}
在第二个用法中出现了一个Looper.getMainLooper()
,使用它作为参数,即使MyHandler是在子线程中定义的,但是它的handleMessage
方法依然运行在主线程。我们看一下Handler的构造方法之一:
public Handler(@NonNull Looper looper) {
this(looper, null, false);
}
handleMessage方法具体运行在哪个线程是和这个Looper息息相关的。
概述
这就是整个Handler在Java层的流程示意图。可以看到,在Handler
调用sendMessage
方法以后,Message
对象会被添加到MessageQueue
中去。而这个MessageQueue
就是被包裹在了Looper
中。那么Looper
对象是干什么的呢?它和Handler
是什么关系呢?我们来看一下他们具体的职责吧~
- Handler
消息机制中作为一个对外暴露的工具,其内部持有了一个
Looper
。负责Message
的发送及处理。
Handler.post(Runnable r)/Handler.sendMessage(Message msg)
:向消息队列发送各种消息事件;
Handler.handleMessage()
:处理相应的消息事件
- Looper
作为消息循环的核心,其内部包含了一个消息队列
MessageQueue
,用于记录所有待处理的消息;通过Looper.loop()
不断地从MessageQueue
中抽取Message,按分发机制将消息分发给目标处理者,可以看成是消息泵。注意,线程切换就是在这一步完成的。
- MessageQueue
则作为一个消息队列,则包含了一系列链接在一起的
Message
;不要被这个Queue
的名字给迷惑了,就以为它是一个队列,但其实内部通过单链表的数据结构来维护消息列表,等待Looper
的抽取。
- Message
则是消息体,内部又包含了一个目标处理器
target
,这个target
正是最终处理它的Handler。
Handler
从我们大家最熟悉的sendMessage方法说起。sendMessage方法见名思意,就是发送一个信息,可是要发送到哪里去呢,这是代码:
# 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);
}
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
// focus -1
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
不管我们是通过post/send发送消息,最终都会调用到enqueueMessage
方法,在这个方法中做了两件事:
1.给Message赋值
2.将Message按照时间顺序放入队列
最后的入队列是在MessageQueue
中完成的,已经不再是Handler
的方法了,也就是说,调用走到了这里。事件的流向已经不归Handler
管了。Handler
只负责把Message
发送出去,然后等待时机处理这条Message
,至于Message
存取的过程跟Handler
没有关系。
注意focus1处的代码,Message
将当前的Handler
对象赋值给了target
字段,所以说:
Handler
在发送Message
(消息)时,每个发出去的Message
都持有把它发出去的Handler
的引用。
MessageQueue
MessageQueue
是一个由单链表构成的优先级队列(取的都是头部,所以说是队列)。前面提到Handler
发送消息入队是由MessageQueue
来完成的,那么MessageQueue
是在哪里定义好的呢?
答案是在Handler
的构造函数中:
public Handler(@Nullable Callback callback, boolean async) {
// ...
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
// ...
}
可以看到,Handler
中持有的MessageQueue
是从Looper
中拿到的。关于Looper
稍后再讲。
上面说到,最后发送消息都调用的是MessageQueue
的queue.enqueueMessage(msg, uptimeMillis)
方法。现在我们已经拿到了queue,进去看看这个方法它做了什么。
// MessageQueue.java
//省略部分代码
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
//【1】拿到队列头部
Message p = mMessages;
boolean needWake;
//【2】如果消息不需要延时,或者消息的执行时间比头部消息早,插到队列头部
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 {
//【3】消息插到队列中间
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.next
prev.next = msg;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
从以上代码也可看出来:
在多个Handler
往MessageQueue
中添加数据(发送消息时各个Handler
可能处于不同线程)时,其内部通过synchronized
关键字保证线程安全。同时messagequeue.next()
内部也会通过synchronized
加锁,确保取的时候线程安全,同时插入也会加锁。
1.mMessages 是队列的第一消息,获取到它判断消息队列是不是空的,是则将当前的消息放到队列头部;
2.如果当前消息不需要延时,或当前消息的执行时间比头部消息早,也是放到队列头部。
3.如果不是以上情况,说明当前队列不为空,并且队列的头部消息执行时间比当前消息早,需要将它插入到队列的中间位置。
如何判断这个位置呢?依然是通过消息被执行的时间。
通过遍历整个队列,当队列中的某个消息的执行时间比当前消息晚时,将消息插到这个消息的前面。
可以看到,消息队列是一个根据消息【执行时间先后】连接起来的单向链表。想要获取可执行的消息,只需要遍历这个列表,对比当前时间与消息的执行时间,就知道消息是否需要执行了。
Looper
Handler
中的MessageQueue
对象其实就是Handler
中的Looper
它的MessageQueue
,Handler
往MessageQueue
中添加消息,其实就是往Handler
的Looper
所持有的MessageQueue
中添加对象。
简单来说,Looper
和MessageQueue
是一对一的关系,一个Looper
持有一个MessageQueue
对象。
回到之前Handler构造函数的代码:
//Handler.java
//省略部分代码
public Handler(@Nullable Callback callback, boolean async) {
//敲黑板,划重点就是这一句!!!!
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
}
在这一句中Handler通过Looper.myLooper
方法获取到了Looper对象,当然,也有可能没获取到。不过,你如果没获取到就要抛异常了。
# Looper.java
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
TreadLocal
//sThreadLocal.get() will return null unless you've called prepare().
@UnsupportedAppUsage
static final ThreadLocal sThreadLocal = new ThreadLocal();
sThreadLocal
是一个ThreadLocal
类,并且它的泛型是Looper
对象。ThreadLocal
提供了线程的局部变量,每个线程都可以通过set()和get()来对这个局部变量进行操作,但不会和其他线程的局部变量进行冲突,实现了线程的数据隔离。简要言之:往ThreadLocal
中填充的变量属于当前线程,该变量对其他线程而言是隔离的。
可以看到Looper对象是通过ThreadLocal获取的,那么ThreadLocal是何时放进去的呢?查看sThreadLoacl.set()
方法的调用位置:
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));
}
可以看得出,最后调用了是prepare(boolean quitAllowed)
方法,而这个方法首先判断,如果sThreadLocal
有值,就抛异常,没有值才会塞进去一个值。其实很好理解,就是说prepare
方法必须调用但也只能调用一次,不调用没有值,抛异常,调用多次也还抛异常。
如果
Looper
为空就抛异常,现在我们知道了,什么时候Looper
为空呢?没有调用prepare
方法的时候会为null。
也就是说在构造Handler
之前,必须得有Looper
对象,换言之,在构造Handler
之前,必须调用Looper
的prepare
方法创建Looper
。
接下来再看看这行sThreadLocal.set(new Looper(quitAllowed));
做了什么吧,它是如何塞进去的呢?
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
set方法首先获取到了当前的线程,然后获取一个map。这个map是以键值对形式存储内容的。如果获取的map为空,就创建一个map。如果不为空就塞进去值。要注意的是,这里面的key
是当前的线程,这里面的value
就是Looper。也就是说,线程和Looper
是一一对应的。也就是很多人说的Looper
和线程绑定了,其实就是以键值对形式存进了一个map中。
Looper
中的MessageQueue
是在Looper
的构造函数中创建的:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Looper
是和当前线程绑定的。并且每个线程中只有一个Looper
和MessageQueue
。
前面提到,创建Handler
对象之前需要调用Looper.prepare()
方法先创建一个Looper
对象,但我们在activity页面中创建Handler
的时候并没有这么做,那是因为主线程默认已经创建Looper
了。因此我们先来看看主线程中是如何处理的。
查看ActivityThread类,这是整个app的入口,内部有一个main方法,它是程序的入口:
//ActivityThread.java
public static void main(String[] args) {
···
Looper.prepareMainLooper();
···
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
可以看到在ActivityThread
中的main
方法中,我们先调用了Looper.prepareMainLooper()
方法,然后获取当前线程的Handler
,最后调用Looper.loop()
。先来看一下Looper.prepareMainLooper()
方法
//Looper.java
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
// 设置不可以退出的Looper
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
//prepare
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));
}
Looper.loop
Looper虽说要分发消息,但是它又不知道你什么时候会发送消息,只能开启一个死循环,不断的尝试从队列中拿数据。这个死循环在哪里开始的?Looper.loop()开启了一个死循环,然后不断的尝试去队列中拿消息。
// Looper.java
public static void loop() {
//拿到当前线程的Looper
final Looper me = myLooper();
...
//拿到Looper的消息队列
final MessageQueue queue = me.mQueue;
...
//1 这里开启了死循环
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
msg.target.dispatchMessage(msg);
...
// 回收复用
msg.recycleUnchecked();
}
}
在循环中Looper不停的取出消息,拿到Message对象以后,会去调用Message
的target字段的dispatchMessage
方法;target正是发送这条消息的Handler
。
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
//如果 callback 处理了该 msg 并且返回 true, 就不会再回调 handleMessage
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
如果Message这个对象有CallBack回调的话,这个CallBack实际上是个Runnable,就只执行这个回调,然后就结束了。
如果Message对象没有CallBack回调,进入else分支判断Handler的CallBack是否为空,不为空执行CallBack的handleMessage方法,然后return,构建Handler的CallBack代码如下:
Handler.Callback callback = new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
//retrun true,就不执行下面的逻辑了,可以用于做优先级的处理
return false;
}
};
最后才调用到Handler的handleMessage()函数,也就是我们经常去重写的函数,在该方法中做消息的处理。
消息拦截
可以看到Handler.Callback 有优先处理消息的权利 ,当一条消息被 Callback 处理并拦截(返回 true),那么 Handler 的 handleMessage(msg) 方法就不会被调用了;
如果 Callback 处理了消息,但是并没有拦截,那么就意味着一个消息可以同时被 Callback 以及 Handler 处理。我们可以利用CallBack这个拦截来拦截Handler的消息。
Handler是如何进行线程切换的
线程间是共享资源的,子线程通过handler.sendXXX,handler.postXXX等方法发送消息,然后通过Looper.loop()在消息队列中不断的循环检索消息,最后交给handle.dispatchMessage方法进行消息的分发处理。
子线程可以更新UI吗?
查看以下代码:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_three);
new Thread(new Runnable() {
@Override
public void run() {
//创建Looper,MessageQueue
Looper.prepare();
new Handler().post(new Runnable() {
@Override
public void run() {
Toast.makeText(HandlerActivity.this,"toast",Toast.LENGTH_LONG).show();
}
});
//开始处理消息
Looper.loop();
}
}).start();
}
这里需要注意在所有事情处理完成后应该调用quit方法来终止消息循环,否则这个子线程就会一直处于循环等待的状态,因此不需要的时候终止Looper,调用
Looper.myLooper().quit()
。
以上代码是否能正确弹出Toast呢?答案是肯定的。
在ViewRootImpl中的checkThread方法会校验mThread != Thread.currentThread(),mThread的初始化是在ViewRootImpl的的构造器中,也就是说一个创建ViewRootImpl线程必须和调用checkThread所在的线程一致,UI的更新并非只能在主线程才能进行。
线程中更新UI的重点是创建它的ViewRootImpl和checkThread所在的线程是否一致。
系统为什么不建议在子线程中访问UI?
这是因为 Android 的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态。
那么为什么系统不对UI控件的访问加上锁机制呢?
缺点有两个:
1.首先加上锁机制会让UI访问的逻辑变得复杂
2.锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。
所以最简单且高效的方法就是采用单线程模型来处理UI操作。
子线程如何通知主线程更新UI(都是通过Handle发送消息到主线程操作UI的)
- 主线程中定义 Handler,子线程通过 mHandler 发送消息,主线程 Handler 的 handleMessage 更新UI。
- 用 Activity 对象的 runOnUiThread 方法。
- 创建 Handler,传入 getMainLooper。
- View.post(Runnable r) 。
Looper死循环为什么不会导致应用卡死,会耗费大量资源吗?
应用被卡死本质上不是阻塞了主线程,而是阻塞了Looper的loop方法。导致loop方法无法处理其他事件,导致出现了ANR事件。
从前面的主线程、子线程的分析可以看出,Looper会在线程中不断的检索消息,如果是子线程的Looper死循环,一旦任务完成,用户应该手动退出,而不是让其一直休眠等待。
线程其实就是一段可执行的代码,当可执行的代码执行完成后,线程的生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出,例如,binder 线程也是采用死循环的方法,通过循环方式不同与 Binder 驱动进行读写操作,当然并非简单地死循环,无消息时会休眠。Android是基于消息处理机制的,用户的行为都在这个Looper循环中,我们在休眠时点击屏幕,便唤醒主线程继续进行工作。
主线程的死循环一直运行是不是特别消耗 CPU 资源呢?其实不然,这里就涉及到 Linux pipe/epoll机制,简单说就是在主线程的 MessageQueue 没有消息时,便阻塞在 loop 的 queue.next() 中的 nativePollOnce() 方法里,此时主线程会释放 CPU 资源进入休眠状态,直到下个消息到达或者有事务发生,通过往 pipe 管道写端写入数据来唤醒主线程工作。这里采用的 epoll 机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
主线程的Looper何时退出
在App退出时,ActivityThread中的mH(Handler)收到消息后,执行退出。
//ActivityThread.java
case EXIT_APPLICATION:
if (mInitialApplication != null) {
mInitialApplication.onTerminate();
}
Looper.myLooper().quit();
break;
如何处理Handler使用不当造成的内存泄漏?
class HandlerActivity: AppCompatActivity() {
private val mHandler = MyHandler()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 在子线程中通过自定义的 Handler 发消息
thread {
mHandler.sendEmptyMessageDelayed(1, 1000)
}
}
// 自定义一个 Handler
class MyHandler: Handler() {
override fun handleMessage(msg: Message) {
Log.i("HandlerActivity", "主线程:handleMessage: ${msg.what}")
}
}
}
再发送延时消息之前,app推出了,那么handleMessage方法还会执行吗?答案是会的。
MyHandler 是 HandlerActivity 的内部类,会持有 HandlerActivity 的引用。在进入页面以后,发送了一个延时 1s 的消息,如果 HandlerActivity 在 1s 内退出了,由于 Handler 会被 Message 持有,保存在其 target 变量中,而 Message 又会被保存在消息队列中,这一系列关联,导致 HandlerActivity 在退出的时候,依然会被持有,因此不能被 GC 回收,这就是内存泄漏!当这个 1s 延时的消息被执行完以后,HandlerActivity 会被回收。
有延时消息,在界面关闭后及时移除Message/Runnable,调用handler.removeCallbacksAndMessages(null)
内部类导致的内存泄漏改为静态内部类,并对上下文或者Activity/Fragment使用弱引用。
正确创建Message实例
1.通过 Message 的静态方法 Message.obtain() 获取;
2.通过 Handler 的公有方法 handler.obtainMessage()
// Message.java
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
sPool是消息池,obtain会先从消息池中获取Message对象,避免通过new创建过多的对象。
参考:
Handler的初级、中级、高级问法,你都掌握了吗?
Handler原理