Android内存优化之图片优化

背景

通常来讲,图片是APP占用内存高的主要缘由,因此优化图片的内存占用是避免OOM的根本手段。对于图片占用的内存,咱们可能总有这样的误区:图片自己所占的存储空间越小,占用的内存越小。因此认为只要将图片进行压缩,就至关于减少了内存占用。其实这是不对的,图片占用的存储空间大小与所占内存大小没有直接关系。git

既然与内存没有关系,那压缩图片有什么意义呢?对于APK而言,压缩图片是为了减少APK的体积,而对于须要网络请求的图片,压缩则是为了更快的网络响应。github

因此优化以前须要清楚2个基本原则:

  • 图片占用内存的大小与图片自己的大小没有直接关系;
  • WebP格式的图片虽然小,但占用的内存和其余格式无差异;

图片占用内存的大小

memorySize ≈ width * height * 每一个像素须要的字节数
复制代码

优化策略

既然须要的内存公式已获得,那优化就显而易见了,无非就是减少的这三个参数的值,具体的策略以下:web

这里咱们将图片分为2种状况来探讨:网络

drawable中的图片

单独探讨这种状况,是由于Android系统会对drawable中的图片进行缩放,缩放系数与设置的屏幕分辨率和drawable所表示的分辨率有关,具体的公式以下:优化

scale = 设备分辨率 / 资源目录分辨率  如:1080x1920的图片显示xhdpi中的图片,scale = 480 / 320 = 1.5
复制代码

因此此时图片占用的内存大小为:spa

memorySize ≈ (width * scale) * (height * scale) * 每一个像素须要的字节数
           ≈ width * height * scale ^ 2 * 每一个像素须要的字节数
复制代码

具体的缩放过程可参考Android中Bitmap内存优化code

这里咱们只探讨一下scale系数的影响因素:设备分辨率和资源目录分辨率。至于其余的可变因子会在另外一种状况中介绍。设备分辨率咱们无法改变,因此影响因素只有资源目录分辨率,也就是说,同一张图片,放在不一样的drawable中,占用的内存大小不一样。从公式可看出,使用同一个设备时,drawable表示的分辨率越高,则图片占用的内存越小,反之越大。因此,在作图片的兼容性时,若是只想使用一张图片,则应使用3倍甚至4倍的图片(3倍是主流机型,但在4倍手机上会被放大,图片可能失真),这样在低分辨率的手机上,不只显示清晰,并且系统会自动进行缩放,从而确保占用较小的内存。orm

一样是存放图片的位置,为何mipmap不在这种状况的考虑范围以内呢?由于mipmap是Android系统为了不Launcher Icon变形而添加的资源目录,也就是说,mipmap中的图片不会被缩放。因此Google也不推荐将除Launcher Icon以外的图片放在mipmap目录中。图片

其余位置的图片

其余位置的图片包括mipmap, asset, 本地图片,网络图片等。这些位置的图片都有一个共同点——不会被缩放。因此只须要考虑如何改变图片分辨率和每一个像素须要的字节数便可。ip

本地图片

本地图片一般都是经过Android提供的BitmapFactory来加载的, 这里看几个经常使用的API:

// 根据路径加载
public static Bitmap decodeFile(String pathName, Options opts);
// 加载drawable或mipmap中的图片
public static Bitmap decodeResource(Resources res, int id, Options opts)
// 根据字节流加载
public static Bitmap decodeByteArray(byte[] data, int offset, int length)
// 根据IO流加载
public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts)
复制代码

图片的优化可经过Options参数来实现(Options的介绍可参考从fresco 看图片优化):

方式一:inSampleSize

inSampleSize可理解为图片的缩小比例,若inSampleSize小于1,则当作1处理。设置inSampleSize后,图片的宽度和高度将变成原来的1/inSampleSize, 其占用的内存空间将是原来的1/(inSampleSize ^ 2)。可是具体如何取值呢,可经过如下代码来获取:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.image, options);
options.inSampleSize = getSampleSize(options, 100, 100);
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.abc, options);
imageView.setImageBitmap(bitmap);

public static int getSampleSize(BitmapFactory.Options options, int viewWidth, int viewHeight) {
	if (viewWidth == 0 || viewHeight == 0 || options == null) {
		return 1;
	}
	int widthScale = options.outWidth / viewWidth;
	int heightScale = options.outHeight / viewHeight;
	Log.i("out", "width==" + widthScale + " heightScale==" + heightScale);
	return widthScale >= heightScale ? heightScale : widthScale;
}
复制代码

方式二:inDensity

