1、前言
Handler在Android中的地位不必说了,学习安卓开发必然跳不过Handler,讲解Handler的文章也是非常的多,这里我对我所了解的Handler这种Android中多线程间的通信方式的相关知识做一个总结。
2、Handler使用
Handler作为线程间通信的方式,最常使用的地方就是子线程更新UI。因为Android的UI控件不是线程安全的,如果在多线程下并发访问可能会导致UI控件处于不可预期的状态。所以在子线程想要更新UI的时候会使用handler.sendMessage(Message msg)方法通知主线程更新。
关于Handler的常用方法,除了sendMessage系列方法,handler还有一个post系列方法,可以在子线程中通过handler.post(Runnable r)方法进行一些在主线程的操作。
sendMessage系列方法:
public final boolean sendMessage(Message msg)
public final boolean sendEmptyMessage(int what)
public final boolean sendEmptyMessageDelayed(int what, long delayMillis)
public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis)
public final boolean sendMessageDelayed(Message msg, long delayMillis)
public final boolean sendMessageAtFrontOfQueue(Message msg)
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
复制代码
post系列方法:
public final boolean post(Runnable r)
public final boolean postAtTime(Runnable r, long uptimeMillis)
public final boolean postAtTime(Runnable r, Object token, long uptimeMillis)
public final boolean postDelayed(Runnable r, long delayMillis)
public final boolean postDelayed(Runnable r, Object token, long delayMillis)
public final boolean postAtFrontOfQueue(Runnable r)
复制代码
简单运用:
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_SEND:
mTitleText.setText((String) msg.obj);
break;
}
}
};
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
new Thread(new Runnable() {
@Override
public void run() {
try {
//子线程耗时操作
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.post(new Runnable() {
@Override
public void run() {
mTitleText.setText("post");
}
});
}
}).start();
break;
case R.id.button2:
new Thread(new Runnable() {
@Override
public void run() {
try {
//子线程耗时操作
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//发送消息
Message message = Message.obtain();
message.what = MESSAGE_SEND;
message.obj = "sendMessage";
handler.sendMessage(message);
}
}).start();
break;
}
}
复制代码
3、Handler源码阅读
学会使用后下一步就是学习原理 ,学习原理就免不了要阅读源码。 从上面的图已经能看出Handler的基本工作流程。过程中主要涉及了以下四个类:- Handler
- Looper
- MesageQueue
- Message
接下来首先就从Handler看起。
Handler的构造方法:
使用Handler第一步就是创建一个Handler对象,从而首先调用的就是Handler的构造方法。当然Handler构造方法有很多的不同参数的重载,这里只看最主要的两个。
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
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());
}
}
//获取当前线程的Looper
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
//给Handler中的成员变量初始化
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
复制代码
第一个是传参带有Looper的所调用的方法,其中就是做了些初始化的操作,调用这个方法创建的Handler的Looper就是作为参数传入的Looper。
第二个构造方法中第一个if判断当前Handler类是否有内存泄漏的隐患,主要是检验当前类是否是匿名类、成员类、局部类是否静态修饰等。接着通过Looper.myLooper()方法获取当前线程中的Looper对象。接下来判断如果这个Looper为空,说明当前Handler初始化所在线程没有Looper,会抛出Exception。这里就决定了Handler初始化所在线程必须有Looper,所以在子线程中创建Handler之前先要通过Looper.prepare()方法创建Looper。接着就是对Handler中一些成员变量进行初始化,将Looper中的消息队列引用传递给mQueue,将构造中传入的callback初始化等。
来看下Looper的myLooper()方法:
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
复制代码
这里是从ThreadLocal中获取到一个Looper。关于ThreadLocal的相关知识点可以看Android进阶知识:ThreadLocal。
Handler创建之后,在需要进行主线程操作的时候,我们会使用handler的sendMessage系列方法,或者post系列方法。这里同样有很多重载,具体的方法在前文中已经列举。这里先看post方法:
post系列方法:
public final boolean post(Runnable r){
return sendMessageDelayed(getPostMessage(r), 0);
}
复制代码
可以看到不管哪个post方法中,都是通过getPostMessage()方法构建一个Message最终还是调用对应的sendMessage方法。
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
复制代码
getPostMessage方法中通过Message.obtain()获取一个Message将传入的Runnable赋给Message中的callback,接着返回这个Message。
sendMessage系列方法:
因为post方法的最后又都调用了对应的sendMessage方法,所以接下来看sendMessage方法的实现:
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(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);
}
复制代码
这里看到经过层层调用最终执行了enqueueMessage方法。这里要注意的是sendMessageDelayed方法中设置的延迟时间是通过SystemClock.uptimeMillis() + 延迟时间来计算的。
SystemClock.uptimeMillis()方法是获取从开机到现在的毫秒数,与System.currentTimeMillis()获取从1970年1月1日到现在的毫秒数不同,后者会受到手机系统时间影响,而系统时间可以手动修改。sendMessageAtTime方法中对MessageQueue进行是否为null的判断,为null抛出异常,这里的MessageQueue就是在Handler构造函数中Looper中的Queue。
消息入队:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
复制代码
enqueueMessage方法中通过msg.target=this这一句,将Handler的引用传递给了Message中的target。接着调用了MessageQueue的enqueueMessage方法。接下来进入MessageQueue。
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
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 {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
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;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
复制代码
MessageQueue的enqueueMessage方法中,传入的msg就是要插入到消息队列的新消息,when是这个任务的延时时间。方法里首先对消息里的target也就是Handler进行空判断,为空抛出异常。接着判断了这个消息是否被使用和消息队列是否退出等。看到 msg.when = when;这一行将延迟时间传递保存到消息内部,下一行定义了一个临时指针p用来指向mMessage,这个mMessage就是消息队列的首结点,接下来的这个if-else做的就是将新消息根据他的when大小,将他按顺序加入到队列中合适位置上。这里可以看出这个消息队列实际上是个链表,每个Message是一个结点,结点中有一个next指针存放下一个结点位置。这里的新消息的添加,就是向这个链表中插入一个节点。
先看if中判断,p==null即首结点为null,when=0及延时时间为0,立即执行,when
取出处理消息:
有往消息队列里加消息,就有从消息队列取消息。谁来取呢?就是Looper,之前看到在Handler的构造方法里,通过Looper.myLooper()方法获取到当前线程(handler创建所在线程)的Looper对象。而且还知道了创建Handler的线程必须存在一个Looper对象否则会抛出异常。这也是我们不能在子线程里直接创建使用Handler的原因。那么为什么主线程可以直接创建Handler呢?是因为主线程中有Looper。那么主线程的Looper又是哪来的呢?这需要看到ActivityThread类里的代码。
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();
......
}
复制代码
ActivityThread中main方法在app启动时调用,这里省去了一些无关代码,只看与主线程Looper相关的。可以看到在main方法里调用了Looper.prepareMainLooper()方法,之后获取了一个主线程的Handler,接着调用了Looper.loop()方法。一个方法一个方法来看,先是prepareMainLooper()方法:
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));
}
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
复制代码
prepareMainLooper()方法中调了prepare(false)方法,这里quitAllowed传的是false,这个标记会传递到MessageQueue中,这里说明主线程的消息队列是不允许退出的。prepare()方法里初始化了new了一个Looper对象,并将它添加到当前线程的ThreadLocal中。接着到Looper的构造函数中看看:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
复制代码
Looper的构造函数很简单,创建了MessageQueue消息队列保存了一个当前线程的对象。从这里看出来创建一个Handler需要传入创建线程的Looper,而创建Looper又对应创建了一个MessageQueue。下面回到main方法中看thread.getHandler()这个获取主线程Handler方法:
final Handler getHandler() {
return mH;
}
复制代码
这里直接返回mH这个Handler,那么这个Handler是在哪里创建的呢?
final H mH = new H();
复制代码
跟踪下去发现这个mH是ActivityThread类的成员变量,并且直接初始化。所以这个Handler就是在main方法中创建ActivityThread对象时就初始化了。最后调用Looper.loop()方法开始循环取消息:
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
......
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
......
try {
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
......
}
}
复制代码
这里同样省略了部分代码,来看主要的流程,首先还是获取当前线程Looper对空做了校验。然后从Looper中获取到MessageQueue,接着进入for循环,调用queue.next()从消息队列中取出消息,根据注释,这个方法有可能会阻塞,如果返回的msg为null,说明消息队列正在退出。接着在try代码块中调用了msg.target.dispatchMessage(msg)方法,这个msg.target就是在前面enqueueMessage方法中设置的发送消息的Handler,所以这里调用了Handler的dispatchMessage(msg)方法。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
复制代码
dispatchMessage方法中对callback进行了判断,这里有两个callback,msg.callback是对应Handler中的post方法,将传入的Runnable存入Message的callback中,如果调用post方法msg.callback不为空调用handleCallback方法,最终会执行Runnable的run方法,开始执行post时传进来的任务。
private static void handleCallback(Message message) {
message.callback.run();
}
复制代码
第二个mCallback,对应的是创建Handler时的传参,如果不为空会执行mCallback.handleMessage方法。如果初始化时没传mCallback,就会执行handleMessage(msg)方法:
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}
复制代码
这个方法就是我们自己需要实现的处理消息的方法,也是我们最常用重写的方法。至此UI主线程中创建Handler,Looper,并且Looper开启轮询到调用了Handler的dispatchMessage处理消息的过程就结束了。
子线程Handler使用:
回到上面说的,这是主线程中Handler、Looper的初始化,那么要在子线程使用Handler该怎么做呢?
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
Handler handler = new Handler(Looper.myLooper()) {
@Override
public void handleMessage(Message msg) {
//处理消息
}
};
Looper.loop();
}
}).start();
复制代码
其实和主线程一样,因为子线程中没有Looper所以需要我们自己创建Looper并且调用Looper.loop()方法开始轮询。这里的Looper.prepare()方法和prepareMainLooper()方法一样最终会调用prepare(boolean quitAllowed)方法,这时传入的quitAllowed为true,表示消息队列可以退出。
至此Handler机制相关类Handler、Looper、MessageQueue的主要方法源码都看完了,他们之间的工作流程相互关系也都清楚了。
Message类:
其实还剩一个Message消息类,Message类中主要看一个obtain()方法,Message除了可以通过new来创建,还可以通过obtain()方法来获得,并且obtain()方法是从全局池中获取Message对象,能避免重新分配对象。
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不等于null,就从sPool头上去一个消息节点返回。所以使用obtain方法,相对直接new一个Message能减少内存分配。
4、Handler相关面试题
Q1:Handler机制中涉及到了哪些类?作用分别是什么?
A1:主要涉及到Handler、Looper、MessageQueue、Message这四个类。 Handler:发送消息到消息队列。
Looper:从消息队列中取出消息,交给Handler的dispatchMessage方法处理。
MessageQueue:消息队列存储管理消息插入取出。
Message:消息类,携带着消息数据。
Q2:MessageQueue 中的 Message有顺序吗?如果有是按什么顺序排列的?
A2:通过之前的源码阅读知道,是有顺序的,是根据Message.when这个相对时间排列的。
Q3:子线程中可以创建 Handler 对象吗?
A3:同样从源码中可以知道,子线程中不能直接创建Handler,Handler创建需要指定一个Looper,子线程中没有Looper,需要先创建Looper,调用Looper.loop方法。
Q4:MessageQueue内部实现是一个队列吗?
A4:不是,内部实现其实是一个单链表。
Q5:Looper的quit方法和quitSafely方法有什么区别?
A5:quit方法会清空消息队列中的所有消息,quitSafely方法只会清除所有延迟消息,非延迟消息还是分发出去交给Handler处理。具体还是看源码:
public void quit() {
mQueue.quit(false);
}
public void quitSafely() {
mQueue.quit(true);
}
复制代码
这里实际上是调用了MessageQueue的quit方法:
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
复制代码
quit方法传入的是false调用的是removeAllMessagesLocked()方法,quitSafely传入的是true调用的是removeAllFutureMessagesLocked方法。
private void removeAllMessagesLocked() {
Message p = mMessages;
while (p != null) {
Message n = p.next;
p.recycleUnchecked();
p = n;
}
mMessages = null;
}
复制代码
removeAllMessagesLocked方法中直接将所有消息清空。
private void removeAllFutureMessagesLocked() {
final long now = SystemClock.uptimeMillis();
Message p = mMessages;
if (p != null) {
if (p.when > now) {
removeAllMessagesLocked();
} else {
Message n;
for (;;) {
n = p.next;
if (n == null) {
return;
}
if (n.when > now) {
break;
}
p = n;
}
p.next = null;
do {
p = n;
n = p.next;
p.recycleUnchecked();
} while (n != null);
}
}
}
复制代码
removeAllFutureMessagesLocked方法中先做判断如果首节点when大于当前时间说明全是延迟消息,就同样调用removeAllMessagesLocked处理全部清空,否则循环找到队列中when大于now也就是大于当前时间的节点位置,将该节点消息同其后的所有消息清空。
Q6:为什么主线程不会因为Looper.loop()里的死循环卡死?
A6:这个涉及到Linux的Epoll机制。简单来说就是Android应用程序的主线程在进入消息循环过程前,会在内部创建一个Linux管道(Pipe),这个管道的作用是使得Android应用程序主线程在消息队列为空时可以进入空闲等待状态,并且使得当应用程序的消息队列有消息需要处理时唤醒应用程序的主线程。
具体解释:Android中为什么主线程不会因为Looper.loop()里的死循环卡死?
5、Handler引发内存泄漏
Handler导致的内存泄露,是平时写代码不注意非常容易出现的问题,而且内存泄露多了对应用性能影响较大,所以单独研究下。
一般我们使用Handler更新UI都是这样的:
public class HandlerActivity extends Activity {
private TextView mTextView;
private final int MESSAGE_SEND = 0x01;
private MyHandler handler = new MyHandler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
mTextView = findViewById(R.id.textView);
Message obtain = Message.obtain();
obtain.what = MESSAGE_SEND;
obtain.obj = "文字";
handler.sendMessageDelayed(obtain, 1000 * 60 * 10);
}
class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
if (msg.what == MESSAGE_SEND) {
mTextView.setText((String) msg.obj);
}
}
}
}
复制代码
这里看到进入Activity就发送了一个延迟消息,现实开发中可能是网络有延迟又或者进入一个界面后立刻离开这时数据还没加载好,只要是耗时任务还没有完成,当前的Activity又需要销毁,这时候因为此时MyHander,它隐式持有外部类的引用,当Activity销毁时,此时异步耗时任务还没有结束,仍然持有Activity的引用,使得Activity无法回收,造成内存泄漏。 通过集成LeakCanary可以检测到内存泄漏,如下图:
同样通过AndroidStudio Profiler可以查看到内存泄露: 通过多次打开关闭HandlerActivity,然后观察内存情况,可以发现即使在我手动GC多次后,仍然存在多个实例没有被回收的现象。内存泄漏解决方法:
- 将Handler定义为静态,静态内部类不会持有外部类的引用。
- 因为静态内部类不持有外部类引用,所以在Handler中无法访问外部类的成员,需要用一个外部类的弱引用来访问外部成员,又因为是弱引用,在GC时可以将其回收,不会造成内存泄露。
- 在Activity的onDestory方法中调用removeCallbacksAndMessages方法清除消息队列。
解决内存泄漏:
public class WeakHandlerActivity extends Activity {
private TextView mTextView;
private static final int MESSAGE_SEND = 1;
private MyHandler handler = new MyHandler(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
mTextView = findViewById(R.id.textView);
Message obtain = Message.obtain();
obtain.what = MESSAGE_SEND;
obtain.obj = "文字";
handler.sendMessageDelayed(obtain, 1000 * 60 * 10);
}
//静态内部类
static class MyHandler extends Handler {
private final WeakReference mActivty;
public MyHandler(WeakHandlerActivity activity) {
//初始化Activity的弱引用
mActivty = new WeakReference(activity);
}
@Override
public void handleMessage(Message msg) {
WeakHandlerActivity activity = mActivty.get();
if (activity != null) {
if (msg.what == MESSAGE_SEND) {
activity.mTextView.setText((String) msg.obj);
}
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
//清除消息队列
handler.removeCallbacksAndMessages(null);
}
}
复制代码
通过这三个方法结合使用,就可以解决Handler导致的内存泄漏的问题。这次再通过Profiler来查看内存情况:
可以看到在多次打开关闭界面后,仍然会存在多个WeakHandlerActivity实例。 但是在GC过后内存中的WeakHandlerActivity已经被全部回收,不会继续占用内存,造成泄漏。6、总结
-
Handler是Android提供的一种线程间通信方式。因为Android中UI控件不是线程安全的,多线程并发访问会出现同步问题,所以如果子线程想更新UI通常通过Handler来完成线程间通信。
-
Handler的工作流程主要是由Handler发送消息,将消息添加到消息队列MessageQueue中,再通过轮询器Looper从消息队列中取出消息,交给Handler去分发处理消息对应任务。
-
Handler使用时容易发生内存泄露,记得通过静态内部类+弱引用的方式使用Handler,并且在Activity的onDestory方法里记得清除消息队列。