首页 > 代码库 > Android内存那点事儿

Android内存那点事儿

好久没有写了,不是忘了,也不是懒,是因为迷茫了~~不知道该学什么,该写什么,该走什么样子的路,该做什么样子的人。我嘴笨,不知道怎么把自己会的讲给别人,我愿意分享,所以我就写出来,不管是对的,错的,希望大家能取其精华去其糟粕,不要因为我而误导诸位。废话不多说了~~

你的应用内存泄漏了么?

要看是不是存在内存泄漏,首先我们要看到内存信息,如何看到内存信息呢?这里介绍一种方法,打开Eclipse连接手机,到DDMS中,选择要分析的应用,点击Update Heap也就是下图中1的图标,点击1图标之后会在要分析的应用进程中出现2的图标,在4的位置就能看到该应用的内存信息。

技术分享

内存信息看到,那怎么知道存在内存泄漏呢?由于这里可以看到分析应用已申请的内存大小,也就是3位置的数值,所以可以根据这个值的变动来判断是不是存在内存泄漏。那怎么判断呢?例如我们要判断进入分析应用的第一个界面是不是存在内存泄漏,我们可以这样来操作,先退出应用,然后点击Cause GC,等3位置数值稳定了,记录下来,然后进入应用,等第一个界面加载完成,再退出应用,再点击Cause GC,等3位置的数值稳定了,再记录下来,重复操作多次之后,来看我们记录的数值,如果成增长趋势,说明分析应用的第一个界面存在内存泄漏问题,如果水平趋势,说明不存在内存泄漏问题。

哪里的内存泄漏了?

要知道哪里内存泄漏了,这就需要安装一个叫MAT的工具了,如何安装就去百度吧,这里不再扯淡。同样,进入打开Eclipse连接手机,进入到DDMS中,选中待分析的应用,然后点击下图中A的位置,也就是Dump HPROF file。

技术分享

等待片刻,就会弹出如下窗口

技术分享

选择Leak Suspects Report,然后点击Finish,就会到如下窗口

技术分享

点击A所指图标,就会出现B所指图标,然后在B中选择Groupby class loader,就可以看到待分析应用的已申请对象信息,Objects也就是C所在位置,就是申请对象个数,Shallow Heap是对象本身所占内存大小,Retained Heap是对象本身和直接或间接引用到的对象的Shallow Heap之和,也就是还对象被GC之后所能回收到内存总和。通过这些数值怎么判断那里内存泄漏了呢?还是接着上面的例子说明,我们进入第一个界面也就是第一个Activity,我们知道当退出Activity的时候就会释放掉该Activity所占用的资源,所以对象个数和你启动Activity次数是没有关系的,所以我们先记录Objects个数,然后多次进出分析应用,再生成hprof文件,再看某个Objects数量只增加不减少,说明该对象出现内存泄漏。当然A对象存在内存泄漏可能是应为B对象对A的引用造成,所以我们就要去找到到底是那个对象没释放导致内存泄漏。剩下的事情就靠自个了,一个一个检查个数一直增长的对象吧。

Context对内存的威胁到底有多大?

这里说的Context不是Application Context,而主要说的是Activity。以前在网上搜内存溢出如何处理的时候,总是说让把Context释放掉,这样做了,但还是糊里糊涂的。今天我们就说说这个Context。我们接着上面的问题开始,当我们监测到很多对象的个数都在增长,并且这些对象的个数相等,就像上图C区出现很多对象个数是2一样,这时我们就要注意了,是不是有一个Context没有释放掉。在Activity中创建的不管是布局,Handler,还是Thread等等,都会保存一个该Activity对象,而如果这些当中有一个不释放该Activity,就会导致该Activity中所有对象都无法释放,如果该Activity中有个大点Bitmap没有主动释放,那隐患就无法形容了。鄙人之前就碰到过一次,是Handler没有释放掉,导致Context没有释放,而Context中较大的Bitmap没有释放,导致每次进入该Activity内存以将近3M的速度增长,来回进入数次就OOM了。

Bitmap到底占多大内存?

技术分享

