您的位置:首页 > 移动开发 > Android开发

android bitmap内存那些事

2016-01-10 14:05 816 查看
bitmap内存占用是一个困扰我很久的问题,今天就好好理一理!
这篇文章用的图片为下面这张图:480 * 800 (这里放上来的我把它缩小了,要不然网页上显示着特别大)



问题:一张压缩前 和 压缩后的图片,PC上的大小有区别,但尺寸不变,只是质量压缩了,那么读入内存时占用的内存大小呢?
验证:
机型:小米2s
压缩前的图大小如下:



压缩后的图大小如下:



加载10张该图,三次打印内存分别对应:加载前、加载后、释放后
下面的内存解释:
maxMem(Runtime.getRuntime().maxMemory()):可以分配给一个应用程序的最大内存;
totalMem(Runtime.getRuntime().totalMemory()) : 当前实际分配给该应用程序的内存;
freeMem(Runtime.getRuntime().freeMemory()):当前实际分配的内存中空闲的内存
totalMem - freeMem 就是当前应用程序实际消耗的内存
代码:
/**
* 图片占用内存测试
*/
public static void bmpMemTest() {
//打印内存占用
LoggerEx.printMem("before load  ");

int cnt = 10;
Bitmap [] bms = new Bitmap[cnt];
for(int i = 0; i < cnt; i++) {
bms[i] = BitmapFactory.decodeResource(App.instance().getResources(), R.drawable.guess_game_bg);
//guess_game_bg_compressed 是在PC上压缩后的图
//			bms[i] = BitmapFactory.decodeResource(App.instance().getResources(), R.drawable.guess_game_bg_compressed);
//及时recycle,程序内存不足时,会去释放之前占用过的内存,所以加载多少图片都不会 out of memory
//			bms[i].recycle();
}

try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
LoggerEx.printMem("after load  ");

for(int i = 0; i < cnt; i++){
if(!bms[i].isRecycled()) {
bms[i].recycle();
bms = null; //设置null 这个对象才会gc的时候回收
}
}
System.gc();

try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
LoggerEx.printMem("after recycle");
}
/**
* 打印当前程序占用的内存
*/
public static void printMem(String when) {
//程序可用的最大内存
float maxMem = Runtime.getRuntime().maxMemory() / 1024 / 1024;
//程序当前占用的内存
float totalMem = Runtime.getRuntime().totalMemory() / 1024 / 1024;
//freeMem != maxMem - totalMem
//我理解 freeMem应该是 当前分配给该程序的内存 - totalMem, 当前分配给程序的内存时动态的(在小于maxMem范围内)
//同virtualbox安装的ubuntu虚拟机占用内存类似,设置个最大内存,但实际占用内存时动态分配的
float freeMem = Runtime.getRuntime().freeMemory() / 1024 / 1024;

bm(when + ": maxMem | totalMem | freeMem : " +
maxMem + "M|" + totalMem + "M|" + freeMem + "M");
}


加载压缩前的10张图片,内存占用如下:



把图片在线压缩,从原来的267kb压缩到85kb,再加载到内存, 占用的内存仍然是一样多



从上述数据看出,无论图片质量好坏,加载到内存中占用内存的大小只与图片大小、Config图片质量参数配置有关,压缩图片只是让打包的apk减小,而运行时的内存大小是无关的。
ps: 上图中看出小米2S给一个应用程序分配的最大内存时96M,
android的config有以下几种:
ALPHA_8——代表8位Alpha位图
ARGB_4444——代表16位ARGB位图
ARGB_8888——代表32位ARGB位图
RGB_565——代表8位RGB位图

理论:图片占用的内存大小,根据bitmap的Config设置,不同质量占不同的大小,如 ARGB_8888,一个像素占4字节,一张 480*800 ,看做横竖每交叉点一个像素
总内存 = 4 * 像素点数量 = 4 * (480 * 800)= 1536000byte = 1500KB=1.5M
但上图中计算出一张图片占用的内存并非1.5M,而是 ((42 - 5)- (18 - 8)) / 10 = 2.7M (这是初略计算),这是为什么? 难道理论有错??
当然理论没错了,原因是我把图片放在了 drawable-hdpi目录下,小米2S机型对应的是 drawable-xhdpi目录,android系统会自动把 hdpi目录下的图片等比例放大约1.8倍导致的,放大的倍数根机型有关,如果不希望被放大,那么就把图片放在xhdpi目录下;这就涉及到机型适配问题了

问题:图片加载出现out of memory,亲眼见识
测试机型:HUAWEI G750-T00
加载40张 480 * 800的图片,内存占用如下图, 实测加载到46张图片时,outOverMemory,
ps: android一个应用程序可分配的最大内存默认16M,不同手机版本和型号有所不同,
参考链接: http://zhidao.baidu.com/link?url=2uJUgMpXT5sgXbvgf0Nm-090nsP0PXe_77Afsmtc1-Rt6z3-5v-8oozVNQA3Rz6UBZVZzlCTXpVm2O_mMvnqMW6a1YwPL_ejbuTpWWhUMlm



PS:HUAWEI G750-T00 释放图片后,分配的内存立即恢复到加载图片前4M,但小米2S在释放后,虽然回收了,空闲内存多了,但分配给当前应用程序的内存并没恢复,还是那么多42M,每个机型的实现机制不一样。

如果每次加载到内存后,立即释放,则可以无限制加载,不会出现out of memory,看来调用recycle之后,虽然不立即释放,但当内存不足时还是会去释放的,并分配给新对象





打出来的totalMem 一直是18M,说明一直在释放,测试机型:小米2S
所以bitmap用完后recycle是很有必要的