inDensity至关于上面说的资源目录分辨率,前面说了,这里考虑的状况,图片不会被缩放,其缘由就是inDensity和设备分辨率的取值是一致的,由于inDensity=设备分辨率,因此scale=1, 若是将inDensity设置为大于设备分辨率的值,那么图片就会被缩小。例如,当前的手机1dp=2px, 即2X屏幕,此时的inDensity为320, 若是将inDensity修改成480, scale=320f/480f=2/3, 那么图片所占用的内存将变成原来的4/9。

方式三:inPreferredConfig

inPreferredConfig的取值为Bitmap.Config类型(这里只考虑如下几种状况),它是一个枚举类型,用来设置每一个像素须要的字节数:

ALPHA_8:占1个字节
RGB_565:占2个字节
ARGB_4444:占2个字节,已废弃,不推荐使用
ARGB_8888:32位真彩色,带透明度,占4个字节
复制代码

显示图片时默认都是ARGB_8888,因此咱们可经过inPreferredConfig的值进行内存优化。但实际上inPreferredConfig的取值对内存的影响并非简单的Bitmap.Config.ALPHA_8占1个字节,ARGB_4444和RGB_565占2个字节,ARGB_8888占4个字节,而是与具体的图片格式有关:

  • inPreferredConfig对jpeg和gif格式的图片无做用,不管inPreferredConfig的值取什么,jpeg格式的图片每一个像素始终占用4个字节,而gif格式的图片每一个像素始终占1个字节;
  • 对于webp格式的图片,inPreferredConfig取值为RGB_565的时候,每一个像素占用2个字节,其他的取值每一个像素仍然占4个字节;
  • 对于png格式的图片,须要分png8, png24, png32三种状况来讲。png8格式的图片每一个像素占用的字节数随inPreferredConfig的取值而变化,取值为ALPHA_8时占用一个字节,取值为RGB_565时占用2个字节,取值为ARGB_4444或ARGB_8888时占用4个字节。png24格式的图片,当inPreferredConfig的取值为RGB_565时,每一个像素占用2个字节,取其余的值(ALPHA_8, ARGB_4444和ARGB_8888)每一个像素都占用4个字节。而对于png32格式的图片,inPreferredConfig的取值(ALPHA_8, RGB_565, ARGB_4444或ARGB_8888)对每一个像素占用的字节数无影响。

因此,若是经过inPreferredConfig来优化图片的内存占用,就须要webp或png24格式的图片,png24与png32相比,也就是不支持透明度而已,对于大多数图片来讲,二者没有明显的差异。固然,做为一种新的图片格式,web可认为是一种不错的选择。

注意: 9patch图虽然在使用时会根据View的尺寸进行放大,但其像素仍然不变,可视为普通图片来处理;

网络图片

网络图片一般咱们都是使用开源库进行加载(这里顺便推荐一个好用的图片加载库ImageSet), inPreferredConfig的值一般可在初始化时进行配置,至于缩放,可以让后台进行实现。即:根据图片的请求参数返回合适的尺寸。最大也只须要控件的大小便可,再大也没意义,不只浪费流量,还占用内存。若是你的APP中有不少图片,那么可对图片的宽高根据设备的内存状况进行适当的缩小:

// 根据内存大小设置缩放系数
public static float getDefaultScale() {
    float scale = 1.0f;
    int totalMemorySize = AndroidPlatformUtil.getTotalMemorySize();
    if (totalMemorySize >= 4) {
        scale = 1.0f;
    } else if (totalMemorySize >= 2 && totalMemorySize < 4) {
        scale = 0.8f;
    } else {
        scale = 0.6f;
    }

    return scale;
}

// 获取设备的内存大小,返回值单位为G
public static int getTotalMemorySize(){
	String path = "/proc/meminfo";
	String firstLine = null;
	FileReader fileReader = null;
	BufferedReader bufferedReader = null;
	try{
		fileReader = new FileReader(path);
		bufferedReader = new BufferedReader(fileReader,8192);
		firstLine = bufferedReader.readLine().split("\\s+")[1];
	} catch (Exception e){
		e.printStackTrace();
	} finally {
		try {
			if (bufferedReader != null) {
				bufferedReader.close();
			}
		} catch (Exception e) {
			e.printStackTrace(System.out);
		}

		try {
			if (fileReader != null) {
				fileReader.close();
			}
		} catch (Exception e) {
			e.printStackTrace(System.out);
		}
	}

	if(TextUtils.isEmpty(firstLine)){
		return (int)Math.ceil((new Float(Float.valueOf(firstLine) / (1024 * 1024)).doubleValue()));
	}

	return 0;
}
复制代码

总结

对于一个多图片的APP来讲,图片所占内存的优化是一项必不可少的工做。总的来讲,其优化也就是经过缩放和指定Bitmap.Config的值来实现的,只是不一样位置,不一样格式的图片有所差别而已。

相关文章
相关标签/搜索