常见的内存泄露及其解决方案

常见的内存泄露及其解决方案


Context泄露

Context使用不当导致的内存泄露. 一般来说是因为某些全部的对象, 理当使用Application级别的Context, 而使用了指定Activity的Context, 导致该Activity无法释放.

例如, 某个单例中需要一个Context, 传入了一个Activity的Context, 导致其被这个单例持续引用而无法回收.

这类泄露的解决方案, 就是根据组件的生命周期来正确使用Context, 全局引用使用Application Context.

关于各种Context的说明和使用请参看这篇译文. Context泄露的实例还可以看下android dev blog中的这篇, 需翻墙.

内部类泄露

由于(匿名)内部类隐式地持有一个外部类的引用, 故而当内部类中执行的事情长于外部类的生命周期时, 就会导致外部类的泄露.

常见的此类泄露包括Handler泄露, Thread泄露..., 这些也是我们经常会作为(匿名)内部类在Activity中使用的.

下面以HandlerLeak为例:

如下:

public class HandlerLeakActivity extends AppCompatActivity {

    private BigObject mBigObject = new BigObject();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_memory_leak);

        mHandler.sendEmptyMessageDelayed(1, 60 * 1000);

    }

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };
}

解决方案 --- 使用Static + WeakReference的方式, 具体如下:

public  class HandlerLeakActivity extends AppCompatActivity {

    private BigObject mBigObject = new BigObject();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_memory_leak);

        new DemoHandler(this).sendEmptyMessageDelayed(1, 60 * 1000);

    }

    private static class DemoHandler extends Handler {

        private final WeakReference<HandlerLeakActivity> mActivity;


        private DemoHandler(HandlerLeakActivity activity) {
            this.mActivity = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            HandlerLeakActivity activity = mActivity.get();
            if (activity != null) {
                activity.doSomething();
            }
        }
    }

    private void doSomething() {

    }
}

###使用静态Thread

private static class MyThread extends Thread {
    private boolean mRunning = false;
 
    @Override
    public void run() {
      mRunning= true;
      while (mRunning) {
       SystemClock.sleep(1000);
      }
    }
 
    public void close() {
      mRunning= false;
    }
  }
 
  @Override
  protectedvoid onDestroy() {
   super.onDestroy();
   mThread.close();
  }
}

Register泄露

对于观察者, 广播, Listener等, 注册和注销没有成对出现而导致的内存泄露.

资源泄露

常见的数据库查询Cursor, 文件读写流等, 用完没有关闭导致的内存泄露.

例如:

public class CursorLeakActivity extends AppCompatActivity {

    private BigObject mBigObject = new BigObject();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_memory_leak);

        Cursor cursor = getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);

        if (cursor != null) {
            cursor.moveToFirst();

            // do something.
        }
    }
}

此类问题的解决方案, 一般我们使用try-catch-finally的结构, 在finally中关闭并释放资源. 如下:

public class CursorLeakActivity extends AppCompatActivity {

    private BigObject mBigObject = new BigObject();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_memory_leak);

        Cursor cursor = getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);

        try {
            if (cursor != null) {
                cursor.moveToFirst();

                // do something.
            }
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        finally {
            if (cursor != null) {
                cursor.close();
                cursor = null;
            }
        }
    }
}

Bitmap图片

1. 对图片本身进行操作

尽量不要使用 setImageBitmap、setImageResource、 BitmapFactory.decodeResource 来设置一张大图,因为这些方法在完成 decode 后,最终都是通过 Java 层的 createBitmap 来完成的,需要消耗更多内存。因此,改用先通过 BitmapFactory.decodeStream 方法,创建出一个 bitmap,再将其设为 ImageView 的 source,decodeStream 最大的秘密在于其直接调用 JNI>>nativeDecodeAsset() 来完成 decode,无需再使用 Java 层的 createBitmap,从而节省了 Java 层的空间。如果在读取时加上图片的 Config 参数,可以更有效的减少加载的内存,从而更有效阻止抛出内存异常。另外,decodeStream 直接拿图片来读取字节码了,不会根据机器的各种分辨率来自动适应,使用了 decodeStream 之后,需要在 hdpi 和 mdpi,ldpi 中配置相应的图片资源, 否则在不同分辨率机器上都是同样大小(像素点数量),显示出来的大小就不对了。 复制代码 代码如下:

InputStream is = this.getResources().openRawResource(R.drawable.pic);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
options.inSampleSize = 2;
Bitmap btp =BitmapFactory.decodeStream(is,null,options);