首先,先确定一下Bitmap图片大小是怎么计算的。我们来计算一个像素500x500的位深度为24的Bitmap大小。我们都知道Bitmap的特点就是一个像素占一个位深度,例如位深度为24,也就是一个像素占24b(位),也就是3B。

大小(B)= 宽度(像素)*高度(像素)*位深度/8。

大小(KB)= 大小(B)/1024。

所以上面我们提到的图片大小就是:500*500*24/8/1024=732KB。

而这个bitmap在Android手机中的大小是多少呢?我们写段代码来测试一下,当然其它的对象大小也可以这么来测试大小。

<span style="font-size:18px;">    private void testBitmapSize (Context context) {
        BitmapDrawable drawable = (BitmapDrawable) context.getResources().getDrawable(R.drawable.test_bitmap);
        Bitmap bitmap = drawable.getBitmap();
        android.util.Log.d("my_test","start----------");
        System.gc();
        bitmap.recycle();
        System.gc();
        android.util.Log.d("my_test","end----------");
    }</span>

我们抓取log如下:

D/my_test (14667): start----------

D/dalvikvm(14667): GC_EXPLICIT freed 9K,4% free 10122K/10503K, paused 1ms+4ms

D/dalvikvm(14667): GC_EXPLICIT freed976K, 13% free 9145K/10503K, paused 2ms+2ms

D/my_test (14667): end----------

根据这个我们可以估算出来这个Bitmap释放之后,内存释放了大概976KB大小,将近1M的内存。如果这个不去释放,那么很快就会溢出了。

什么情况会导致Context不去释放?

鄙人遇到的Context没有释放,是这样子的,应用中有一个翻滚的标题,每隔一段时间就去翻滚一次,所以我就这么去写的:

技术分享

从代码中很容易看出来,每个5s,Handler就给自个发个消息,而这个Handler在activity销毁时没有释放,所以里面的View所拥有的Context没有释放,而Context中有2M多的Bitmap没有主动释放,导致每次进入应用都会多一个Context对象,多出将近3M的空间。

Bitmap是否需要释放?

就像上面说的,Context没有释放导致里面的Bitmap没有释放,那如果Context释放了里面的Bitmap会释放吗答案是会的。鄙人遇到上面的内存泄漏时,在还没有查到真正原因是,我发下里面有2M的Bitmap没有主动释放掉,所以就在用完的时候主动去释放了:

<p align="left"><span style="font-size:18px;"><strong><span style="color:#7F0055;">if</span></strong> (bitmap != <strong><span style="color:#7F0055;">null</span></strong> && !bitmap.isRecycled()) {</span></p><p align="left"><span style="font-size:18px;">    bitmap.recycle();</span></p><p align="left"><span style="font-size:18px;">    bitmap = <strong><span style="color:#7F0055;">null</span></strong>;</span></p><p><span style="font-size:18px;">}</span></p>

释放后每次进入应用,确实少了2M多,但是还有将近1M的空间没有释放,我很纳闷,就用mat工具去看,发现是第一个Activity生成的Context没有释放,接着就查到了,那个Handler消息,所以就在Activity销毁之前主动释放掉Handler消息:

<span style="font-size:18px;">    public void releaseRes () {
        mContext = null;
        if (mHandler != null && mHandler.hasMessages(HANDLER_SEARCH_HOT_WORD))
            mHandler.removeMessages(HANDLER_SEARCH_HOT_WORD);
        mHandler = null;
    }</span>

结果我再去观察应用所占内存,没有增加,基本保持在一个固定值附近。这是我又突发奇想,如果我的Bitmap不去释放,会不会泄漏呢,所以我就把释放Bitmap的地方注释掉了。我又去验证,进出应用多次,我发现应用基本上还是保持在那个固定值附近。所以我就认为,Bitmap会随着Context的释放而释放掉的。问题来了,那为什么还要主动去释放Bitmap呢?那是因为在你还没退出应用之前,这个Bitmap所占的空间是存在的,既然不用了,占着空间就是浪费,另外,如果某个地方的Bitmap没有释放,而这个地方会被重复调用,那后果也是不堪设想的。所以Bitmap不再使用的时候就要去释放,不是一定,只要你能确保内存够用。

