首页 > 代码库 > 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内存那点事儿