Handler 源码解析系列文章:
- Handler 源码解析(一)—— Handler 的作业流程
- Handler 源码解析(二)—— 正确创立 Handler 目标
- Handler 源码解析(三)—— Handler 内存走漏
1. 匿名内部类是导致 Handler 内存走漏的实质原因吗?
很多人说,导致 Handler 内存走漏的原因是:假如 Handler 发送了一个延迟很长时刻或者周期性的音讯,而在音讯处理前 Activity 现已被毁掉,Handler 依然持有对 Activity 的引证,或许导致内存走漏。
咱们都知道匿名内部类会持有外部类的引证,当咱们在 Activity 中创立如下 Handler 实例时,会提示有内存走漏危险:
那么导致该危险的根本原因是匿名内部类持有外部类的引证吗?
咱们再看一个比如:
咱们经常运用匿名内部类给控件增加点击事件,但在这儿从未呈现内存走漏危险提示,也从未见过谁剖析此处会存在内存走漏的危险。能够看出,导致 Handler 呈现内存走漏的实质原因并不是匿名内部类持有外部类的引证。依据这个,咱们只是能够知道 Handler 目标持有了 Activity.this。
依据可达性剖析,被 GCRoots 直接或直接引证的目标是不能够被收回的。那 Handler 呈现内存走漏时,一定是被某个 GCRoots 直接或直接引证着。
假如不了解 GC,建议先了解一下。 点击阅览:JVM(三)—— 废物收回机制
回过头来,咱们能够得出结论:匿名内部类会持有外部类的引证,但外部类开释时,匿名内部类也会被开释,这并不是导致 Handler 产生内存走漏的实质原因,但能够作为一个直接原因。
那么GCRoots 又是谁,下文接着剖析。
2. Handler 内存走漏原因
2.1 在主线程中创立 Handler 目标
在主线程中创立 Handler 目标:
// MainActivity.java
Handler handler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
// 修正 TextView 内容
}
};
new Thread(new Runnable() {
@Override
public void run() {
Message msg = Message.obtain();
...
handler.sendMessageDelayed(msg, 20000);
}
}).start();
然后在子线程中发送一个延迟音讯,立刻毁掉 MainActivity ,会产生内存走漏。 操作手顺:
- 点击按钮,打开 MainActivity 页面。
- 在 20s 内毁掉 MainActivity 页面。
- 手动 GC。
经过 Profiler 进行剖析,会发现内存产生走漏,引证链:
2.1.1 sMainLooper 作为 GCRoot
经过匿名内部类持有外部类的目标,咱们能够知道Handler 持有了 MainActivity.this。
在上一篇文章中,咱们说到 Handler 中音讯入队办法 enqueueMessage()
:
// Handler.java
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
第4行处,msg.target
引证了 Handler 的目标。也就是 Message 的目标 msg 持有了 handler 的引证。
第10行处,调用了 MessageQueue 的 enqueueMessage()
办法并传入了 msg:
// MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) {
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
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 中的 mMessages 会有对该音讯的引证,所有待处理的音讯被组织成一个单向链表运用 next 特点来指示下一个音讯的方位。MessageQueue 目标持 有了 msg 的引证。
MessageQueue 又被谁持有呢,在 Handler 的结构函数中:
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
第3行处,依据 mQueue = mLooper.mQueue
,估测应该在 Looper 中进行了赋值,接着看 Looper 的结构函数:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
能够发现,在创立 Looper 目标时,一起创立了一个 MessageQueue 实例。 Looper 目标持有了 MessageQueue 目标。一起能够发现,Looper 中的 mQueue 为 final 目标,Looper 对应的 mQueue 不能够被修正:
// Looper.java
final MessageQueue mQueue;
Looper 目标又被谁持有了呢?检查 ActivityThread 中的 main() 办法,其中:
public static void main(String[] args) {
...
Looper.prepareMainLooper();
...
Looper.loop();
...
}
第3行,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();
}
}
第 2 行,经过 prepare 办法创立了 Looper 目标,在第 7 行,sMainLooper 持有了创立的 Looper 目标。
// Looper.java
private static Looper sMainLooper;
sMainLooper 是 static 修饰的,就是咱们所说的 GCRoot。综上,存在如下引证链:
2.1.2 活动中的线程作为 GCRoots
其实还存在另外一条引证链, 检查prepareMainLooper()
的 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));
}
第5行,new 出一个 Looper 目标并调用了 ThreadLocal 的 set 办法:
// ThreadLocal.java
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
// ThreadLocal.java
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// Thread,java
ThreadLocal.ThreadLocalMap threadLocals = null;
经过 set 办法能够发现,Looper 目标放到了 ThreadLocalMap 中,而第4行的 ThreadLocalMap 目标,是经过当时线程取得的。当时 thread 持有 ThreadLocalMap 目标。ThreadLocalMap 目标经过 Entry 持有 Looper 目标。
这儿有个简单搞错的点,Looper 目标是 Entry 节点中的一个 value,并不是被 sThreadLocal 持有。经过 sThreadLocal.set(new Looper(quitAllowed))
将 Looper 目标作为 value 值增加到 ThreadLocalMap 的 Entry中:
// ThreadLocal.java ThreadLocalMap
private void set(ThreadLocal<?> key, Object value) {
...
tab[i] = new Entry(key, value);
...
}
// ThreadLocal.java ThreadLocalMap
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private Entry[] table;
咱们能够发现,Entry 经过弱引证去引证 key 值(ThreadLocal 目标),经过强引证去引证 value 值(Looper 目标)。
所以 ThreadLocal.ThreadLocalMap 经过 Entry 强引证了 Looper 目标。而当时 Thread 持有了 ThreadLocalMap 目标。
活动的线程也是 GCRoot ,不能被收回。假如线程一向处于运转中,则一向会存在如下引证链:
2.2 在子线程中创立 Handler 目标
// 仅作为测试代码
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
handler = new Handler(Looper.myLooper()){
@Override
public void handleMessage(@NonNull Message msg) {
if (msg.what == 1){
binding.text.setText("111111111");
}
}
};
handler.sendEmptyMessageDelayed(1, 20000);
Looper.loop();
}
}).start();
同样发送延迟音讯,只不过本次的 Handler 目标是在子线程中创立的,子线程中的 Looper 目标是经过第5行调用 Looper.prepare()
直接创立的。
与主线程不同的是,这次不会调用 prepareMainLooper()
办法了,自然也就不存在以 sMainLooper 为 GCRoot 的引证链。另一条引证链同主线程剖析时相同,存在。只需创立 Looper 目标的线程存在,就会存在如下引证链,然后导致内存走漏:
上述的引证联系会一向保持,直到 Handler 音讯行列中的所有音讯被处理完毕。在 Handler 音讯行列还有未处理的音讯 / 正在处理音讯时,此刻若需毁掉外部类 MainActivity ,但由于上述引证联系,废物收回器(GC)无法收回MainActivity,然后形成内存走漏。
形成内存走漏的两个要害条件:
- 存在 “未被处理 / 正处理的音讯 -> Handler 实例 -> 外部类” 的引证联系
- Handler 的生命周期 > 外部类的生命周期
3. Handler 内存走漏的解决方案
3.1 静态内部类 + 弱引证
将Handler
的子类设置成静态内部类。静态内部类不持有外部类的引证。此外,可运用 WeakReference 弱引证持有外部类,保证外部类能被收回。
public class MainActivity extends AppCompatActivity {
private UIHandler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler = new UIHandler(this, Looper.myLooper());
new Thread(new Runnable() {
@Override
public void run() {
Message msg = Message.obtain();
msg.what = 1;
msg.obj = "Hello";
handler.sendMessage(msg);
}
}).start();
}
// 设置为:静态内部类
private static class UIHandler extends Handler{
// 界说弱引证实例
private final WeakReference<Activity> mReference;
// 在结构办法中传入需持有的Activity实例
public UIHandler(Activity activity, Looper looper) {
super(looper);
// 运用 WeakReference 弱引证持有 Activity 实例
mReference = new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
...
break;
case 2:
...
break;
}
}
}
}
3.2 清空音讯行列
当外部类完毕生命周期时,清空Handler内音讯行列
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
终究会调用 MessageQueue 中的 removeCallbacksAndMessages()
:
void removeCallbacksAndMessages(Handler h, Object object) {
if (h == null) {
return;
}
synchronized (this) {
Message p = mMessages;
// Remove all messages at front.
while (p != null && p.target == h
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
p.recycleUnchecked();
p = n;
}
// Remove all messages after front.
while (p != null) {
Message n = p.next;
if (n != null) {
if (n.target == h && (object == null || n.obj == object)) {
Message nn = n.next;
n.recycleUnchecked();
p.next = nn;
continue;
}
}
p = n;
}
}
}
该函数中为什么进行两次循环?
一个线程中,音讯行列与 Handler 实例的份额为 1:n。如下图所示,一个音讯行列中的音讯或许由不同的 Handler 目标发送过来的,而 mHandler.removeCallbacksAndMessages(null)
移除的是指定 Handler 目标对应的音讯。
若当时音讯行列队头音讯 mMessages 为想要清空 Handler 目标所发出的,则进行第一次循环,否则进行第二次循环。