内存泄露是指无用对象(不再使用的对象)持续占有内存 或 无用对象的内存得不到及时释放,从而造成的内存空间的浪费称为内存泄露。
长生命周期的对象持有短生命周期对象的引用,此时就很有可能发生内存泄露。尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这是java中内存泄露的常见场景。
public class MainActivity extends Activity{
public static Context mContext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext = this;
}
}
解决办法:1使用Application的Context。 2慎用statistic关键字
RetrofitHelper.getInstance(MainActivity.this)
解决办法:1使用Application的Context。不使用Activity的Context。
属性动画中有一类无限循环的动画,如果在Activity 中播放此类动画却没有在onDestroy 中去停止动画,那么动画会一直播放下去的。尽管已近无法在界面上看到动画效果,但这个时候的Activity 的View会被动画持有,而View有持有Activity ,最终无法释放。
public class HomeActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ObjectAnimator animator = ObjectAnimator.ofFloat(btn_home, "ratation", 0, 360).setDuration(2000);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.start();
}
}
解决方案:在当前Activity的onDestroy()方法中取消动画:animator.cancel()。
注册广播、注册观察者,未进行注销会导致Gc无法回收内存泄露
解决方案:进行注销
我们通常把一些对象的引用加入到了集合中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。
解决方案:将集合置为 null ,避免使用static.
资源性对象如Cursor、File、Socket,应该在使用后及时关闭。未在finally中关闭,会导致异常情况下资源对象未被释放的隐患。
解决方案:在 finalize(),调用它的close()函数
及时销毁
系统能够确认Bitmap分配的内存最终会被销毁,但是由于它占用的内存过多,所以很可能会超过Java堆的限制。因此,在用完Bitmap时,要及时的recycle掉。recycle并不能确定立即就会将Bitmap释放掉,但是会给虚拟机一个暗示:“该图片可以释放了”
设置一定的采样率
设置一定的采样率,可以大大减小占用的内存
解决方案: 在用完Bitmap时,要及时的recycle掉; 设置一定的采样率
如果我们不去使用convertView,而是每次都在getView()中重新实例化一个View对象的话,即浪费时间,也造成内存垃圾,给垃圾回收增加压力,如果垃圾回收来不及的话,虚拟机将不得不给该应用进程分配更多的内存,造成不必要的内存开支
解决方案: 复用convertView
非静态内部类(包括匿名内部类)默认就会持有外部类的引用,当非静态内部类对象的生命周期比外部类对象的生命周期长时,就会导致内存泄露。
非静态内部类导致的内存泄露在Android开发中有一种典型的场景就是使用Handler,很多开发者在使用Handler是这样写的:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
start();
}
private void start() {
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) {
// 做相应逻辑
}
}
};
}
熟悉Handler消息机制的都知道,mHandler
会作为成员变量保存在发送的消息msg
中,即msg
持有mHandler
的引用,而mHandler
是Activity的非静态内部类实例,即mHandler
持有Activity的引用,那么我们就可以理解为msg
间接持有Activity
的引用。msg
被发送后先放到消息队列MessageQueue
中,然后等待Looper
的轮询处理(MessageQueue
和Looper
都是与线程相关联的,MessageQueue
是Looper
引用的成员变量,而Looper
是保存在ThreadLocal
中的)。那么当Activity
退出后,msg
可能仍然存在于消息对列MessageQueue
中未处理或者正在处理,那么这样就会导致Activity
无法被回收,以致发生Activity
的内存泄露
解决方案:1.通常在Android开发中如果要使用内部类,但又要规避内存泄露,一般都会采用静态内部类+弱引用的方式
。
public class MainActivity extends AppCompatActivity {
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler = new MyHandler(this);
start();
}
private void start() {
Message msg = Message.obtain();
msg.what = 1;
mHandler.sendMessage(msg);
}
private static class MyHandler extends Handler {
private WeakReference activityWeakReference;
public MyHandler(MainActivity activity) {
activityWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = activityWeakReference.get();
if (activity != null) {
if (msg.what == 1) {
// 做相应逻辑
}
}
}
}
}
mHandler
通过弱引用的方式持有Activity
,当GC
执行垃圾回收时,遇到Activity
就会回收并释放所占据的内存单元。这样就不会发生内存泄露了。
上面的做法确实避免了Activity
导致的内存泄露,发送的msg不再已经没有持有Activity
的引用了,但是msg
还是有可能存在消息队列MessageQueue
中,所以更好的是在Activity
销毁时就将mHandler
的回调和发送的消息给移除掉。
解决方案:2.在Activity销毁时就将mHandler的回调和发送的消息给移除掉
。
Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
非静态内部类造成内存泄露还有一种情况就是使用Thread或者AsyncTask。
比如在Activity中直接new一个子线程Thread:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new Runnable() {
@Override
public void run() {
// 模拟相应耗时逻辑
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
这种方式新建的子线程Thread和AsyncTask都是匿名内部类对象,默认就隐式的持有外部Activity的引用,导致Activity内存泄露。要避免内存泄露的话还是需要像上面Handler一样使用静态内部类+弱应用的方式
关于WebView
的内存泄露,因为WebView
在加载网页后会长期占用内存而不能被释放,因此我们在Activity
销毁后要调用它的destory()
方法来销毁它以释放内存。
另外在查阅WebView内存泄露相关资料时看到这种情况:
Webview下面的Callback持有Activity引用,造成Webview内存无法释放,即使是调用了Webview.destory()等方法都无法解决问题(Android5.1之后)。
解决方案:在销毁WebView之前需要先将WebView从父容器中移除,然后在销毁WebView
@Override
protected void onDestroy() {
super.onDestroy();
// 先从父控件中移除WebView
mWebViewContainer.removeView(mWebView);
mWebView.stopLoading();
mWebView.getSettings().setJavaScriptEnabled(false);
mWebView.clearHistory();
mWebView.removeAllViews();
mWebView.destroy();
}
详细分析过程请参考这篇文章:WebView内存泄漏解决方法。