Bitmap在哪些地方可能被忽略释放?

这个最长出现的就是Bitmap被修改的地方:

<span style="font-size:18px;">        BitmapDrawable drawable = (BitmapDrawable) this.getResources().getDrawable(R.drawable.test_bitmap);
        Bitmap bitmap = drawable.getBitmap();
        
        android.util.Log.d("logTest","bitmap.getWidth()="+bitmap.getWidth());
        android.util.Log.d("logTest","bitmap.getHeight()="+bitmap.getHeight());
        
        Bitmap newBitmap = Bitmap.createBitmap(bitmap, 0, 0, 500, 500);
        Bitmap newBitmap2 = Bitmap.createBitmap(bitmap, 0, 0, 400, 400);
        
        android.util.Log.d("logTest","bitmap="+bitmap);
        android.util.Log.d("logTest","bitmap.isRecycled()="+bitmap.isRecycled());
        android.util.Log.d("logTest","newBitmap="+newBitmap);
        android.util.Log.d("logTest","newBitmap.isRecycled()="+newBitmap.isRecycled());
        android.util.Log.d("logTest","newBitmap2="+newBitmap2);
        android.util.Log.d("logTest","newBitmap2.isRecycled()="+newBitmap2.isRecycled());</span>

Log打出来是这样子的:

D/logTest (27782):bitmap.getWidth()=500

D/logTest (27782):bitmap.getHeight()=500

D/logTest (27782):bitmap=android.graphics.Bitmap@4143c758

D/logTest (27782):bitmap.isRecycled()=false

D/logTest (27782):newBitmap=android.graphics.Bitmap@4143c758

D/logTest (27782):newBitmap.isRecycled()=false

D/logTest (27782):newBitmap2=android.graphics.Bitmap@4143a110

D/logTest (27782):newBitmap2.isRecycled()=false


很明显了,当我们做图片修改的时候,如果传进入的Bitmap和输出的Bitmap规格相同,那么返回的就是原图,同样的地址;如果图片做规格不同了,那么就会重新创建一个Bitmap,同时原来的Bitmap并没有释放掉。这时就容易出问题了,我们会忘记释放原来的Bitmap,如果多个图片被重新修改,那么内存泄漏就严重了,比如Listview中有图片,并且在设置图片之前我们会对图片进行处理,那么如果忘记释放修改前的图片,那么后果就不堪设想。

<span style="font-size:18px;">    if (newBitmap != null && !newBitmap.isRecycled() && newBitmap != bitmap) {
	newBitmap.recycle();
        newBitmap = null;
    }</span>
这么简单的几行代码就能拯救你的内存。

能释放布局中的图片来节省内存么?

网上有中方法释放掉布局中不需要的图片:

<span style="font-size:18px;">        //设置图片
        BitmapDrawable drawable = (BitmapDrawable) this.getResources().getDrawable(R.drawable.test_bitmap);
        mImageView.setBackgroundDrawable(drawable);
        
        
        //释放图片
        BitmapDrawable drawableRel = (BitmapDrawable) mImageView.getBackground();
        if (drawableRel != null) {
            System.gc();
            size = Runtime.getRuntime().totalMemory();
            drawableRel.setCallback(null);
            drawableRel.getBitmap().recycle();
            drawableRel = null;
            System.gc();
            android.util.Log.d("test_my_log","rel_size="+(Runtime.getRuntime().totalMemory() - size));
        }</span>

Log是这样子的:

D/test_my_log(29891):rel_size=0

D/test_my_log(29891):rel_size=0

D/test_my_log(30292):rel_size=0

D/test_my_log(30292):rel_size=0

D/test_my_log(30292):rel_size=0


释放内存的大小是0,这说明什么,大家就自个体会吧。

今天就先扯这些了,以后有机会再接着扯,还是那句话,给大神们茶余饭后取乐,给后来者抛砖引玉,对与错大家自个判定,只是不要取笑鄙人就好~



Android内存那点事儿