前言
因为Android采取了单线程UI模型,开发者无法在子线程中更新UI,为此Android为我们提供了Handler这个工具,可以开发者切换到主线程更新UI。Android最原始线程间通信都是基于Handler发送消息,来进行线程间通信方案的。
事例
首先看一段示例代码
public class LeakActivity extendsAppCompatActivity {
privateHandler mHandler;
@Override
protectedvoid onCreate(BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
mHandler = new Handler() {
@Override
public void handleMessage(Messagemsg) {
super.handleMessage(msg);
}
};
Message message = Message.obtain();
message.what = 1;
mHandler.sendMessageDelayed(message,30 * 1000);
}
}
这段代码的逻辑很简单,mHandler延时了10分钟发送消息,类似的代码在我们的项目中也经常出现,但是这样的代码会出现一个问题。
问题
我们首先打开 LeakActivity ,然后按返回键将这个Activity finish 掉。等待几秒logcat控制台就会看到内存泄漏现象。
原因
究竟是什么时候发生了内存泄露的问题呢?
我们知道在java中,非静态内部类会隐式地持有外部类的引用,静态内部类则不会。在上面的代码中,Message在小溪队列中延时了30秒钟,然后才处理该消息。而这个消息引用了Handler对象,Handler对象又隐式地持有了Activity对象,当发生GC是以为message - handler - activity 的引用链导致Activity无法被回收,所以发生了内存泄漏的问题。
危害
众所周知,内存泄露在 Android 开发中是一个比较严重的问题,系统给每一个应用分配的内存是固定的,一旦发生了内存泄露,就会导致该应用可用内存越来越小,严重时会发生OOM 导致 Crash。
解决
这个问题该如何解决呢?
1、使用弱引用。
概念:java对于强引用的对象,就绝不收回。对于软引用和弱引用的对象,是能不收回就不收回,这里的能不收回就是指内存足够的情况。但是内存不足,系统自动触发GC时,会回收弱引用和软引用修饰的对象。
很显然,出现内存泄漏问题的原因,就是Handler对Activity是强引用,导致GC在回收Activity时无法回收。为了解决这个问题,我们可以把Handler对Activity弱引用,这样GC就能把Activity及时的回收,从而杜绝内存泄漏的问题。
public class NoLeakActivity extends AppCompatActivity{
privateNoLeakHandler mHandler;
@Override
protectedvoid onCreate(BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
mHandler = new NoLeakHandler(this);
Message message = Message.obtain();
mHandler.sendMessageDelayed(message,30 * 1000);
}
privatestatic class NoLeakHandler extends Handler {
privateWeakReference<NoLeakActivity>mActivity;
public NoLeakHandler(NoLeakActivity activity) {
mActivity = new WeakReference<>(activity);
}
@Override
publicvoid handleMessage(Message msg){
super.handleMessage(msg);
final Activityactivity = mActivity.get();
if (activity == null) {
return;
}
..............
}
}
@Override
protectedvoid onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
}
2、及时清除消息。
刚才我们说到,正是因为被延时处理的message持有Handler的引用,Hander持有对Activity的引用。形成了message- handler - activity 这样一条引用链,导致 Activity 的泄漏。因此我们可以尝试在当前界面结束时将消息队列中未被处理的消息清除。从源头上解除了这条引用链,是Activity能及时地被回收。
public class LeakActivity extendsAppCompatActivity {
privateHandler mHandler;
@Override
protectedvoid onCreate(BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
mHandler = new Handler() {
@Override
public void handleMessage(Messagemsg) {
super.handleMessage(msg);
}
};
Message message = Message.obtain();
message.what = 1;
mHandler.sendMessageDelayed(message,30 * 1000);
}
@Override
protectedvoid onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
}
运行 Ok。