使用Handler时内存泄漏分析
在Android中,处理完异步任务后常常会在主线程进行一些操作,所以我们可能会使用到Handler,下面是Handler的常见使用方法:
public class MainActivity extends AppCompatActivity {
private Handler mHanlder = new Handler() {
@Override
public void handleMessage(Message msg) {
//TODO
}
};
}
但是我们这样使用时就会看到这样一句提示:
This Handler class should be static or leaks might occur
为什么会有这样的提示呢?
在Java中,匿名(非静态)内部类会隐性引用外部对象,而静态内部类则不会。
所以导致内存泄漏的原因是Activity被mHandler引用,而mHandler被其它比Activity生命周期更长的对象强引用。先上张Handler可以跨线程通信的简要说明图:
##Handler工作原理图
一个线程只有一个Looper,一个Looper对应一个MessageQueue,但一个线程可对应多个Handler。
Handler可在任意线程创建,就以文章开头提到的最常见Handler创建方式为例:
mHandler = new Handler(){}是Activity的一个成员变量,Handler()无参构造函数相当于调用Handler(Looper.myLooper()),即将Handler与创建Activity所在的主线程的Looper绑定,与Handler(Looper.getMainLooper())是一样的效果,所以通过此Handler发送的消息可以会主线程进行处理,可以处理UI更新之类的消息。
源码分析
简单看了Handler工作原理图后,我们深入源码分析下为何Handler没释放?在此可根据源码简单分析一下:
//Activity中含有一个成员变量mHandler,mHandler是一个匿名内部类的实例,让我们看看Handler的构造函数中做了什么
public Handler() {
this(null, false);
}
//Handler默认构造函数最终会调到此方法
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());
}
}
//Handler中有一个mLooper
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
//消息队列mQueue
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
大家已经看到Handler有一个成员变量是mLooper,那么此Looper是如何实例化的呢?
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
通过上述代码可以看到,Looper实例原来与当前线程相关联,而创建Handler时调用 myLooper() 方法是在哪个线程?主线程。
所以说Handler实例中的成员变量mLooper与mQueue最起码在APP运行期间一直存在。那么这又与内存泄漏有什么关系呢? 毕竟是mHandler强引用mLooper,而不是mLooper强引用mHandler,为什么不能释放?
那么这就要说到我们使用Handler时必会做一事情,postMessage()
下面继续看下Handler中发送消息的源码:
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
public static Message obtain(Handler h) {
Message m = obtain();
//Handler已被Message强引用!
m.target = h;
return m;
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public final boolean sendMessageAtFrontOfQueue(Message msg) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
//最终会调到mQueue的enqueueMessage()方法
return enqueueMessage(queue, msg, 0);
}
boolean enqueueMessage(Message msg, long when) {
//...
Message p = mMessages;
//...
//用单向链表把msg保存起来
Message prev;
for (;;) {
prev = p;
p = p.next;
//已到链表底部或此msg的执行时间小于p的执行时间则退出循环,之后便可将新msg插入到prev结点与p结点之间,
//保证MessageQueue中的Message按执行时间有序排序。其中when是(SystemClock.uptimeMillis() + delayMillis)
//而不是传入的delayMillis,这也是Handler可以保证让Message顺序执行的原因。
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
//...
}
从上述代码可以看到,当我们用Handler post消息时,Message被保存到Looper.mQueue中,那么Message什么时候被销毁呢?让我们来看Looper如何对Message进行处理:
//ActivityThread.java
public final class ActivityThread {
public static void main(String[] args) {
//...
Looper.loop();
//...
}
}
//Looper.java
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.");
}
final MessageQueue queue = me.mQueue;
//...
for (;;) {
//此处mQueue解除对msg的强引用,下面细分析
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
//target即Handler, 在此处将msg交给Handler处理,所以一个MessageQueue中的每个Message都可对应不同的Handler。
msg.target.dispatchMessage(msg);
//...
msg.recycleUnchecked();
}
}
//Message.java
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
//此处不在强引用Handler
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
//Message加入对象池等待复用
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
//MessageQueue.java
Message next() {
//...
int nextPollTimeoutMillis = 0;
for (;;) {
//不到执行时间阻塞,Message可以准时执行的原因
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
//计算msg准时执行需要等待时间
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
//Message引用方式prevMsg->msg(prevMsg.next)->msg.next,通过此方法将msg从单向链表中移除,
//MessageQueue对Message实例的引用已解除
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
//...
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
源码中重点位置已加注释,通过上述分析,我们终于可以得到一个完整的内存泄漏引用路径:
Activity <-Handler <- Message <-MessageQueue <-Looper <- MainThread
只要还有发出的Message未处理,Activity就不能释放,所以我们应该知道如何修改了:
- 解除Activity与Handler之间的引用:
private SafeHandler mHandler = new SafeHandler(this);
private static class SafeHandler extends Handler{
WeakReference<MainActivity> mActivityReference;
public SafeHandler(MainActivity activity){
mActivityReference = new WeakReference<MainActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = mActivityReference.get();
if(activity != null){
activity.handleMessage(msg);
}
}
}
private void handleMessage(Message msg) {
//TODO
}
- 解除Handler与Message间的引用
mHandler.removeCallbacksAndMessages(null);
Android消息处理机制
根据上述的源码分析我们也理清了Message、Handler、MessageQueue与Looper这四者之间的协作机制:
Message:
用途:携带要发送到Looper所在线程的任务信息。
直接被引用位置:MessageQueue
Handler:
用途:通过Looper绑定目标线程,负责在任意线程将Message实例加入目标线程的Looper的MessageQueue中,之后用于接收要在目标线程处理的Message信息。
直接被引用位置:任何对象都可包含Handler,Handler通过引用存储在Thread上的Looper来建立绑定关系。
MessageQueue:
用途:MessageQueue引用所有要发送的Message,持有一个对应的Handler实例引用,将每一个新加入的Message都插入单向链表中来保存。
直接被引用位置:每个Looper实例化时都会初始化一个MessageQueue对象.
Looper:
用途:负责接收Message将其加入MessageQueue,之后再派发出去交给每个Message对应的Handler进行处理。
直接被引用位置:Thread的ThreadLocal(线程局部变量)。
动画
下面来个图比喻下(其实不太恬当,无实操>.>):
班组:Thread
煤块:Message
皮带:MessageQueue
煤块工人:Handler
皮带运输机:Looper
任意班组的煤块工人都可以扔煤块过来(任意Thread都可以通过Handler可以向这个线程发Message),扔到皮带上皮带将其按一定次序整理好(将Message加入MessageQueue),之后运输机带动皮带将其一个个处理(Looper.loop()),每个煤块上都有煤块工人的标记(Message引用Handler),皮带运输机根据标记将其运到各个煤块工人对应班组的煤块堆上(Looper将Message发到相应的Handler的handleMessage()处理)。当然图上只有一个堆可以看成只有一个班组的煤块工人在扔。
Done.