1、背景
要展示一张图片,先得把图片加载到内存,才能把图片显示出来。在Android开发中,显示图片有很多种方式,而区别就在于显示图片时占用的内存和显示的效果不同。
Android内存优化中图片显示是需要特别关注的点,所以这里总结展示图片的方式,并对每种方式进行分析其内存的占用情况。
用来显示图片的ImageView.setImageResource()方法内部实现是通过BitmapFactory.decodeXXXX()方法实现,其实在开发时显示图片的过程都会通过BitmapFactory.decodeXXXX()系列方法从不同的数据源解析图片,所以最终要分析的就是BitmapFactory.decodeXXXX()系列方法。
下图红框里的是BitmapFactory提供的用于解析图片的方法:
提供了从不同的数据源解析图片的方法,如:文件、输入流、资源文件、Byte数组等。
2、分析
这里我把这些图片的数据源划分为两类
1. 工程资源文件数据源
2. 非工程资源文件数据源
划分的依据是根据是否属于工程的资源文件数据源,因为我们知道解析工程资源文件时,系统会根据存放的目录和屏幕的密度进行缩放,所以其实就是根据在解析图片是否对图片进行缩放进行划分。
查看Factory.decorXXXX()系列方法的源码,你会发现其实就是根据不同的数据源设置不同的解析参数,最终调用native方法进行解析:
private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage,
Rect padding, Options opts);
在调用该方法之前,都是根据不同的数据源设置Options参数。
进入BitmapFactory.decodeResourceStream(res, value, is, pad, opts);
创建了BitmapFactory.Options对象,并分别设置了三个参数inScreenDensity、inDensity、inTargetDensity。
并最终调用Native方法nativeDecodeStream()解析图片。BitmapFactory.Options中的这个三个参数我们这里需要详细的讲解一下,因为这三个参数决定图片解析过程的压缩比例
经过上面对三个重要的配置参数在解析图片时的作用,我们知道Native方法肯定会根据这三个参数对图片进行缩放
scale就是计算出来的缩放比例
第一步:判断isScaled,是否允许进行缩放
第二步:读取出inDensity、inTargetDensity、inScreenDensity三个参数
第三步:判断若inDensity为0 或inTargetDensity为0 或inDensity等于inScreenDensity,则不进行缩放。
3、总结
1:缩放比例为(inTargetDensity/inDensity),而inTargetDensity在相同设备上的值是不变的屏幕像素密度 ,inDensity的值是根据图片所在的资源文件而不同 ,如:来自drawable-hdpi则inDensity为240、来自drawable-mdpi则inDensity为160。这就是我们需要注意的,在同一个设备上,图片放在不同的drawable中,加载出来的bitmap占用的内存大小是不同的。如:设备屏幕密度为480dpi, 一张500*633图片,放在drawable-mdpi文件夹中,那如果直接通过ImageView.setImageResource(resId)显示图片,那图片将放大480/160=3倍,
占用内存=500宽*633高*3倍*4字节=3.6M。如果图片放在drawable-xxhdpi文件夹中,那图片放大480/480=1倍, 占用内存=500宽*633高*1倍*4字节=1.2M
2:在我们的输入法主题商店中,下载的主题资源包中的图片都是存放在drawable-hdpi文件夹中,当加载主题资源时,inDensity=240dpi, 如果在屏幕像素密度小于240dpi的设备上应用主题
那主题资源会缩小,但如果在屏幕像素密度高于240dpi的,主题资源图片都会被放大,在一些xxhdip的设备上甚至会被放大2倍,这是一个可以进行内存优化的一个点
3:我们可通过设置Options.inSampleSize参数,对图片进行采样压缩。例如:在一个小的ImageView显示一张图,而该图比较大,如果把该图全部像素点加载到内存,会占用大量的内存。我们可设置Options.inSampleSize参数,对图片进行采样压缩。