Android如何加载大图,防止OOM

Android加载大图,防止OOM

本文是根据Android开发文档写的,其中多次提到了堆内存,不太了解的同学可以先预习下JVM的内存模型,再来食用本文。

什么是OOM

OutOfMemory(内存泄漏),当Java虚拟机由于内存不足而无法分配对象,并且垃圾回收器无法再提供更多内存时,抛出异常OutOfMemoryError。

为什么我们的移动设备加载大图需要处理

在网络上有着许许多多的图片,有高清的,有高糊的,在pc端我们可以为所欲为,但在移动端,不可能让我们这样啊,手机的内存没有那么的大,我们的图片都是加载到内存堆中,一个app分配的内存堆是有限的,我们还要存储其他对象使用,还有一个重要因素就是虽然wifi已经普及,但还有大多数上了年纪的人对流量的概念不清楚,用你的app几分钟不仅oom了或者ANR了还欠费了(UI线程加载位图可能会降低程序性能,导致前台响应速度变慢而ANR),所以我们需要“改变图片的大小”。

处理的逻辑

显示的时候符合实际的显示规格,而不是整个图片的加载,当用户自己想看完整分辨率的图片的时候再将完整的图片载入内存显示。

处理方案

最原始的处理方案就是官网的方案,Android对位图的处理提供了整整一组的方法。这我想也应该是其他加载图片框架的基础原理。

CreateScaledBitmap

CreateScaledBitmap是Bitmap中的一个api,能更具自定义创建一个新的Bitmap,如果与原来的宽高相同则不会创建新的Bitmap。 缺点:他必须先创建一个位图。也就是说需要这个图片先被加载,解码。导致性能不高。 所以一般不采用该模式加载大图,除非你的需求是显示同一张图片不同大小。

BitmapFactory bitmapFactory = new BitmapFactory();
Bitmap bitmap = null;
try {
    bitmap = BitmapFactory.decodeStream(getAssets().open(("scg.jpeg")));
} catch (IOException e) {
    e.printStackTrace();
}
if(null != bitmap){
    //必须传入一个不为null的bitmap,宽度,高度,是否使用双线性滤波优化图片
    Bitmap changeBt = Bitmap.createScaledBitmap(bitmap, 480,240,true); 
    imgv.setImageBitmap(bitmap);
    cImgv.setImageBitmap(changeBt);
}

效果图:

 

inSampleSize

BitmapFactory.Options#inSampleSize

 

这是BitmapFactory.Options中的一个成员变量: 使用解码器对原图进行二次取样,接收的是一个int值,因为我们的位图就是我们的像素图,是由一个一个的小像素块组成的(你把一个图使劲放大就能看到它是由一堆小方块拼接而成的了)。小于等于1的值返回的结果都与原图一样,inSampleSize == 4 返回的图像为原始宽度/高度的1/4,像素数目的1/16。
它的原理是:行列方向每隔n格取一个像素,最后合并为一张图片

BitmapFactory.Options三件套(inScaled+inDensity+inTargetDensity)

inScaled

inScaled设置为true时(设置此标志时),如果inDensityinTargetDensity不为0,Bitmap就会在加载的时候直接进行缩放以匹配inTargetDensity,而不是绘制的时候进行缩放。(加载到堆内存时已经缩放了大小了)(.9图会忽略此标志)

inDensity

加载图片的原始宽度,如果此密度与inTargetDensity不匹配,则在返回Bitmap前会将它缩放至目标密度。

inTargetDensity

目标图片的显示宽度,它与inScaled与inDensity结合使用,确定如何在返回Bitmap前对其进行缩放。

原理

它的会进行相应的计算,进行颜色的混合,从而生成新的图片。也就是说图片越大它处理的性能也就越差。但它显示的效果要比inSampleSize的效果好,色彩还原度要高许多。

扩展(如何知道图片的原始大小?)

我们要先知道图片的原始大小,然后对其进行目标密度的缩放,但如何知道图片的原始大小呢?先加载到堆内存?然后再生成新的Bitmap?这样的话和CreateScaledBitmap不是几乎一样了吗?还是会有导致OOM的风险。 我们就要介绍inJustDecodeBounds属性,它属于BitmapFactory.Options,将其设置为true,它会进行一次图片的解析,但不会生成Bitmap对象,它返回的是bull,但他会对out...属性赋值,允许调用者查询Bitmap而不用为其分配内存。后续我们就可以使用BitmapFactory.Options的实例中的outWidth,outHeight获取原始图片的宽高,就可以进行像素的压缩了。

代码

BitmapFactory.Options mBitmapOptions = new BitmapFactory.Options();
mBitmapOptions.inJustDecodeBounds = true;

BitmapFactory.decodeResource(getResources(), R.drawable.scg, mBitmapOptions);
int srcWidth = mBitmapOptions.outWidth;
int srcHeight = mBitmapOptions.outHeight;

BitmapFactory.Options cBitmapOptions = new BitmapFactory.Options();
cBitmapOptions.inScaled = true;
cBitmapOptions.inDensity = srcWidth;
cBitmapOptions.inTargetDensity = srcWidth * 2;

Bitmap cbitmap = BitmapFactory.decodeResource(getResources(), R.drawable.scg, cBitmapOptions);

cImgv.setImageBitmap(cbitmap);
Toast.makeText(this,""+mBitmapOptions.outHeight,Toast.LENGTH_SHORT).show(); //结果:265 ,我本地的图片为265x265 
复制代码

效果图:

 

综合

结合上面两种方案,我们可以配套使用 inSampleSize + 三件套 。 先将图片进行快速初略压缩(inSampleSize),再进行细微的精细压缩(三件套),只测量原图的相应属性使用inJustDecodeBounds,最后就可以按需要显示“大图”了。想要原图就使用下载或者只加载对应的原位图到堆内存中就好。

总结

以上就是Android官方文档视频对位图处理的方法。我想也应该是其他框架的压缩原理。官网视频还介绍了Glide与Picass中有异步解码和缓存。(这里我想说的是这两个框架的使用方法几乎一模一样,但好像在with中的绑定是不同的,他们的绑定生命周期不同,你使用Glide的with如果绑定的是this,可能按了home再回到Activity应用会crash掉,如果有兴趣大家可以去了解一下这两个框架)

好了,文章到这里就结束了如果你觉得文章还算有用的话,不妨把它们推荐给你的朋友。

漫漫开发之路,我们只是其中的一小部分……只有不断的学习、进阶,才是我们的出路!才跟得上时代的进步!

今年年初我花一个月的时间收录整理了一套知识体系,如果有想法深入的系统化的去学习的,可以私信我【学习】,我会把我收录整理的资料都送给大家,帮助大家更快的进阶。

发布了289 篇原创文章 · 获赞 30 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_45365889/article/details/102770775