概念
内存泄漏(Leak):当一个无用的对象仍然被其它对象所持有引用,从而造成该对象无法被系统回收,以致该对象在堆中所占用的内存单元无法被释放而造成内存空间浪费。最坏的情况下,内存泄漏会导致内存溢出。
内存溢出(OutIfMemoryError,OOM):为了允许多进程,Android为每个应用程序分配的堆大小设置了硬性限制。确切的堆大小限制根据设备有多少内存总量而有所不同。如果你的应用程序使用的内存已达到该限制并尝试分配更多内存时,系统就会抛出OutOfMemoryError。
原因
单例模式下使用Activity作为Context
错误代码
public class TestManager {
private static TestManager manager;
private Context context;
private TestManager(Context context) {
this.context = context;
}
public static TestManager getInstance(Context context) {
if (manager == null) {
manager = new TestManager(context);
}
return manager;
}
}
单例模式的静态特性导致它的对象的生命周期是和应用一样的,这种写法单例模式持有这个Activity的引用,导致Activity无法被释放,造成内存泄漏。
正确代码
public static TestManager getInstanceSafe(Context context) {
if (manager == null) {
manager = new TestManager(context.getApplicationContext());
}
return manager;
}
通过context.getApplicationContext()使用ApplicationContext作为Context传入,才能避免内存泄漏。如果一定要使用Activity的话,要使用弱引用。
需要手动关闭的资源没有关闭
如IO流需要调用close()方法进行关闭,广播需要取消注册等,而且需要在finally块中执行,保证无论如何都会关闭。
Handler使用不当
错误代码
public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
//do something
}
};
private void loadData(){
//do request
Message message = Message.obtain();
mHandler.sendMessage(message);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
loadData();
}
}
这种情况mHandler是Handler的非静态匿名内部类的实例,持有外部类Activity的引用,如果这个Activity退出时消息队列中还有未处理的消息或者正在处理的消息,那么这时候Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏。
正确代码
public class MainActivity extends AppCompatActivity {
private MyHandler mHandler = new MyHandler(this);
private void loadData() {
//do request
Message message = Message.obtain();
mHandler.sendMessage(message);
}
private static class MyHandler extends Handler {
private WeakReference<Context> reference;
public MyHandler(Context context) {
reference = new WeakReference<Context>(context);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
MainActivity mainActivity = (MainActivity) reference.get();
if (mainActivity != null) {
//do something to update UI via mainActivity
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
loadData();
}
}
创建一个静态Handler内部类,然后对Handler持有的对象使用弱引用。(弱引用,即在引用对象的同时仍然允许通过垃圾回收来回收该对象。)同时,对于还未处理完的消息,应在onDestroy()中通过removeCallbacksAndMessages(null)移除。
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
** AsyncTask使用不当**
AsyncTask造成内存泄漏的情况和Handler一样,这里不多说,直接上代码。
错误代码
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new MyAscnyTask().execute();
}
class MyAscnyTask extends AsyncTask<Void, Integer, String>{
@Override
protected String doInBackground(Void... params) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "";
}
}
}
MyAsyncTask 是一个非静态内部类,在 doInbackground() 方法中执行耗时操作。如果在耗时操作结束之前,Activity 被销毁了,这时候因为 MyAsyncTask 持有 Activity 的强引用,便会导致 Activity 的内存无法被回收,产生内存泄露。
解决:使用静态内部类+弱引用,类似Handler 。
线程引发的内存泄漏
线程生命周期的不可控性导致很容易引发内存泄漏。一段经典的代码:
错误代码
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(50000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new 出一个匿名的 Thread,进行耗时的操作,线程隐式持有Activity的引用,Thread 中的耗时操作没有结束时,Activity也不会被销毁,导致内存泄漏。
解决:使用静态内部类+弱引用。
集合造成内存泄漏
错误代码
static List<Object> objectList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Object obj = new Object();
objectList.add(obj);
obj = null;
}
我们将一个对象添加到 List 等集合中,这个集合就会持有该对象的引用。这段代码中的objectList是静态变量,生命周期和app一致,它持有集合中对象的引用,导致这些对象无法被释放,造成内存泄漏。
解决:
objectList.clear();
objectList = null;
objectList.clear();将集合中的所有对象都释放了,但是list对象还是存在。
objectList = null;将集合置空,为集合分配的空间也会回收。
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();
}
网友评论