1. Handler 的作用
在Android为了保障线程安全,规定只能由主线程来更新UI信息。而在实际开发中,会经常遇到多个子线程都去操作UI信息的情况,那么就会导致UI线程不安全。这时,我们就需要借助 Handler 作为媒介,让 Handler 通知主线程按顺序一个个去更新UI,避免UI线程不安全。
那么,子线程要更新UI的信息时,我们就需要将要更新的消息传递到 UI
主线程中,再由主线程完成更新,从而实现工作线程对UI
的更新处理,最终完成异步消息的处理(如图1所示)。
2. Handler 相关概念解释
主要涉及的有:处理器(Handler)、消息(Message)、消息队列(Message Queue)、循环器(Looper)。
概念 | 定义 | 作用 |
---|---|---|
Message | 线程间通讯的数据单元(即Handler接受/处理的对象) | 存储需要操作的信息 |
Message Queue | 一种数据结构(先进先出) | 存储Handler发来的消息 |
Handler | 主线程与子线程的通讯媒介<br />线程消息的处理者 | 添加消息(Message)到消息队列(Message Queue)<br />处理由循环器(Looper)分配过来的消息(Message)。 |
Looper | Message Queue 与 Handler的通讯媒介 | 消息获取:循环取出essage Queue中的Message <br />消息分发:将取出的Message发送给对应的Handler |
3.Handler 工作流程
4.Handler 使用
4.1子线程向主线程发消息
我们一般使用handler发送消息,只需要两步,首先是创建一个Handler对象,并重写handleMessage方法,就是上图中的3(Message.target.handleMeesage),然后需要消息通信的地方,通过Handler的sendMessage方法发送消息(这里我们创建了一个子线程,模拟子线程向主线程发送消息)。代码如下:
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
private Handler mHandler;
private Button btnSendeToMainThread;
private static final int MSG_SUB_TO_MAIN= 100;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 1.创建Handler,并重写handleMessage方法
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// 处理消息
switch (msg.what) {
case MSG_SUB_TO_MAIN:
// 打印出处理消息的线程名和Message.obj
Log.e(TAG, "接收到消息: " + Thread.currentThread().getName() + ","+ msg.obj);
break;
default:
break;
}
}
};
btnSendeToMainThread = (Button) findViewById(R.id.btn_sendto_mainthread);
btnSendeToMainThread .setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 创建一个子线程,在子线程中发送消息
new Thread(new Runnable() {
@Override
public void run() {
Message msg = Message.obtain();
msg.what = MSG_SUB_TO_MAIN;
msg.obj = "这是一个来自子线程的消息";
// 2.发送消息
mHandler.sendMessage(msg);
}
}).start();
}
});
}
}
4.2主线程向子线程发消息
handler需要与looper绑定,在主线程开始的时候会自动创建一个looper,而在子线程中需要我们自己去创建looper。所以使用Handler通信之前需要有以下三步:
- 调用Looper.prepare()
- 创建Handler对象
- 调用Looper.loop()
代码如下:
// 创建一个子线程,并在子线程中创建一个Handler,且重写handleMessage
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
subHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// 处理消息
switch (msg.what) {
case MSG_MAIN_TO_SUB:
Log.e(TAG, "接收到消息: " + Thread.currentThread().getName() + ","+ msg.obj);
break;
default:
break;
}
}
};
Looper.loop();
}
}).start();
btnSendToSubThread = (Button) findViewById(R.id.btn_sendto_subthread);
btnSendToSubThread.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Message msg = Message.obtain();
msg.what = MSG_MAIN_TO_SUB;
msg.obj = "这是一个来自主线程的消息";
// 主线程中发送消息
subHandler.sendMessage(msg);
}
});
5. Handler机制原理
5.1 Looper.prepare()
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
// 规定了一个线程只有一个Looper,也就是一个线程只能调用一次Looper.prepare()
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// 如果当前线程没有Looper,那么就创建一个,存到sThreadLocal中
sThreadLocal.set(new Looper(quitAllowed));
}
从上面的代码可以看出,一个线程最多只有一个Looper对象。当没有Looper对象时,去创建一个Looper,并存放到sThreadLocal中,sThreadLocal是一个static的ThreadLocal对象,它存储了Looper对象的副本,并且可以通过它取得当前线程在之前存储的Looper的副本。如下图:
Looper的构造方法:
private Looper(boolean quitAllowed) {
// 创建了MessageQueue,并供Looper持有
mQueue = new MessageQueue(quitAllowed);
// 让Looper持有当前线程对象
mThread = Thread.currentThread();
}
这里主要就是创建了消息队列MessageQueue,并让它供Looper持有,因为一个线程最大只有一个Looper对象,所以一个线程最多也只有一个消息队列。然后再把当前线程赋值给mThread。
MessageQueue的构造方法没有什么可讲的,它就是一个消息队列,用于存放Message。
所以Looper.prepare()的作用主要有以下三点
- 创建Looper对象
- 创建MessageQueue对象,并让Looper对象持有
- 让Looper对象持有当前线程
5.2 new Handler()
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
// 不相关代码
......
//得到当前线程的Looper,其实就是调用的sThreadLocal.get
mLooper = Looper.myLooper();
// 如果当前线程没有Looper就报运行时异常
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
// 把得到的Looper的MessagQueue让Handler持有
mQueue = mLooper.mQueue;
// 初始化Handler的Callback
mCallback = callback;
mAsynchronous = async;
}
Handler的创建过程主要有以下几点
- 创建Handler对象
- 得到当前线程的Looper对象,并判断是否为空
- 让创建的Handler对象持有Looper、MessageQueu、Callback的引用
5.3 Looper.loop()
public static void loop() {
// 得到当前线程的Looper对象
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
// 得到当前线程的MessageQueue对象
final MessageQueue queue = me.mQueue;
// 无关代码
......
// 死循环
for (;;) {
// 不断从当前线程的MessageQueue中取出Message,当MessageQueue没有元素时,方法阻塞
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// Message.target是Handler,其实就是发送消息的Handler,这里就是调用它的dispatchMessage方法
msg.target.dispatchMessage(msg);
// 回收Message
msg.recycleUnchecked();
}
}
首先还是判断了当前线程是否有Looper,然后得到当前线程的MessageQueue。接下来,就是最关键的代码了,写了一个死循环,不断调用MessageQueue的next方法取出MessageQueue中的Message,注意,当MessageQueue中没有消息时,next方法会阻塞,导致当前线程挂起,后面会讲到。
拿到Message以后,会调用它的target的dispatchMessage方法,这个target其实就是发送消息时用到的Handler。所以就是调用Handler的dispatchMessage方法,代码如下:
public void dispatchMessage(Message msg) {
// 如果msg.callback不是null,则调用handleCallback
if (msg.callback != null) {
handleCallback(msg);
} else {
// 如果 mCallback不为空,则调用mCallback.handleMessage方法
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
// 调用Handler自身的handleMessage,这就是我们常常重写的那个方法
handleMessage(msg);
}
}
可以看出,这个方法就是从MessageQueue中取出Message以后,进行分发处理。
首先,判断msg.callback是不是空,其实msg.callback是一个Runnable对象,是Handler.post方式传递进来的参数,后面会讲到。而hanldeCallback就是调用的Runnable的run方法。
然后,判断mCallback是否为空,这是一个Handler.Callback的接口类型,之前说了Handler有多个构造方法,可以提供设置Callback,如果这里不为空,则调用它的hanldeMessage方法,注意,这个方法有返回值,如果返回了true,表示已经处理 ,不再调用Handler的handleMessage方法;如果mCallback为空,或者不为空但是它的handleMessage返回了false,则会继续调用Handler的handleMessage方法,该方法就是我们经常重写的那个方法。
关于从MessageQueue中取出消息以后的分发,如下面的流程图所示:
5.4发送消息
使用Handler发送消息主要有两种,一种是sendMessage方式,还有一个post方式,不过两种方式最后都会调用到sendMessageDelayed方法。
sendMessage方法传入的是Message,将Message传入Message Queue。
post方法代码:
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
// 构造一个Message,并让其callback执行传来的Runnable
Message m = Message.obtain();
m.callback = r;
return m;
}
可以看到,post方法只是先调用了getPostMessage方法,用Runnable去封装一个Message,然后就调用了sendMessageDelayed,把封装的Message加入到MessageQueue中。
所以使用handler发送消息的本质都是:把Message加入到Handler中的MessageQueue中去。
6.Handler的内存泄漏
Handler的常用方式:
public class HandlerActivity extends AppCompatActivity {
private static final String TAG = "HandlerActivity";
private Handler mHandler;
private Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
// 匿名内部类
mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// 处理消息
}
};
btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 发送延时100s的消息
mHandler.sendEmptyMessageDelayed(100, 100 * 1000);
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
// 使用leakcanary做内存泄漏检测
RefWatcher refWatcher = MyApplication.getRefWatcher(this);
if (refWatcher != null) {
refWatcher.watch(this);
}
}
}
但是会有一个问题,我们进入这个页面然后点击按钮,发送一个延时100s的消息,再退出这个Activity,这时候可能导致内存泄漏。
根本原因是因为我们创建的匿名内部类Handler对象持有了外部类Activity的对象
,我们知道,当使用handler发送消息时,会把handler作为Message的target保存到MessageQueue,由于延时了100s,所以这个Message暂时没有得到处理,这时候它们的引用关系为MessageQueue持有了Message,Message持有了Handler,Handler持有了Activity,如下图所示
当退出这个Activity时,因为Handler还持有Activity,所以gc时不能回收该Activity,导致了内存泄漏。
解决方案:
静态内部类+弱引用
静态内部类是不会引用外部类的对象的,但是既然静态内部类对象没有持有外部类的对象,那么我们怎么去调用外部类Activity的方法呢?答案是使用弱引用。代码如下:
public class HandlerActivity extends AppCompatActivity {
private static final String TAG = "HandlerActivity";
private Handler mHandler;
private Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
// 创建Handler对象,把Activity对象传入
mHandler = new MyHandler(HandlerActivity.this);
btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 发送延时100s的消息
mHandler.sendEmptyMessageDelayed(100, 100 * 1000);
}
});
}
// 静态内部类
static class MyHandler extends Handler {
private WeakReference<Activity> activityWeakReference;
public MyHandler(Activity activity) {
activityWeakReference = new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// 处理消息
if (activityWeakReference != null) {
Activity activity = activityWeakReference.get();
// 拿到activity对象以后,调用activity的方法
if (activity != null) {
}
}
}
}
}
首先,我们自定义了一个静态内部类MyHandler,然后创建MyHandler对象时传入当前Activity的对象,供Hander以弱应用的方式持有,这个时候Activity就被强引用和弱引用两种方式引用了,我们继续发起一个延时100s的消息,然后退出当前Activity,这个时候Activity的强引用就不存在了,只存在弱引用,gc运行时会回收掉只有弱引用的Activity,这样就不会造成内存泄漏了。
但这个延时消息还是存在于MessageQueue中,得到这个Message被取出时,还是会进行分发处理,只是这时候Activity被回收掉了,activity为null,不能再继续调用Activity的方法了。所以,其实这是Activity可以被回收了,而Handler、Message都不能被回收。
至于为什么使用弱引用而没有使用软引用,其实很简单,对比下两者回收前提条件就清楚了
- 弱引用(WeakReference): gc运行时,无论内存是否充足,只有弱引用的对象就会被回收
- 软引用(SoftReference): gc运行时,只有内存不足时,只有软引用的对象就会被回收
很明显,当我们Activity退出时,我们希望不管内存是否足够,都应该回收Activity对象,所以使用弱引用合适。
7.Handler面试常见问题
1、线程、Looper、Handler之间的关系如下:
- 一个线程只能绑定一个Looper,一个MessageQueue;但一个Thread可以有多个Handler。
- 一个Looper可绑定多个Handler,一个MessageQueue。
- 一个Handler只能绑定一个Looper。
2、子线程中创建 Handler 对象
不可以在子线程中直接调用 Handler 的无参构造方法,因为 Handler
在创建时必须要绑定一个 Looper
对象
Looper.prepare();
Handler handler = new Handler();
// 这一步可别可少了
Looper.loop();
3、Handler 是如何与 Looper 关联的?
(1)通过构造方法传参
Looper looper = .....;
Handler handler = new Handler(looper);
(2)直接调用无参构造方法自动绑定
// Handler.java:192
public 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;
}
4、Looper 是如何与 Thread 关联的
Looper 与 Thread 之间是通过 ThreadLocal 关联的,这个可以看 Looper.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
中有一个 ThreadLocal
类型的 sThreadLocal
静态字段,Looper
通过它的 get
和 set
方法来赋值和取值。
由于 ThreadLocal
是与线程绑定的,所以我们只要把 Looper
与 ThreadLocal
绑定了,那 Looper
和 Thread
也就关联上了
5、在子线程中如何获取当前线程的 Looper
Looper.myLooper()
// Looper.java:203
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
//获取主线程looper
Looper.getMainLooper()
6、Looper.loop() 会退出吗?
不会自动退出,但是我们可以通过 Looper.quit()
或者 Looper.quitSafely()
让它退出。
两个方法都是调用了 MessageQueue.quit(boolean)
方法,当 MessageQueue.next()
方法发现已经调用过 MessageQueue.quit(boolean)
时会 return null
结束当前调用,否则的话即使 MessageQueue
已经是空的了也会阻塞等待
7、MessageQueue#next 在没有消息的时候会阻塞,如何恢复?
当其他线程调用 MessageQueue#enqueueMessage
时会唤醒 MessageQueue
,这个方法会被 Handler#sendMessage
、Handler#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;
}
8、Looper.loop() 方法是一个死循环为什么不会阻塞APP
线程是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程肯定不能运行一段时间后就自动结束了,那么如何保证一直存活呢??简单的做法就是可执行代码能一直执行下去,死循环便能保证不会被退出。把所有要做的任务放到循环中去做就不会觉得卡了。
9、子线程更新UI的方式
- Handler的sendMessage方式
- Handler的post方式
- Activity的runOnUiThread方法
- View的post方式
10、总结
Android中,有哪些是基于Handler来实现通信的?
答:App的运行、更新UI、AsyncTask、Glide、RxJava等处理Handler消息,是在哪个线程?一定是创建Handler的线程么?
答:创建Handler所使用的Looper所在的线程消息是如何插入到MessageQueue中的?
答: 是根据when在MessageQueue中升序排序的,when=开机到现在的毫秒数+延时毫秒数当MessageQueue没有消息时,它的next方法是阻塞的,会导致App ANR么?
答:不会导致App的ANR,是Linux的pipe机制保证的,阻塞时,线程挂起;需要时,唤醒线程子线程中可以使用Toast么?
答:可以使用,但是Toast的显示是基于Handler实现的,所以需要先创建Looper,然后调用Looper.loop。Looper.loop()是死循环,可以停止么?
答:可以停止,Looper提供了quit和quitSafely方法Handler内存泄露怎么解决?
答: 静态内部类+弱引用 、Handler的removeCallbacksAndMessages等方法移除MessageQueue中的消息