ps: 测试发现,HUAWEI G750-T00空应用占约(4-1)=3M内存,小米2s占约(18-8)=10M内存 , 区别这么夸张?

问题:imageView 显示一个图片,内存占用是否与加载到bitmap一样多? imageview所在的activity finish后,占用的内存是否释放掉?立即释放还是内存不足时释放?
验证: 图片显示到imageView 和 解码到bitmap,占用内存一样多;
activity finish后,可能不会立即释放内存,但内存不足时应该是会释放掉的,只要这个activity里的对象没被其他类引用;所以android里不用过于关注activity里各种内存占用、释放,只要引用不被其他类引用

问题:xml里设置ImageView的src 和 代码中设置imageResource占用的内存哪个大?
验证:
机型:HUAWEI G750-T00,图片尺寸:480*800
(1)xml设置图片



内存:



(2)代码设置图片



内存:



从上图而知,占用的内存一样多。
那么如果imageView的width 不是wrap_content呢?比如 width=100,height=150
经测试,占用的内存情况与上述wrap_content完全相同; 看来android底层加载图片时,也没有进行内存优化,未根据imageView控件大小对图片做缩放后再加载进来;或者什么原因不优化

问题:bitmap加载内存优化,究竟能优化多少
验证:机型HUAWEI G750-T00,加载 480*800的10张图片
(1) 普通的 BitmapFactory.
decodeResource 占用内存:



(2) 通过
Options优化,目标大小为200*200,根据期望的大小加载图片:



上面两个结果,普通方式占用32M,优化后占用5M,效果是显著的; 对于优化大图加载效果显著,如果是很小的图就不一定要这么优化;
PS:图片优化不要在UI线程
代码:
<span style="font-size:14px;">	/**
* bitmap加载优化,测试优化后占用内存大小
*/
public static void optimizeBmMem() {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(App.instance().getResources(), R.drawable.guess_game_bg, options);
options.inSampleSize = calculateInSampleSize(options, 200, 200);
options.inJustDecodeBounds = false;

LoggerEx.printMem("before load");
int cnt = 10;
Bitmap []bitmaps = new Bitmap[cnt];
for(int i = 0; i < cnt; i++) {
bitmaps[i] = BitmapFactory.decodeResource(App.instance().getResources(), R.drawable.guess_game_bg, options);
}
LoggerEx.printMem("after load");

for(int i = 0; i < cnt; i++) {
bitmaps[i].recycle();
}
}

/**
* 计算图片采样率
* @param options
* @param reqWidth
* @param reqHeight
* @return
*/
private static int calculateInSampleSize(BitmapFactory.Options options,int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;

if (height > reqHeight || width > reqWidth) {

final int halfHeight = height / 2;
final int halfWidth = width / 2;

while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}

long totalPixels = width / inSampleSize * height / inSampleSize ;

final long totalReqPixelsCap = reqWidth * reqHeight * 2;

while (totalPixels > totalReqPixelsCap) {
inSampleSize *= 2;
totalPixels /= 2;
}
}
return inSampleSize;
}</span>


问题:创建一个空的bitmap 480*800, 占用的内存大小是不是与 加载一张480*800的图片大小一样?
验证:机型HUAWEI G750-T00,
(1)加载本文中480*800的10张图片(放在 drawable-xhdpi目录)



(2)
Bitmap.createBitmap(480, 800, Config.ARGB_8888) 10张:



从上图看出,占用内存一样多,证明 理论是正确的,bitmap占用内存大小 与三个因素有关: width 、height、config质量参数
代码:
<span style="font-size:14px;">	public static void emptyBmp() {
LoggerEx.printMem("before load");
int cnt = 1;
Bitmap []bitmaps = new Bitmap[cnt];
for(int i = 0; i < cnt; i++) {
bitmaps[i] = Bitmap.createBitmap(480, 800, Config.ARGB_8888);
}

LoggerEx.printMem("after load");
for(int i = 0; i < cnt; i++) {
if(!bitmaps[i].isRecycled()) {
bitmaps[i].recycle();
bitmaps[i] = null;
}
}
}</span>


问题:同一张图片,在不同的机型上占用的内存是否相同
验证:机型:HUAWEI G750-T00、小米2S
加载10张 480*800的图片,三条日志对应:加载前、加载后、recycle后
华为内存:



图片占用内存= (33 - 1)- (4 - 1)= 29M
小米2S内存:



图片占用内存=( 42 -5 )- (18 - 8)= 27M
这只是大概的计算,两者差不多,这只是初略计算,用下面的代码可以更精确的读取bitmap占的内存大小

问题:android bitmap占用的内存代码直接计算
验证:
代码: int bitmapMem = bitmap.getByteCount(); (ps: 还有别的计算方式,可以在网上查)
bitmapMem = 1.5M

问题:PC上看 480*800的图片,读入内存bitmap后大小 getWidth getHeight值变了
getWidth: 640, getHeight: 1067
原因:drawable存放目录导致,我放在drawable-hdpi目录下,机型是 720*1080分辨率的(320密度),
改放在 drawable-xhdpi目录下,bitmap.getWidth() 值就与预期一致了为480,竟然还有这学问?!

drawable目录,android图片处理有自动缩放功能,如果xhdpi的机型,读取hdpi目录,则会认为图适配的机型是hdpi,用在 xhdpi的机型需要等比例放大,故出现这样的问题

PS:测试的代码工程上传到csdn了,有兴趣的可以交流交流哦,地址:http://download.csdn.net/detail/duantihi/9398161
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: