内存泄漏:当一个无用的对象仍然被其它对象所持有引用,从而造成该对象无法被系统回收,以致该对象在堆中所占用的内存单元无法被释放而造成内存空间浪费;这就是内存泄漏。
单例造成内存泄漏
在自定义工具类时,经常会使用到单例模式,也经常这么写代码:
public class Utils {
private static Utils utils;
private Context mContext;
private Utils(Context context) {
this.mContext = context;
}
public static Utils getInstance(Context context) {
if (utils == null) {
utils = new Utils(context);
}
return utils;
}
}
如上面的代码,当我们调用 getInstance 方法时,传入的 context 参数是 Activity 等上下文,因为静态单例在应用程序的整个生命周期中存在,就会导致内存泄漏。
举个例子:
当我们新建一个Activity 并且调用 getInstance 方法时,我们将 Activity 作为 context 传进来,这样 Utils 类就持有了 activity 的引用;当我们在退出 activity 时,activity 就没有用了,应该被回收,但是 utils 对象会持有这个 Activity 的应用,导致这个 Activity 无法正常被回收,从而造成内存泄漏。
为了避免这种情况发生,应该这样写:
private Utils(Context context) {
this.mContext = context.getApplicationContext();
}
getApplicationContext 获取的就是应用程序的上下文,和单例生命周期一样长,从而避免内存泄漏。
静态变量造成内存泄漏
静态变量存储在方法区,它的生命周期从类加载开始,到整个进程结束。
当我们新建静态持有变量的时候要考虑各个成员之间的引用关系,尽可能少的使用静态变量,以免发生内存泄漏。
当我们使用后,需要在适当的时候将静态变量重置为 null ,使其不再持有引用。
非静态内部类造成内存泄漏
非静态内部类(包括匿名内部类)默认会持有外部类的引用,当静态内部类对象的生命周期比外部类对象的生命周期长时,就会导致内存泄漏。
非静态内部类导致的内存泄露典型的例子就是 Handler 的使用了,在使用时,大多数我们会这么写:
public class RJPActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.item_rjp);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
sendMessage();
}
});
}
private void sendMessage() {
Message msg = Message.obtain();
msg.what = 1;
mHandler.sendMessage(msg);
}
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 1) {
Toast.makeText(RJPActivity.this, "提示", Toast.LENGTH_SHORT).show();
}
}
};
}
上面代码中,mHandler 是 Activity d的非静态内部类实例,持有 Activity 的引用;mHandler 作为成员变量保存在 message 中,所以 message 也持有 Activity 的引用;这样 Looper 从 MessageQueue 中取出消息并处理消息时,此时 Activity 有可能已经关闭了;因为 message 持有 Activity 的引用,所以 Activity 并不会回收,从而导致内存泄漏。
当我们需要使用内部类,但又要避免内存泄漏,一般会采用静态内部类 + 弱引用的方式。修改后代码如下:
public class RJPActivity extends Activity {
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.item_rjp);
mHandler = new MyHandler(this);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
sendMessage();
}
});
}
private void sendMessage() {
Message msg = Message.obtain();
msg.what = 1;
mHandler.sendMessage(msg);
}
private class MyHandler extends Handler {
private WeakReference activityWeakReference;
public MyHandler(RJPActivity activity) {
activityWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
RJPActivity activity = activityWeakReference.get();
if (activity != null) {
if (msg.what == 1) {
Toast.makeText(RJPActivity.this, "提示", Toast.LENGTH_SHORT).show();
}
}
}
}
}
这样便能避免内存泄漏了,但是防止 message 还在遗留消息队列中,最好在 Activity 销毁时,将 handler 的回调和发送的消息移除:
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
当我们使用Thread 或者 AsyncTask 时,也要采用静态内部类 + 弱引用的方式,避免内存泄漏。
未取消注册广播/回调造成内存泄漏
如果我们使用了广播,使用后没有取消注册,那么这个广播就会一直存在于系统中,也就是说非静态内部类持有 Activity 的引用,导致内存泄漏;现在使用系统原生的广播很少了,一般用 EventBus 代替,所以应当使用 EventBus 给我们的方法取消注册广播。
如果我们使用到观察者模式,没有取消也会造成内存泄漏。如使用 Retrofit + RxJava 注册网络请求。
Timer/TimerTask造成内存泄露
Timer/TimerTask 在 Android 中使用也非常普遍,一般用于计时任务或循环任务。
平时操作 Timer 一般都这么写:
public class TimerTaskActivity extends Activity {
private Handler mHandler;
private Timer timer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.item_rjp);
mHandler = new MyHandler(TimerTaskActivity.this);
timer = new Timer(true);
timer.schedule(timerTask, 1000, 1000); //延时1000ms后执行,1000ms执行一次
}
TimerTask timerTask = new TimerTask() {
public void run() {
Message message = new Message();
message.what = 1;
mHandler.sendMessage(message);
}
};
private class MyHandler extends Handler {
private WeakReference activityWeakReference;
public MyHandler(TimerTaskActivity activity) {
activityWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
TimerTaskActivity activity = activityWeakReference.get();
if (activity != null) {
if (msg.what == 1) {
//回到主线程执行结束操作
Toast.makeText(TimerTaskActivity.this,"结束计时",Toast.LENGTH_SHORT).show();
}
}
}
}
private void stopTimer() {
if (timer != null) {
timer.cancel();
timer.purge();
timer = null;
}
// if (mTimerTask != null) {
// mTimerTask.cancel();
// mTimerTask = null;
// }
}
@Override
protected void onDestroy() {
super.onDestroy();
stopTimer();
}
}
当 Activity 销毁时将 Timer 和 TimerTask 取消,以免造成内存泄漏。
集合中的对象未清理造成内存泄漏
我们将一个对象添加到 List 等集合中,这个集合就会持有该对象的引用。
当使用完对象后,也没有将这个对象移除,这个便造成了内存泄漏。
如果集合被静态引用,也会造成内存泄漏。
当使用完集合时,应当及时清除,以免造成内存泄漏。
属性动画造成内存泄漏
使用属性动画时,应当添加下面代码:
@Override
protected void onDestroy() {
super.onDestroy();
mAnimator.cancel();
}
以免造成内存泄漏。
WebView造成内存泄露
WebView 已经为我们提供了销毁的方法:destroy() ,但有时候并不能将 WebView 销毁释放内存。
因为 WebView 中 Callback 也会持有 Activity 对象,从而造成无法释放内存。
所以在销毁 WebView 之前应该将 WebView 从父容器中移除,再销毁。
如下面代码:
@Override
protected void onDestroy() {
super.onDestroy();
// 先从父控件中移除WebView
mWebViewContainer.removeView(mWebView);
mWebView.stopLoading();
mWebView.getSettings().setJavaScriptEnabled(false);
mWebView.clearHistory();
mWebView.removeAllViews();
mWebView.destroy();
}
以免造成内存泄漏。