实际开发中发现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内存泄露调试》