Android如何加载大图,防止OOM

Android加载大图,防止OOM

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

OOM与SOF

OutOfMemory(内存溢出),当Java虚拟机因为内存不足而没法分配对象,而且垃圾回收器没法再提供更多内存时,抛出异常OutOfMemoryError。api

StackOverFlow(栈溢出),当应用程序递归过深致使堆栈溢出时抛出异常StackOverflowError。缓存

(ps:内存泄露的主要缘由是new出来的Object放在Heap上没法被gc回收,会致使内存泄漏)bash

感谢你们指出的错误,这里补充一下两个的区别网络

为何咱们的移动设备加载大图须要处理

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

处理的逻辑

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

处理方案

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

CreateScaledBitmap

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

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);
}
复制代码

效果图: 性能

creatScaledBitmap

inSampleSize

BitmapFactory.Options#inSampleSize

inSampleSize

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

inSampleSize

BitmapFactory.Options cBitmapOptions = new BitmapFactory.Options();
cBitmapOptions.inSampleSize = 2;
Bitmap cbitmap = BitmapFactory.decodeResource(getResources(), R.drawable.scg, cBitmapOptions);

cImgv.setImageBitmap(cbitmap);
复制代码

效果图:

inSalmpSize.png

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

inScaled

inScaled

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

inDensity

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

inTargetDensity

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

原理

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

inDensity

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

咱们要先知道图片的原始大小,而后对其进行目标密度的缩放,但如何知道图片的原始大小呢?先加载到堆内存?而后再生成新的Bitmap?这样的话和CreateScaledBitmap不是几乎同样了吗?仍是会有致使OOM的风险。 咱们就要介绍inJustDecodeBounds属性,它属于BitmapFactory.Options,将其设置为true,它会进行一次图片的解析,但不会生成Bitmap对象,它返回的是null,但他会对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 
复制代码

效果图:

inJustDecodeBounds

综合

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

总结

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

PS

在最后一个🌰中我发现:上面的那个原理图上写着inTargetDensity / inDensity ,它主要是按像素进行压缩的,是一种图形处理的规则吧!由于我肯定了inDensity后,无论怎么修改inTargetDensity图片的宽高都不会改变,但为0的话图片的清晰度不会变,inTargetDensity / inDensity 的值很小的话他会糊掉,应该就是像原理图同样进行色彩混合了,inTargetDensity / inDensity 的值很大的话,会有明显的短暂白屏(应该是计算合并处理会花费大量的时间),再大就oom了,因此在处理大图的时候才有了总结的先使用inSampleSize。想改变图片的长宽的话,就使用inDensity。它是根据你手机的dpi与inDensity的进行缩放的。(我模拟器使用的xmdpi是160 / inDensity,因此咱们看效果图没缩小一半)若是inDensity设置为80它就会放大为原图的2倍。同理320就是缩小为一半。inDensity = srcWidth的话就至关于进行了自适应。

就是说inTargetDensity能够不设置,设置后是根据inTargetDensity / inDensity的值对图片进行“优化”;inDensity设置是根据手机的像素密度dpi/inDensity的值进行图片的“缩放”。

bitmap所占内存大小计算方式:

图片长度 x 图片宽度 x 一个像素点占用的字节数(因此压缩像素也是一种压缩方式)

相关文章
相关标签/搜索