一般而言,android中常见的OOM原因(一般都是内存泄漏引起)主要有以下几个:
- 数据库的cursor没有关闭。
- 构造adapter没有使用缓存contentview。
- 调用registerReceiver()后未调用unregisterReceiver().
- 未关闭InputStream/OutputStream。
- Bitmap使用后未调用recycle()。
- Context泄漏。
- static关键字等。
static关键字
- 第一,应该尽量避免static成员变量引用资源耗费过多的实例,比如Context。
- 第二、Context尽量使用Application Context,因为Application的Context的生命周期比较长,引用它不会出现内存泄露的问题。
- 第三、使用WeakReference代替强引用。比如可以使用WeakReference<Context> mContextRef
Context泄漏
上述中包含好几个Context的泄漏。初次之外有一种就是内部类持有外部对象造成的内存泄露,常见是内部线程造成的。
由于我们的线程是Activity的内部类,所以OneThread中保存了Activity的一个引用,当OneThread的run函数没有结束 时,OneThread是不会被销毁的,因此它所引用的老的Activity也不会被销毁,因此就出现了内存泄露的问题。
有些人喜欢用Android提供的AsyncTask,但事实上AsyncTask的问题更加严重,Thread只有在run函数不结束时才出现这种内 存泄露问题,然而AsyncTask内部的实现机制是运用了ThreadPoolExcutor,该类产生的Thread对象的生命周期是不确定的,是应 用程序无法控制的,因此如果AsyncTask作为Activity的内部类,就更容易出现内存泄露的问题,故一般不建议将AsyncTask作为内部类 使用。
那么上述内存泄露问题应该如何解决呢?
- 第一、将线程的内部类,改为静态内部类。并且注意第二条。
- 第二、在线程内部采用弱引用保存Context引用。
bitmap内存泄露
第一、及时的销毁。
系统能够确认Bitmap分配的内存最终会被销毁,但是由于它占用的内存过多,所以很可能会超过java堆的限制。因此,在用完Bitmap时,要 及时的recycle掉。
if(!bitmap.isRecycled()){
bitmap.recycle()
}
此外,最好手动设置为NULL这样 还能大大的加速Bitmap的主要内存的释放。
第二、设置一定的采样率。(压缩)
有时候,我们要显示的区域很小,没有必要将整个图片都加载出来,而只需要记载一个缩小过的图片,这时候可以设置一定的采样率,那么就可以大大减小占用的内存。如下面的代码:
private ImageView preview;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;//图片宽高都为原来的二分之一,即图片为原来的四分之一
Bitmap bitmap = BitmapFactory.decodeStream(cr.openInputStream(uri), null, options);
preview.setImageBitmap(bitmap);
第三、巧妙的运用软引用(SoftRefrence)
有些时候,我们使用Bitmap后没有保留对它的引用,因此就无法调用Recycle函数(特别是在Adapter中)。这时候巧妙的运用软引用,可以使Bitmap在内存快不足时得到有效的释放。如下例:
private class MyAdapter extends BaseAdapter {
private ArrayList> mBitmapRefs = new ArrayList>();
public View getView(int i, View view, ViewGroup viewGroup) {
View newView = null;
if(view != null) {
newView = view;
} else {
newView =(View)mInflater.inflate(R.layout.image_view, false);
}
Bitmap bitmap = BitmapFactory.decodeFile(mValues.get(i).fileName);
mBitmapRefs.add(new SoftReference(bitmap)); //此处加入ArrayList
((ImageView)newView).setImageBitmap(bitmap);
return newView;
}
}
第四 未关闭InputStream/OutputStream
这个就不多说了,我们操作完输入输出流都要关闭流
第五 调用registerReceiver()后未调用unregisterReceiver()
当我们Activity中使用了registerReceiver()方法注册了BroadcastReceiver,一定要在Activity的生命周期内调用unregisterReceiver()方法取消注册
也就是说registerReceiver()和unregisterReceiver()方法一定要成对出现,通常我们可以重写Activity的onDestory().
第六 构造adapter没有使用缓存contentview
这个现在adapter 我们要求一定要这么写
第七 数据库的cursor没有关闭
Cursor是Android查询数据后得到的一个管理数据集合的类,正常情况下,如果查询得到的数据量较小时不会有内存问题,而且虚拟机能够保证Cusor最终会被释放掉。
所以我们使用Cursor的方式一般如下:
Cursor cursor = null;
try {
cursor = mContext.getContentResolver().query(uri,null, null,null,null);
if(cursor != null) {
cursor.moveToFirst();
//do something
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null) {
cursor.close();
}
}
有一种情况下,我们不能直接将Cursor关闭掉,这就是在CursorAdapter中应用的情况,但是注意,CursorAdapter在Acivity结束时并没有自动的将Cursor关闭掉,因此,你需要在onDestroy函数中,手动关闭。
@Override
protected void onDestroy() {
if (mAdapter != null && mAdapter.getCurosr() != null) {
mAdapter.getCursor().close();
}
super.onDestroy();
}