现在的位置: 首页 > 综合 > 正文

Android内存泄露与分析

2018年05月01日 ⁄ 综合 ⁄ 共 4826字 ⁄ 字号 评论关闭

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


抱歉!评论已关闭.