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

Android知识总结:图片放错mipmap文件夹导致内存泄露的问题

2016-12-01 21:53 579 查看

背景

最近开始着手优化app的性能。其中有这样一个activity,只要手机一开启内存就会飙升二十多M, 之前认为可能是其中大的图片资源解压导致,但是通过查看记录内存的.hprof文件,这个activity布局中一个ImageView中的bitmap竟然引用了二十多M的内存空间, 但是这张图在布局中是一张很小的图,打开图片资源文件,发现原图也仅仅是一张800x800的图…. 奇怪的是,当把这个应用放到一个平板上时,内存又变化的不是那么明显了….

分析

我们知道Android中图片正常情况下是以RGBA的格式进行显示,其中RGBA中每一位占一个byte,显示一个像素点需要4个byte。根据经验我们可以猜测Android中位图buffer的大小是 长x宽x4。也就是说一张800x800的图片,解压出来大小800 x 800 x 4等于2.56M, 而事实上我们拿到的buffer大小是这个数字的整整9倍。

为了防止其他代码的干扰,我单独开了一个工程,得到的结果也是一样的。通过翻阅一些资料,我发现解压图片的大小和资源文件是有关系的。之前总是听前人说切三套不同尺寸的图,分别放倒hdpi,xhdpi,xxhdpi,然后就万事大吉了,实践中也基本没出过大问题,现在才发现其实原先一直都没有领会不同mipmap文件的真正用途。

下面我使用三台手机做了三个实验,一台是像素密度为640dpi的三星,一台密度为480dpi的华为,还有一台密度为160dpi的华为平板。将一张2000x2000的png图片放入mipmap的mdpi文件下,使用如下代码进行测试

BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap bitmap2 = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher_mine, opt);


我们在程序中打断点,查看每一个bitmap2中引用的byte数组大小。我们先使用像素密度高达640dpi的三星手机开刀…. 我们可以先猜测一下这张图的解压大小 ,会是16000000吗…..

其实程序运行的结果是,数组大小256 000 000,也就是256M, 是的没错,是256M……起初看到这个结果我也懵逼了,一张小小的图片那里来了那么多额外信息……

下面是这张图在不同手机的不同资源文件夹下解压大小的统计

640dpi的手机 (xxxdpi)

存放图片资源文件夹    文件解压大小

xxxhdpi            16 000 000
xxhdpi             28 451 556
xhdpi              64 000 000
hdpi              112 763 556
mdpi              256 000 000


480dpi的手机(xxdpi)

存放图片资源文件夹    文件解压大小

xxxhdpi             9 000 000
xxhdpi             16 000 000
xhdpi              36 000 000
hdpi               64 000 000
mdpi              144 000 000


160dpi的平板 (mdpi)

存放图片资源文件夹     文件解压大小

xxxhdpi             1 000 000
xxhdpi              1 779 556
xhdpi               4 000 000
hdpi                7 710 556
mdpi               16 000 000


通过上面的结果我们可以看出,只有图片存储到手机本身像素密度对应的dpi文件夹中,文件解压的大小才是理论上的解压大小。

对于高密度的手机,如果资源文件存放于低密度的文件夹中,那么高密度的手机会认为这张图片的像素密度不够,所以解压的时候会自动填充更多的像素信息。

同理,对于低密度的手机,如果资源文件存放与高密度的文件夹中,那么低密度的手机会认为图片密度过高,而自己的密度达不到那么高,所以就会降采样,最终得到的位图也会变小。

所以,针对上面出现的问题我们应该注意以下几问题

1.避免将高分辨率的图放入低密度的资源文件中,否则高密度的手机加载图片时会认为这张图是一张低密度的图,进而填充更多像素点,浪费内存。

2.如果低分辨率手机加载高分辨率文件夹中的图片时,解压的图片会自动降采样,对于一些图片,清晰度会下降,有些线条多的图片会变得非常模糊,所以最好在低分辨率文件夹中放一套图,或者给那些不清晰的图片单独放。

补充

发现一个网站可以对png图片进行再次压缩。我们直接从sketch中导出的png图是非常大的,有些背景图甚至上兆,而apk打包时图片并不会被压缩,这就导致我们的apk包非常大。

这个网站为我们提供了png图片压缩的接口

https://tinypng.com/

这里分享一下我使用Python调用其api的脚本

其中 compress 函数用于压缩传入的图片,compressResize函数使用Python的PIL包中的方法将图片长宽缩减为原图的一半,再进行压缩。

tinify.key的值需要自己在网站进行申请

import tinify
import os
import os.path
from PIL import Image

tinify.key = 'xxxxxxxxxx';

sourceDir = '/Users/lidechen/Desktop/compress/test/';
targetDir = '/Users/lidechen/Desktop/compress/output/';
targetDirResize = '/Users/lidechen/Desktop/compress/output_resize/';

def compress(filename):
source = tinify.from_file(sourceDir+filename)
source.to_file(targetDir+filename)
print 'done'

def compressResize(filename):
source = tinify.from_file(sourceDir+filename)

im = Image.open(sourceDir+filename);

originalWidth = im.size[0]
originalHeight = im.size[1]
print 'original size '+str(originalWidth)+' x '+str(originalHeight)

newWidth = originalWidth/2
newHeight = originalHeight/2
print 'new size '+str(newWidth)+' x '+str(newHeight)

resized = source.resize(
method = 'fit',
width = newWidth,
height = newHeight
)
resized.to_file(targetDirResize+filename)

#source.to_file(targetDirResize+filename)
print 'resize done'

if "__name == __main__":

counter = 0
for root, dirs, files in os.walk(sourceDir):
for file in files:
if os.path.splitext(file)[1] == '.png':
print ''
counter = counter + 1;
print '######### counter '+str(counter)+'#########'
print 'compress .... '+file
compress(file);
#compressResize(file);
print 'done  '+file+'  !'
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: