一、简介
Handler、Looper、MessageQueue、Message这四大金刚构成了Android的消息机制,Handler是暴露给开发人员的上层接口。通过Handler可以自由地进行线程切换,常被用于从子线程切换到UI线程,进而更新UI界面。例如:Retrofit获取网络数据后回调、EventBus消息跨线程传递以及LiveData的postValue都是使用Handler来将子线程切换到UI线程。
二、Handler的用法
下面举几个例子:
1、子线程处理任务结束后,切换到UI线程进行更新
2、执行定时任务
3、子线程创建Handler
1、子线程切换到UI线程
在子线程中进行耗时操作后,在UI线程更新控件
// Handler
private Handler mHandler = new Handler(getMainLooper()) {
@Override
public void handleMessage(Message msg) {
// 处理消息,UI线程,在这里进行更新处理
...
}
}
// 子线程
new Thread(new Runnable() {
@Override
public void run() {
// 执行耗时操作
...
// 发送消息
Message message = mHandler.obtainMessage(0);
mHandler.sendMessage(nessage);
}
}).start();
// 在onDestory 释放资源
protected void onDestory() {
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
}
}
在UI线程中,创建了mHandler对象,在子线程中执行耗时操作后,通过mHandler发送消息,并会在mHandler的handleMessage中处理该消息,此时就由子线程切换到了主线程中了。需要注意下:在onDestory释放资源的时候,调用下mHandler.removeCallbacksAndMessages(null)。
2、定时任务
在创建Handler的线程中,执行一个定时任务
// 在UI线程创建 mHandler
private Handler mHandler = new Handler(getMainLooper());
// 在onCreate 做定时任务
protected void onCreate() {
if (mHandler != null) {
mLaunchHandler.postDelayed(new Runnable() {
@Override
public void run() {
// 在UI线程,做某些操作
}
}, 500);
}
}
// 在onDestory 释放资源
protected void onDestory() {
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
}
}
3、子线程创建Handler
Handler的工作需要Looper。在UI线程时,ActivityThread已经为我们准备好了Loop。但是如果要在子线程自己创建Handler的话,需要手动为该线程添加Loop:
new Thread("sub Thread Name") {
@Override
public void run() {
Looper.prepare();
Handler handler = new Handler();
Looper.loop();
}
}.start();
三、原理
应用程序也是一个Java程序,有一个main函数。对于Java程序而言,执行完main函数所有内容之后,程序就退出了。而Android的应用程序从ActivityThread的main函数执行之后,会有一个死循环在进行,这样main函数执行的UI线程就不会退出了。
1、处理消息前的准备工作
且从ActivityThread的main函数看起:
public static void main(String[] args) {
...
// 创建Loop对象,并将其存入到该线程的ThreadLocal中
Looper.prepareMainLooper();
...
// 创建ActivityThread对象
ActivityThread thread = new ActivityThread();
// 建立Binder通道(创建新线程)
thread.attach(false, startSeq);
...
// 在当前线程中死循环读取MessageQueue消息
Looper.loop();
先看下执行Looper.prepareMainLooper()会做哪些操作:
public static void prepareMainLooper() {
// 到这里执行prepare函数
prepare(false);
...
}
// 在该函数下可以看到 将Loop对象设置 和该线程绑定的sThreadLocal 中
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));
}
// 继续看下 sThreadLocal :线程内部的数据存储类
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
// 再继续看下 new Looper() 构造函数: 可以发现在Loop构造函数中创建了一个与其绑定的MessageQueue对象
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
然后在看下执行Looper.loop()会做哪些操作:
public static void loop() {
// 这里的myLooper()方法就是:sThreadLocal.get(),获取当前线程的Looper对象
final Looper me = myLooper();
...
// 这里在UI线程开启了一个死循环(可能会有疑惑这里有死循环:UI线程会被阻塞吗?UI线程如何处理后续的逻辑呢?)
for (;;) {
if (!loopOnce(me, ident, thresholdOverride)) {
return;
}
}
}
// 先继续往下看,看下loopOnce方法:
private static boolean loopOnce(final Looper me,
final long ident, final int thresholdOverride) {
// 这是个阻塞方法,取Loop的MessageQueue对象中的消息。
Message msg = me.mQueue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return false;
}
...
// 这里的msg.target实际就是指发消息的Handler:即谁发消息,谁处理
// 因此通过该方法就将消息回到了Handler的dispacthMessage中来进行处理
msg.target.dispatchMessage(msg);
...
}
// 再继续看下 MessageQueue 的 next方法: mQueue.next()
```java
Message next() {
...
for (;;) {
// 该方法是一个阻塞方法,根据nextPollTimeoutMillis判断是否要阻塞
nativePollOnce(ptr, nextPollTimeoutMillis);
}
...
}
// 如果有消息msg,则将交有发送该消息的handler进行处理:
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
上述小结:
(1)ActivityTreahd的main函数中通过Looper.prepareMainLooper()创建了Looper对象,并将其设置到该UI线程的ThreadLocal当中,此Looper对象与当前UI线程进行了绑定。其中Looper对象中创建了MessageQueue对象。
(2)ActivityTreahd的main函数中的Looper.loop()开启了死循环:调用loopOnce方法
(3)在loopOnce方法中通过MessageQueue对象的next方法来获取需要处理的Message对象。但next方法是一个阻塞方法,由内部的nativePollOnce方法进行阻塞
(4)如果此时来一个消息message,则next方法将被唤醒,从而获取到message后交由其发送消息的handler进行处理。
2、发送消息
从构建Handler及sendMessage()方法看起:
// 构建Handler
private Handler mHandler = new Handler(getMainLooper()) {
@Override
public void handleMessage(Message msg) {
...
}
}
// 上述的构建Handler最终会调用到这里:将getMainLooper()赋值给当前mLooper对象
// getMainLooper方法实际就是前面的sThreadLocal.get(),从线程内部的数据存储类中获取已经创建好的Loop对象
// 这里的mQueue消息队列 也是Loop中创建好的MessageQueue对象赋值给mQueue的
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
看下发送消息sendMessage(msg)方法
// 子线程
new Thread(new Runnable() {
@Override
public void run() {
// 执行耗时操作
...
// 发送消息
Message message = mHandler.obtainMessage(0);
mHandler.sendMessage(nessage);
}
}).start();
// sendMessage 方法最后会调用到这里
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
// 将消息存储到消息队列中去(实际上数据结构是一个单链表)
return enqueueMessage(queue, msg, uptimeMillis);
}
// 继续看下Handler的enqueueMessage方法
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
// 这里的Message的target属性是Handler类型,所以在这里将此msg和发送它的Handler进行了绑定
msg.target = this;
// 从Handler 切换到 MessageQueue 中的 enqueueMessage 执行
return queue.enqueueMessage(msg, uptimeMillis);
}
// 继续看下 MessageQueue 中的 enqueueMessage 方法
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) {
// message 存入单链表中的操作...
...
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
// 这里有消息了,将唤醒next方法中的阻塞
nativeWake(mPtr);
}
}
return true;
}
// 最后看下Message 消息的载体结构
public final class Message implements Parcelable {
/**
* User-defined message code so that the recipient can identify
* what this message is about. Each {@link Handler} has its own name-space
* for message codes, so you do not need to worry about yours conflicting
* with other handlers.
*/
// what 常用于 同一个handler的哪个消息
public int what;
/**
* arg1 and arg2 are lower-cost alternatives to using
* {@link #setData(Bundle) setData()} if you only need to store a
* few integer values.
*/
public int arg1;
/**
* arg1 and arg2 are lower-cost alternatives to using
* {@link #setData(Bundle) setData()} if you only need to store a
* few integer values.
*/
public int arg2;
/**
* An arbitrary object to send to the recipient. When using
* {@link Messenger} to send the message across processes this can only
* be non-null if it contains a Parcelable of a framework class (not one
* implemented by the application). For other data transfer use
* {@link #setData}.
*
* <p>Note that Parcelable objects here are not supported prior to
* the {@link android.os.Build.VERSION_CODES#FROYO} release.
*/
// obj 常用于 携带的参数
public Object obj;
/*package*/ Bundle data;
// 这个是消息入队列时,将该处理该消息的Handler绑定在消息上,在处理消息时就是通过target来拿到对应的Handler,从而调用Handler的dispatchMessage方法的
@UnsupportedAppUsage
/*package*/ Handler target;
}
上述小结:
(1)在UI线程中构建Handler,并为其提供了ActivityThread创建的Looper对象及MessageQueue对象以供使用
(2)通过sendMessage发送消息,调用了Handler的enqueueMessage方法:将Handler绑定在该message消息上,然后调用了MessageQueue的enqueueMessage方法
(3)MessageQueue的enqueueMessage方法:将消息存入单链表中,并进行唤醒操作,此时会唤醒next方法
(4)唤醒next方法后,将读MessageQueue中的消息(并移除掉)给到Looper中的loopOnce方法,进而得到需要处理的消息
3、处理消息
Looper中的loopOnce方法拿到消息后,将进行消息的处理, 从Loop 中的 loopOnce 方法看起:
private static boolean loopOnce(final Looper me,
final long ident, final int thresholdOverride) {
// 这里已经拿到需要处理的msg
Message msg = me.mQueue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return false;
}
...
// 这里的msg.target实际就是指发消息的Handler:即谁发消息,谁处理
// 因此通过该方法就将消息回到了Handler的dispacthMessage中来进行处理
msg.target.dispatchMessage(msg);
...
}
// 有消息msg,通过handler下的该方法进行处理,从而回调到handler中的handleMessage方法中
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
上述小结:
(1)Looper中死循环的loopOnce方法,拿到message信息后会立马进行处理
(2)通过message信息的target属性,找到发送该message的handler,然后调用handler的dispatchMessage方法
(3)通过handler的dispatchMessage方法,最终又回到了 handler 的回调中的 handleMessage 方法来完成处理消息工作
4、线程是如何从子线程切换到UI线程的
通过上面处理消息前的准备工作、发送消息、处理消息,基本上对整个消息处理有点整理轮廓的认知了,那在哪一个步骤是由子线程切换到UI线程的呢?
我个人理解是在:将消息入队列之后,执行nativeWake(mPtr)进行唤醒,此enqueueMessage方法在子线程中执行完毕。此时next方法被唤醒,开始继续执行,而此刻已经是在UI线程了。
// 消息入队列,此时还是在子线程中工作,但执行完nativeWake(mPtr)方法后,该子线程已经执行完毕了
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) {
// message 存入单链表中的操作...
...
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
// 这里有消息了,将唤醒next方法中的阻塞
nativeWake(mPtr);
}
}
return true;
}
// 此时,MessageQueue的next方法被唤醒,开始继续执行,而此刻已经是在UI线程了。
四、注意事项
1、在子线程中创建Handler时,记得要进行退出操作
在UI线程,ActivityThread已经创建好了Looper,并且一直在死循环处理消息,此时当应用程序退出时,该Looper的死循环才结束。
如果在子线程开启了Looper,在处理完任务后,一定要手动调用Looper的quit或quitSafely来退出Looper,否则该子线程会一直死循环,消耗资源,不能结束掉该线程。
2、使用Handler时,注意内存泄漏
如果在当前Activity创建的Handler是匿名内部类(或声明为非静态变量),很容易造成内存泄漏,原因是:当前Activity要销毁时,handler有任务还没处理完,handler持有Activity的引用,导致该Activity不能被GC
// 匿名内部类
new Handler().post(...)
// 非静态变量
private Handler mHandler = new Handler(getMainLooper()) {
@Override
public void handleMessage(Message msg) {
// 处理消息,UI线程,在这里进行更新处理
...
}
}
解决方法:
(1)在Activity销毁时,清空Handler中执行或未执行的Callback及Message:
// 在onDestory 释放资源
protected void onDestory() {
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
}
}
(2)非静态内部类 + 弱引用:
private static class TaskHandler extend Handler {
// 弱引用, T -> 你的Activity
private WeakReference<T> mWeakReference;
public TaskHandler(T t) {
this.mWeakReference = new WeakReference<T>(t);
}
@override
public void handlerMessage(Message msg) {
T activity = mWeakReference.get();
// 处理消息
}
}
五、待学习
1、学习下 ThreadLocal
2、学习下 nativePollOnce和nativeWake 及背后的linux的pipe/epoll
3、学习下 EventBus、Retrofit、RxJava、LiveData 如何使用 Handler 进行线程切换的
4、学习下 ActivityThread、ApplicationThread 和 AMS 如何使用 Handler的
六、参考
1、《Android开发艺术探索》 - 第10章 Android的消息机制
2、Android:遇到Handler中有Loop死循环,还没有阻塞主线程,这是为什么呢?www.jianshu.com/p/56c7dd2ba…
3、Handler 源码解析:nativePollOnce阻塞和nativeWake唤醒 www.jianshu.com/p/10924b905…
4、android Handler避免内存泄露handler.removeCallbacksAndMessages(null)的使用 blog.csdn.net/qq_26317325…
5、Android --- Handler 内存泄漏原因及解决方案 blog.csdn.net/qq_43290288…
6、Handler内存泄漏原因及解决方案 blog.csdn.net/sqf25187754…
7、Handler中有Loop死循环,为什么没有阻塞主线程,原理是什么segmentfault.com/a/119000002…