一、Handler的内存泄漏问题
背景:
private final Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
}
};
这相当于一个匿名内部类。编译器在创建内部类时会把外部类的引用传入其内部类,内部类隐式的持有着外部类的引用。Handler 为内部类持有外部类(Activity)的引用,而 Handler 一般都是耗时操作,当子线程在执行一项耗时操作时或者发送一个delay消息时,用户退出程序, Activity 需要被销毁,而 Handler 还在持有 Activity 的引用导致无法回收,就会导致内存泄漏。
private final Handler handler = new ExampleHandler ();
class ExampleHandler extends Handler{
@Override
public void handleMessage(Message msg) {}
}
在Handler构造函数中会检查内存是否泄漏抛异常
/**
* @hide 隐藏的构造方法,外部不可见
*/
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();
// 如果 Looper 还没初始化抛出异常
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
// 得到当前线程的 MessageQueue
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
如何解决内存泄漏:
方法一:
- 将内部类声明为静态的(静态内部类不会隐式持有外部类的引用)
- 使用弱引用持有外部类的引用
static class ExampleHandler extends Handler{
WeakReference<Activity> mActivity ;
public ExampleHandler(Activity activity){
mActivity = new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
Activity activity = mActivity.get();
if (activity == null){
return;
}
// TODO
}
}
方法二:
2、匿名类 Runnable 设置成静态的
private final ExampleHandler mHandler = new ExampleHandler (this);
private static final Runnable sRunnable = new Runnable() {
@Override
public void run() {//to something}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
finish();
}
}
二、Message和MessageQueue相关问题
Message 缓存池:
Message用于重复利用 Message 对象的机制。Message方法充分运用 Java 的高级语言特性,即类中持有一个类自身的属性作为经典数据结构中的链表 next 指针,以静态属性属于类本身的特性实现了链表的表头。
Message 只是一个发送消息的载体。创建方式:一种是new一个 Message 对象,另一种是调用 Message.obtain() 的方式去复用一个已经被回收的 Message。第一种需要不断的去创建新对象,可能会导致垃圾回收区域中新生代被占满,从而触发 GC。第二种会先检查是否可有复用对象,若无才去创建一个新的Message。
Message回收主要方式:
1、在 MessageQueue 中移除 Message 后:当消息在消息队列中被处理并且从消息队列中移除后,通常会触发 Message 的回收。这种情况下,Message 的回收是由 Looper 负责处理的。Looper 在分发 Message 给宿主 Handler 之后,会调用Message.recycle() 方法将其放回到缓存池中,以便下次重复利用。回收用过的 Message 不仅可以减少重复消耗系统资源而且回收成本低。
2、单次消息循环(Loop)结束后:在每次消息循环(Loop)结束时,通常会检查消息队列中是否有已处理的消息需要回收。如果某个消息已经被处理完毕,并且不再需要,那么会触发该消息的回收。
3、手动调用 Message 的 recycle() 方法后:在一些特殊情况下,我们可能会手动调用 Message 的 recycle() 方法来回收消息对象。这种情况下,我们可以主动释放消息对象的资源,以便更好地管理内存。需要注意的是,在调用 recycle() 方法之后,该消息对象的状态将不再可靠,因此在之后不能再使用该消息对象。
MessageQueue 的阻塞:
Message 是用于在线程之间传递消息的对象。MessageQueue 是用于存储和管理消息的队列,每个线程都有自己的 MessageQueue,用于存放该线程接收到的消息。MessageQueue 的消息存取,即向消息队列里面存储消息时,会拿当前的 MessageQueue 对象作为锁对象,通过加锁就可确保操作的原子性和可见性。消息的读取也是同理。
消息队列采用先进先出(FIFO)的原则,每次消息出队时如果没有合适的待取出消息就会阻塞线程等待有合适的消息。
调用 Handler 的 sendMessage() 方法发送一条消息时,实际是将该消息添加到该 Handler 对应的线程的 MessageQueue 中。该线程中的 Looper 负责不断地从 MessageQueue 中取出消息,并将其发送给相应的 Handler 进行处理。Handler 接收到消息后,会执行与该消息相关联的任务或操作。
三、Looper相关问题
数量关系:
一个 Thread 只有一个 Looper,一个 MessageQueen,可以有多个 Handler。数量级关系是: Thread(1) : Looper(1) :MessageQueue(1) : Handler(N)。
主线程与主线程创建Looper循环:
主线程(UI 线程)中的 Activity 包含一个 Looper 对象,并且这个 Looper 对象被用来管理主线程的消息队列(MessageQueue)。主线程中创建一个 Handler 对象时,默认情况下,该Handler 会与主线程的 Looper 相关联。
在子线程开启 Handler 要先创建 Looper,并开启 Looper 循环
1. prepare(); 2. loop(); 3. quit();
/**
* Quits the looper.
* <p>
* Causes the {@link #loop} method to terminate without processing any more messages in the message queue.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p><p class="note">
* Using this method may be unsafe because some messages may not be delivered before the looper terminates.
* Consider using {@link #quitSafely} instead to ensure that all pending work is completed in an orderly manner.
* </p>
*
* @see #quitSafely
*/
public void quit() {
mQueue.quit(false);
}
/**
* Quits the looper safely.
* <p>
* Causes the {@link #loop} method to terminate as soon as all remaining messages
* in the message queue that are already due to be delivered have been handled.
* However pending delayed messages with due times in the future will not be delivered before the loop terminates.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p>
*/
public void quitSafely() {
mQueue.quit(true);
}
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);
}
}
//将消息队列中的全部消息回收
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);
}
}
}