Android 异步消息处理机制详解及源码分析,Android 多线程实现方式及并发与同步
public static void main(String[] args) {
SamplingProfilerIntegration.start();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
Security.addProvider(new AndroidKeyStoreProvider());
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
看见 22 行没?没说错吧?Looper.prepareMainLooper();
,我们跳到 Looper 看下 prepareMainLooper 方法,如下:
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
可以看到,UI 线程中会始终存在一个 Looper 对象(sMainLooper 保存在 Looper 类中,UI 线程通过 getMainLooper 方法获取 UI 线程的 Looper 对象),从而不需要再手动去调用 Looper.prepare()方法了。如下 Looper 类提供的 get 方法:
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
看见没有,到这里整个 Handler 实例化与为何子线程在实例化 Handler 之前需要先调运 Looper.prepare();语句的原理分析完毕。
3-1-3 Handler 与 Looper 实例化总结
到此先初步总结下上面关于 Handler 实例化的一些关键信息,具体如下:
在主线程中可以直接创建 Handler 对象,而在子线程中需要先调用 Looper.prepare()才能创建 Handler 对象,否则运行抛出”Can’t create handler inside thread that has not called Looper.prepare()”异常信息。
每个线程中最多只能有一个 Looper 对象,否则抛出异常。
可以通过 Looper.myLooper()获取当前线程的 Looper 实例,通过 Looper.getMainLooper()获取主(UI)线程的 Looper 实例。
一个 Looper 只能对应了一个 MessageQueue。
一个线程中只有一个 Looper 实例,一个 MessageQueue 实例,可以有多个 Handler 实例。
Handler 对象也创建好了,接下来就该用了吧,所以下面咱们从 Handler 的收发消息角度来分析分析源码。
3-2 继续看看 Handler 消息收发机制源码
3-2-1 通过 Handler 发消息到消息队列
还记得上面的例子吗?我们在 Child Thread 的最后通过主线程的 Handler 对象调运 sendEmptyMessage 方法发送出去了一条消息。
当然,其实 Handler 类提供了许发送消息的方法,我们这个例子只是用了最简单的发送一个 empty 消息而已,有时候我们会先定义一个 Message,然后通过 Handler 提供的其他方法进行发送。通过分析 Handler 源码发现 Handler 中提供的很多个发送消息方法中除了 sendMessageAtFrontOfQueue()方法之外,其它的发送消息方法最终都调用了 sendMessageAtTime()方法。所以,咱们先来看下这个 sendMessageAtTime 方法,如下:
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);
}
再看下 Handler 中和其他发送方法不同的 sendMessageAtFrontOfQueue 方法,如下:
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;
}
return enqueueMessage(queue, msg, 0);
}
对比上面两个方法可以发现,表面上说 Handler 的 sendMessageAtFrontOfQueue 方法和其他发送方法不同,其实实质是相同的,仅仅是 sendMessageAtFrontOfQueue 方法是 sendMessageAtTime 方法的一个特例而已(sendMessageAtTime 最后一个参数传递 0 就变为了 sendMessageAtFrontOfQueue 方法)。所以咱们现在继续分析 sendMessageAtTime 方法,如下分析:
sendMessageAtTime(Message msg, long uptimeMillis)方法有两个参数;msg 是我们发送的 Message 对象,uptimeMillis 表示发送消息的时间,uptimeMillis 的值等于从系统开机到当前时间的毫秒数再加上延迟时间。
在该方法的第二行可以看到 queue = mQueue,而 mQueue 是在 Handler 实例化时构造函数中实例化的。在 Handler 的构造函数中可以看见 mQueue = mLooper.mQueue;,而 Looper 的 mQueue 对象上面分析过了,是在 Looper 的构造函数中创建的一个 MessageQueue。
接着第 9 行可以看到,上面说的两个参数和刚刚得到的 queue 对象都传递到了 enqueueMessage(queue, msg, uptimeMillis)方法中,那就看下这个方法吧,如下:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
这个方法首先将我们要发送的消息 Message 的 target 属性设置为当前 Handler 对象(进行关联);接着将 msg 与 uptimeMillis 这两个参数都传递到 MessageQueue(消息队列)的 enqueueMessage()方法中,所以接下来我们就继续分析 MessageQueue 类的 enqueueMessage 方法,如下:
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("MessageQueue", 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;
}
通过这个方法名可以看出来通过 Handler 发送消息实质就是把消息 Message 添加到 MessageQueue 消息队列中的过程而已。
通过上面遍历等 next 操作可以看出来,MessageQueue 消息队列对于消息排队是通过类似 c 语言的链表来存储这些有序的消息的。其中的 mMessages 对象表示当前待处理的消息;然后 18 到 49 行可以看出,消息插入队列的实质就是将所有的消息按时间(uptimeMillis 参数,上面有介绍)进行排序。所以还记得上面 sendMessageAtFrontOfQueue 方法吗?它的实质就是把消息添加到 MessageQueue 消息队列的头部(uptimeMillis 为 0,上面有分析)。
到此 Handler 的发送消息及发送的消息如何存入到 MessageQueue 消息队列的逻辑分析完成。
那么问题来了!既然消息都存入到了 MessageQueue 消息队列,当然要取出来消息吧,不然存半天有啥意义呢?我们知道 MessageQueue 的对象在 Looper 构造函数中实例化的;一个 Looper 对应一个 MessageQueue,所以说 Handler 发送消息是通过 Handler 构造函数里拿到的 Looper 对象的成员 MessageQueue 的 enqueueMessage 方法将消息插入队列,也就是说出队列一定也与 Handler 和 Looper 和 MessageQueue 有关系。
还记不记得上面实例部分中 Child Thread 最后调运的 Looper.loop();方法呢?这个方法其实就是取出 MessageQueue 消息队列里的消息方法。具体在下面分析。
3-2-2 通过 Handler 接收发送的消息
先来看下上面例子中 Looper.loop();这行代码调运的方法,如下:
/**
Run the message queue in this thread. Be sure to call
{@link #quit()} to end the 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;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
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);
}
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that d
uring the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
Long.toHexString(ident) + " to 0x"
Long.toHexString(newIdent) + " while dispatching to "
msg.target.getClass().getName() + " "
msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
}
}
可以看到,第 6 行首先得到了当前线程的 Looper 对象 me,接着第 10 行通过当前 Looper 对象得到与 Looper 对象一一对应的 MessageQueue 消息队列(也就类似上面发送消息部分,Handler 通过 myLoop 方法得到 Looper 对象,然后获取 Looper 的 MessageQueue 消息队列对象)。17 行进入一个死循环,18 行不断地调用 MessageQueue 的 next()方法,进入 MessageQueue 这个类查看 next 方法,如下:
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
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) {
// 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;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (false) Log.v("MessageQueue", "Returning message: " + msg);
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf("MessageQueue", "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
可以看出来,这个 next 方法就是消息队列的出队方法(与上面分析的 MessageQueue 消息队列的 enqueueMessage 方法对比)。可以看见上面代码就是如果当前 MessageQueue 中存在待处理的消息 mMessages 就将这个消息出队,然后让下一条消息成为 mMessages,否则就进入一个阻塞状态(在上面 Looper 类的 loop 方法上面也有英文注释,明确说到了阻塞特性),一直等到有新的消息入队。
继续看 loop()方法的第 30 行(msg.target.dispatchMessage(msg);),每当有一个消息出队就将它传递到 msg.target 的 dispatchMessage()方法中。其中这个 msg.target 其实就是上面分析 Handler 发送消息代码部分 Handler 的 enqueueMessage 方法中的 msg.target = this;语句,也就是当前 Handler 对象。所以接下来的重点自然就是回到 Handler 类看看我们熟悉的 dispatchMessage()方法,如下:
/**
Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
可以看见 dispatchMessage 方法中的逻辑比较简单,具体就是如果 mCallback 不为空,则调用 mCallback 的 handleMessage()方法,否则直接调用 Handler 的 handleMessage()方法,并将消息对象作为参数传递过去。
这样我相信大家就都明白了为什么 handleMessage()方法中可以获取到之前发送的消息了吧!
对了,既然上面说了获取消息在 MessageQueue 消息队列中是一个死循环的阻塞等待,所以 Looper 的 quit 方法也很重要,这样在不需要时可以退出这个死循环,如上面实例部分使用所示。
3-2-3 结束 MessageQueue 消息队列阻塞死循环源码分析
如下展示了 Looper 类的 quit 方法源码:
public void quit() {
mQueue.quit(false);
}
看见没有,quit 方法实质就是调运了 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);
}
}
看见上面 2 到 4 行代码没有,通过判断标记 mQuitAllowed 来决定该消息队列是否可以退出,然而当 mQuitAllowed 为 fasle 时抛出的异常竟然是”Main thread not allowed to quit.”,Main Thread,所以可以说明 Main Thread 关联的 Looper 一一对应的 MessageQueue 消息队列是不能通过该方法退出的。
你可能会疑惑这个 mQuitAllowed 在哪设置的?
其实他是 MessageQueue 构造函数传递参数传入的,而 MessageQueue 对象的实例化是在 Looper 的构造函数实现的,所以不难发现 mQuitAllowed 参数实质是从 Looper 的构函数传入的。上面实例化 Handler 模块源码分析时说过,Looper 实例化是在 Looper 的静态方法 prepare(boolean quitAllowed)中处理的,也就是说 mQuitAllowed 是由 Looper.prpeare(boolean quitAllowed)参数传入的。追根到底说明 mQuitAllowed 就是 Looper.prpeare 的参数,我们默认调运的 Looper.prpeare();其中对 mQuitAllowed 设置为了 true,所以可以通过 quit 方法退出,而主线程 ActivityThread 的 main 中使用的是 Looper.prepareMainLooper();,这个方法里对 mQuitAllowed 设置为 false,所以才会有上面说的”Main thread not allowed to quit.”。
回到 quit 方法继续看,可以发现实质就是对 mQuitting 标记置位,这个 mQuitting 标记在 MessageQueue 的阻塞等待 next 方法中用做了判断条件,所以可以通过 quit 方法退出整个当前线程的 loop 循环。
到此整个 Android 的一次完整异步消息机制分析使用流程结束。接下来进行一些总结提升与拓展。
3-3 简单小结下 Handler 整个使用过程与原理
通过上面的源码分析原理我们可以总结出整个异步消息处理流程的关系图如下:
这幅图很明显的表达出了 Handler 异步机制的来龙去脉,不做过多解释。
上面实例部分我们只是演示了 Handler 的局部方法,具体 Handler 还有很多方法,下面详细介绍。
3-4 再来看看 Handler 源码的其他常用方法
在上面例子中我们只是演示了发送消息的 sendEmptyMessage(int what)方法,其实 Handler 有如下一些发送方式:
sendMessage(Message msg);
sendEmptyMessage(int what);
sendEmptyMessageDelayed(int what, long delayMillis);
sendEmptyMessageAtTime(int what, long uptimeMillis);
sendMessageDelayed(Message msg, long delayMillis);
sendMessageAtTime(Message msg, long uptimeMillis);
sendMessageAtFrontOfQueue(Message msg);
方法。
这些方法不再做过多解释,用法雷同,顶一个 Message 决定啥时发送到 target 去。
post(Runnable r);
postDelayed(Runnable r, long delayMillis);
等 post 系列方法。
该方法实质源码其实就是如下:
public final boolean postDelayed(Runnable r, long delayMillis)
{
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
额,原来 post 方法的实质也是调运 sendMessageDelayed()方法去处理的额,看见 getPostMessage(r)方法没?如下源码:
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
如上方法仅仅是将消息的 callback 字段指定为传入的 Runnable 对象 r。其实这个 Message 对象的 m.callback 就是上面分析 Handler 接收消息回调处理 dispatchMessage()方法中调运的。在 Handler 的 dispatchMessage 方法中首先判断如果 Message 的 callback 等于 null 才会去调用 handleMessage()方法,否则就调用 handleCallback()方法。那就再看下 Handler 的 handleCallback()方法源码,如下:
private static void handleCallback(Message message) {
message.callback.run();
}
额,这里竟然直接执行了 Runnable 对象的 run()方法。所以说我们在 Runnable 对象的 run()方法里更新 UI 的效果完全和在 handleMessage()方法中更新 UI 相同,特别强调这个 Runnable 的 run 方法还在当前线程中阻塞执行,没有创建新的线程(很多人以为是 Runnable 就创建了新线程)。
Activity.runOnUiThread(Runnable);
方法。
首先看下 Activity 的 runOnUiThread 方法源码:
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
看见没有,实质还是在 UI 线程中执行了 Runnable 的 run 方法。不做过多解释。
View.post(Runnable);和View.postDelayed(Runnable action, long delayMillis);
方法。
首先看下 View 的 postDelayed 方法源码:
public boolean postDelayed(Runnable action, long delayMillis) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.postDelayed(action, delayMillis);
}
// Assume that post will succeed later
ViewRootImpl.getRunQueue().postDelayed(action, delayMillis);
return true;
}
看见没有,实质还是在 UI 线程中执行了 Runnable 的 run 方法。不做过多解释。
到此基本上关于 Handler 的所有发送消息方式都被解释明白了。既然会用了基本的那就得提高下,接下来看看关于 Message 的一点优化技巧。
3-5 关于 Handler 发送消息的一点优化分析
还记得我们说过,当发送一个消息时我们首先会 new 一个 Message 对象,然后再发送吗?你是不是觉得每次 new Message 很浪费呢?那么我们就来分析一下这个问题。
如下是我们正常发送消息的代码局部片段:
Message message = new Message();
message.arg1 = 110;
message.arg2 = 119;
message.what = 0x120;
message.obj = "Test Message Content!";
mHandler.sendMessage(message);
相信很多初学者都是这么发送消息的吧?当有大量多次发送时如上写法会不太高效。不卖关子,先直接看达到同样效果的优化写法,如下:
mHandler.sendMessage(mHandler.obtainMessage(0x120, 110, 119, ""Test Message Content!""));
咦?怎么 send 时没实例化 Message?这是咋回事?我们看下mHandler.obtainMessage(0x120, 110, 119, "\"Test Message Content!\"")
这一段代码,obtainMessage 是 Handler 提供的一个方法,看下源码:
public final Message obtainMessage(int what, int arg1, int arg2, Object obj)
{
return Message.obtain(this, what, arg1, arg2, obj);
}
这方法竟然直接调运了 Message 类的静态方法 obtain,我们再去看看 obtain 的源码,如下:
public static Message obtain(Handler h, int what,
int arg1, int arg2, Object obj) {
Message m = obtain();
m.target = h;
m.what = what;
m.arg1 = arg1;
m.arg2 = arg2;
m.obj = obj;
return m;
}
看见没有?首先又调运一个无参的 obtain 方法,然后设置 Message 各种参数后返回。我们继续看下这个无参方法,如下:
/**
Return a new Message instance from the global pool. Allows us to
avoid allocating new objects in many cases.
*/
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();
}
真相大白了!看见注释没有?从整个 Messge 池中返回一个新的 Message 实例,在许多情况下使用它,因为它能避免分配新的对象。
所以看见这两种获取 Message 写法的优缺点没有呢?明显可以看见通过调用 Handler 的 obtainMessage 方法获取 Message 对象就能避免创建对象,从而减少内存的开销了。所以推荐这种写法!!!
3-6 关于 Handler 导致内存泄露的分析与解决方法
正如上面我们实例部分的代码,使用 Android Lint 会提示我们这样一个 Warning,如下:
In Android, Handler classes should be static or leaks might occur.
意思是说在 Android 中 Handler 类应该是静态的否则可能发生泄漏。
啥是内存泄漏呢?
Java 通过 GC 自动检查内存中的对象,如果 GC 发现一个或一组对象为不可到达状态,则将该对象从内存中回收。也就是说,一个对象不被任何引用所指向,则该对象会在被 GC 发现的时候被回收;另外,如果一组对象中只包含互相的引用,而没有来自它们外部的引用(例如有两个对象 A 和 B 互相持有引用,但没有任何外部对象持有指向 A 或 B 的引用),这仍然属于不可到达,同样会被 GC 回收。本该被回收的对象没被回收就是内存泄漏。
Handler 中怎么泄漏的呢?
当使用内部类(包括匿名类)来创建 Handler 的时候,Handler 对象会隐式地持有一个外部类对象(通常是一个 Activity)的引用。而 Handler 通常会伴随着一个耗时的后台线程一起出现,这个后台线程在任务执行完毕之后,通过消息机制通知 Handler,然后 Handler 把消息发送到 UI 线程。然而,如果用户在耗时线程执行过程中关闭了 Activity(正常情况下 Activity 不再被使用,它就有可能在 GC 检查时被回收掉),由于这时线程尚未执行完,而该线程持有 Handler 的引用,这个 Handler 又持有 Activity 的引用,就导致该 Activity 暂时无法被回收(即内存泄露)。
Handler 内存泄漏解决方案呢?
方案一:通过程序逻辑来进行保护
在关闭 Activity 的时候停掉你的后台线程。线程停掉了,就相当于切断了 Handler 和外部连接的线,Activity 自然会在合适的时候被回收。
如果你的 Handler 是被 delay 的 Message 持有了引用,那么使用相应的 Handler 的 removeCallbacks()方法,把消息对象从消息队列移除就行了(如上面的例子部分的 onStop 中代码)。
方案二:将 Handler 声明为静态类
静态类不持有外部类的对象,所以你的 Activity 可以随意被回收。代码如下:
评论