正文  高级教程 > 程序优化 >

Android的内存溢出是如何发生的

且谈Android内存溢出前言 关于android的内存溢出在创新文档库中也有不少,网络上也有很多这方面的资料.所以这遍文章不算是正真意义上的创新,仅仅只是对xxx源码中出现的一些android开发中容易犯错的代码习惯一次总......

且谈Android内存溢出

前言

    关于android的内存溢出在创新文档库中也有不少,网络上也有很多这方面的资料.所以这遍文章不算是正真意义上的创新,仅仅只是对xxx源码中出现的一些android开发中容易犯错的代码习惯一次总结.

1.  Android的内存溢出是如何发生的

 Android的虚拟机是基于寄存器的Dalvik,它的最大堆大小一般是16M,有的机器为24M.因此我们所能利用的内存空间是有限的.如果我们的内存占用超过了一定的水平就会出现OutOfMemory的错误.原因主要有两个:
l  由于我们程序的失误,长期保持某些资源(如Context)的引用,造成内存泄露,资源造成得不到释放.
l  保存了多个耗用内存过大的对象(如Bitmap),造成内存超出限制.

2.  Static

static是Java中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例.所以用static修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费过多的实例(Context的情况最多)就要谨慎对待了.例如xxx源码:

public class IntentMapping {
    private static Contextcontext;
    //. . .
}

如果将Activity赋值到么context的话.那么即使该Activity已经onDestroy,但是由于仍有对象保存它的引用,因此该Activity依然不会被释放.所以这里就潜在内存溢出.

再看android文档中的一个例子:

    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);

}

sBackground,是一个静态的变量,我们并没有显式的保存Contex的引用,但是,当Drawable与View连接之后,Drawable就将View设置为一个回调,由于View中是包含Context的引用的,所以,实际上我们依然保存了Context的引用.这个引用链如下:

Drawable->TextView->Context

所以,最终该Context也没有得到释放,发生了内存泄露,该部分的详细内容也可以参考Android文档中Article部分.

有效的避免内存溢出的方法:

1.应该尽量避免static成员变量引用资源耗费过多的实例,比如Context.

2.Context尽量使用Application Context,因为Application的Context的生命周期比较长,引用它不会出现内存泄露的问题.

3.使用WeakReference代替强引用.比如可以使用WeakReference<Context> mContextRef;

在这里要提到,在xxx中其实是有在ApplicationInit中将baseContext缓存下来,但可惜的是并没有很好的利用,而是在各自的模块中又缓存了自身的Context例如上面例子中的IntentMapping.

3.  线程

程产生内存泄露的主要原因在于线程生命周期的不可控.看看下面这段代码.

public class Pandareader extends BaseActivity {

@Override

    public void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    `   new Thread() {
           public void run() {

           InitDataBase.initDB(Pandareader.this.getBaseContext());

           //. . .

        }

    }

}

这段代码很平常也很简单,也是我们经常使用的形式.但存在一个问题:假设Thread的run函数是一个很费时的操作,当我们开启该线程后,将设备的横屏变为了竖屏,这时会重新创建Activity,按照我们的想法,老的Activity应该会被销毁才对,然而事实上并非如此.

由于我们的线程是Activity的内部类,所以Thread中保存了Activity的一个引用,当Thread的run函数没有结束时,Thread是不会被销毁的,因此它所引用的老的Activity也不会被销毁,因此潜在内存泄露.

Android提供的AsyncTask同样也会潜在内存溢出的风险.因为Thread只有在run函数不结束时才出现这种内存泄露问题,然而AsyncTask内部的实现机制是运用了ThreadPoolExcutor,该类产生的Thread对象的生命周期是不确定的,是应用程序无法控制的,因此如果AsyncTask作为Activity的内部类,就更容易出现内存泄露.

要避免线程的内存溢出要到2点:

1.  将线程的内部类,改为静态内部类.

2.  在线程内部采用弱引用保存Context引用.

4.  Bitmap

可以说出现OutOfMemory问题的绝大多数人,都是因为Bitmap的问题.因为Bitmap占用的内存实在是太多了,特别是分辨率大的图片,如果要显示或创建多张那OutOfMemory问题就更显著了.而恰恰xxx的文本阅读就是依赖于PageBitmap. PageBitmap是创建屏幕分辨率大小的Bitmap,虽然有封装了一个池BitmapPool来管理PageBitmap的创建和销毁,以及引用.但是还是没发完全避免由于PageBitmap所带来的内存溢出.并且TextViewerActivity, TextDraw, TXTParagraph, ParagraphLineData, ParagraghData, TXTReader之间错综复杂的调用关系以及引用,决定了在TextViewerActivity中创建的对象并没有完全的释放,容易发生内存溢出.

有效的解决内存溢出:

1.及时的销毁.

虽然,系统能够确认Bitmap分配的内存最终会被销毁,但是由于它占用的内存过多,所以很可能会超过java堆的限制.因此,在用完Bitmap时,要及时的recycle掉.recycle并不能确定立即就会将Bitmap释放掉,但是会给虚拟机一个暗示:”该图片可以释放了”.

2.设置一定的采样率.

有时候,我们要显示的区域很小,没有必要将整个图片都加载出来,而只需要记载一个缩小过的图片,这时候可以设置一定的采样率,那么就可以大大减小占用的内存.

但像上面xxx中出现的问题,并不能简单的应用采样率来解决.那就要更深一步分析Bitmap在TextDraw中使用的情况,包括BitmapPool中缓存PageBitmap数量以及对象的引用情况.

5.  Cursor

Cursor是Android查询数据后得到的一个管理数据集合的类,正常情况下,如果查询得到的数据量较小时不会有内存问题,而且虚拟机能够保证Cusor最终会被释放掉.然而如果Cursor的数据量特表大,特别是如果里面有Blob信息时,应该保证Cursor占用的内存被及时的释放掉,而不是等待GC来处理.并且Android明显是倾向于手动的将Cursor close掉. 有一种情况下,我们不能直接将Cursor关闭掉,这就是在CursorAdapter中应用的情况,但是注意,CursorAdapter在Acivity结束时并没有自动的将Cursor关闭掉,因此,你需要在onDestroy函数中,手动关闭.