实际开发中发现Android内存泄露问题很容易发生,下面大致是一些常犯的问题:
1、资源性对象比如(Cursor,File文件、stream流等)是否关闭。
try {
Cursor c = queryCursor();
int a = c.getInt(1);
......
c.close();
} catch (Exception e) {
}
虽然表面看起来,Cursor.close()已经被调用,但若出现异常,将会跳过close(),从而导致内存泄露。 所以,我们的代码应该以如下的方式编写:
Cursor cursor = null;
try {
cursor = getContentResolver().query(uri...);
if (cursor != null &&cursor.moveToNext()) {
... ...
}
} finally {
if (cursor != null) {
try {
cursor.close();
} catch (Exception e) {
//ignore this
}
}
}
2、构造Adapter时,没有使用缓存的contentView。
public class ViewHolder {
private ImageView playrecord_img;
private TextView playrecord_name;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
ViewHolder holder = null;
if (convertView == null) {
holder = new ViewHolder();
convertView = inflater.inflate(
R.layout.gridview_playrecord_item_layout, null);
holder.playrecord_img = (ImageView) convertView
.findViewById(R.id.play_record_img);
holder.playrecord_name = (TextView) convertView
.findViewById(R.id.play_record_name);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.playrecord_img.setBackgroundResource(R.drawable.bgselector);
holder.playrecord_name.setText(list.get(position).getVideoName());
return convertView;
}
3、重型对象,即占用内存比较多的对象,比如bitmap对象,Bitmap对象不再使用时,没有调用bitmap.recycle()释放资源。
第一、及时的销毁。bitmap.recycle()
第二、设置一定的采样率。
有时候,我们要显示的区域很小,没有必要将整个图片都加载出来,而只需要记载一个缩小过的图片,这时候可以设置一定的采样率,那么就可以大大减小占用的内存。如下面的代码:
private ImageView preview;
BitmapFactory.Options options = newBitmapFactory.Options();
options.inSampleSize = 2;//图片宽高都为原来的二分之一,即图片为原来的四分之一
Bitmap bitmap =BitmapFactory.decodeStream(cr.openInputStream(uri), null, options); preview.setImageBitmap(bitmap);
第三、巧妙的运用软引用(SoftRefrence)
有些时候,我们使用Bitmap后没有保留对它的引用,因此就无法调用Recycle函数。这时候巧妙的运用软引用,可以使Bitmap在内存快不足时得到有效的释放。如下:
SoftReference<Bitmap> bitmap_ref = new SoftReference<Bitmap>(BitmapFactory.decodeStream(inputstream));
……
……
if (bitmap_ref .get() != null)
bitmap_ref.get().recycle();
4、没有释放间接或直接对Activity context的引用对象。
private static Drawable sBackground;
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this);
label.setText("Leaks are bad");
if (sBackground == null) {
sBackground = getDrawable(R.drawable.large_bitmap);
}
label.setBackgroundDrawable(sBackground);
setContentView(label);
}
在这段代码中,我们使用了一个static的Drawable对象。
这通常发生在我们需要经常调用一个Drawable,而其加载又比较耗时,不希望每次加载Activity都去创建这个Drawable的情况。
此时,使用static无疑是最快的代码编写方式,但是其也非常的糟糕。
当一个Drawable被附加到View时,这个View会被设置为这个Drawable的callback (通过调用Drawable.setCallback()实现)。
这就意味着,这个Drawable拥有一个TextView的引用,而TextView又拥有一个Activity的引用。
这就会导致Activity在销毁后,内存不会被释放。
(1).是否被被Application引用
Application在android应用中属于长生命周期对象,如果你的activity需要被回收,但是此时却被application引用,那么会导致activity无法被顺利回收了。
(2).是否被activity中内部类引用
普通内部类会持有对外部类对象的引用,如果你的内部类对象在从事一个耗时的操作,这个时候如果想回收外部类对象的话,需要先停止内部类对象正在做的操作。
静态内部类不会持有对外部类对象的引用,可以解决这个问题。
(3).是否有被静态引用
你是否也写过这种傻傻的事情,在一个类中写上了对一个activity的静态引用,
比如 private static XActivity activity = XActivity.getInstance();
你想想,如果你这么写了,你在退出这个XActivity的时候它可以有效的释放么?
如果你想保持一个长期生存的对象,并且这个对象需要一个context,记得使用application对象。你可以通过调用Context.getApplicationContext() or Activity.getApplication()来获得
5、在Activity的生命周期中在onPause()、onStop()、onDestory()方法中需要适当释放资源。
6、注册广播监听器,忘记反注册。
registerReceiver(mReceiver, intentFilter)
unRegisterReceiver(mReceiver, intentFilter)
7、其他类型监听器,比如监听电话状态:
在android.telephony.TelephonyManager.java中,提供了一个监听电话状态的接口:
public void listen(PhoneStateListener listener, int events) {
Xxxxxx;//
}
记得在activity destroy的时候将其反注册啊。如何反注册呢?是unListen么?不是的,看源码注释:
所以你只需要在相应位置再次调用listen接口,将listener这个参数值传递成PhoneStateListener#LISTEN_NONE LISTEN_NONE就可以了。
8、集合中对象没清理造成的内存泄漏
我们通常把一些对象的引用加入到了集合中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。
比如某公司的ROM的锁屏曾经就存在内存泄漏问题:
这个泄漏是因为LockScreen每次显示时会注册几个callback,它们保存在KeyguardUpdateMonitor的ArrayList<InfoCallback>、ArrayList<SimStateCallback>等ArrayList实例中。但是在LockScreen解锁后,这些callback没有被remove掉,导致ArrayList不断增大, callback对象不断增多。这些callback对象的size并不大,heap增长比较缓慢,需要长时间地使用手机才能出现OOM,由于锁屏是驻留在system_server进程里,所以导致结果是手机重启。
9、使用handler时的内存问题
我们知道,Handler通过发送Message与主线程交互,Message发出之后是存储在MessageQueue中的,有些Message也不是马上就被处理的。在Message中存在一个 target,是Handler的一个引用,如果Message在Queue中存在的时间越长,就会导致Handler无法被回收。如果Handler是非静态的,则会导致Activity或者Service不会被回收。 所以正确处理Handler等之类的内部类,应该将自己的Handler定义为静态内部类。
HandlerThread的使用也需要注意:
当我们在activity里面创建了一个HandlerThread,代码如下:
public classMainActivity extends Activity
{
@Override
public void onCreate(BundlesavedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Thread mThread = newHandlerThread("demo", Process.THREAD_PRIORITY_BACKGROUND);
mThread.start();
MyHandler mHandler = new MyHandler( mThread.getLooper( ) );
…….
…….
…….
}
@Override
public void onDestroy()
{
super.onDestroy();
}
}
这个代码存在泄漏问题,因为HandlerThread的run方法是一个死循环,它不会自己结束,线程的生命周期超过了activity生命周期,当横竖屏切换,HandlerThread线程的数量会随着activity重建次数的增加而增加。
应该在onDestroy时将线程停止掉:mThread.getLooper().quit();
另外,对于不是HandlerThread的线程,也应该确保activity消耗后,线程已经终止,可以这样做:在onDestroy时调用mThread.join();
如何分析、调试、解决android系统中的内存泄露问题呢?
参见文章《Android内存泄露调试》