以上代码即是读取 drawable 下名为 pic 图片的缩略图,长度、宽度都只有原图片的 1/2。图片大小减少,占用的内存自然也变小了。这么做的弊端是图片质量变差,inSampleSize 的值越大,图片的质量就越差。由于各手机厂商缩放图片的算法不同,在不同手机上的缩放图片质量可能会不同。

2. 调用图片的 recycle() 方法

复制代码 代码如下:

if(!bmp.isRecycle() ){
   bmp.recycle()    //回收图片所占的内存
   system.gc()      //提醒系统及时回收
}

这种方法其实不是真正降低图片内存的方法。主要目的是标记图片对象,方便回收图片对象的本地数据。图片对象的本地数据占用的内存最大,而且与程序 Java 部分的内存是分开计算的。所以经常出现 Java heap 足够使用,而图片发生 OutOfMemoryError 的情况。在图片不使用时调用该方法,可以有效降低图片本地数据的峰值,从而减少 OutOfMemoryError 的概率。不过调用了 recycle() 的图片对象处于“废弃”状态,调用时会造成程序错误。所以在无法保证该图片对象绝对不会被再次调用的情况下,不建议使用该方法。特别要注意已经用 setImageBitmap(Bitmap img) 方法分配给控件的图片对象,可能会被系统类库调用,造成程序错误。

3. 以最省内存的方式读取本地资源的图片

复制代码 代码如下:

/**
 * 以最省内存的方式读取本地资源的图片
 */
public static Bitmap readBitMap(Context context, int resId){  
   BitmapFactory.Options opt = new BitmapFactory.Options();  
   opt.inPreferredConfig = Bitmap.Config.RGB_565;  
   opt.inPurgeable = true;  
   opt.inInputShareable = true;  
   // 获取资源图片  
   InputStream is = context.getResources().openRawResource(resId);  
   return BitmapFactory.decodeStream(is,null,opt);  
}

Android 中加载图片的颜色模式有四种,分别是:ALPHA_8:每个像素占用 1byte 内存、ARGB_4444:每个像素占用 2byte 内存、ARGB_8888:每个像素占用 4byte 内存、RGB_565:每个像素占用 2byte 内存。Android默认的颜色模式为ARGB_8888,这个颜色模式色彩最细腻,显示质量最高。但同样的,占用的内存也最大。以上代码即是将图片资源以 RGB_565 (或以 ARGB_4444)模式读出。内存减少虽然不如第一种方法明显,但是对于大多数图片,看不出与 ARGB_8888 模式有什么差别。不过在读取有渐变效果的图片时,可能有颜色条出现。另外,会影响图片的特效处理。

4. 使用 Matrix 对象放大的图片如何更改颜色模式:

虽然使用 Matrix 对象放大图片,必定会耗费更多的内存,但有时候也不得不这样做。放大后的图片使用的 ARGB_8888 颜色模式,就算原图片是ARGB_4444 颜色模式也一样,而且没有办法在放大时直接指定颜色模式。可以采用以下办法更改图片颜色模式。 复制代码 代码如下:

Matrix matrix = new Matrix();
float newWidth = 200;       // 图片放大后的宽度
float newHeight = 300;      // 图片放大后的长度
matrix.postScale(newWidth / img.getWidth(), newHeight/ img.getHeight());
Bitmap img1 = Bitmap.createBitmap(img, 0, 0, img.getWidth(), img.getHeight(), matrix, true);// 得到放大图片
img2 = img1.copy(Bitmap.Config.ARGB_4444, false);                           // 得到 ARGB_4444 颜色模式的图片
img = null;
img1 = null;

这里比起本来的图片额外生成了一个图片对象 img1。然则体系会主动收受接管 img1,所以实际内存还是削减了。 归结起来还是以缩略图模式读取图片和削减图片中每个像素占用的内存最为有效。 这两种办法固然有效,然则也有各自的弊病。实际开辟中还是应当按照景象酌情应用。最王道的办法,还是避免垃圾对象的产生。例如在 ListView 的应用中,复用 convertView 等。若是应用 AsyncTask 加载图片,要及时将引用的 ImageView 对象置为 null。因为 AsyncTask 是用线程池实现的,所以此中引用的对象可能会拥有很长的生命周期,造成 GC 无法开释。我还是信赖 Android 的内存收受接管机制的,recycle 什么的固然必然程度上有效,但总感觉不合适 Java 内存收受接管的原则。

VideoView内存优化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值