BitMap压缩以及二次采样

BitMap压缩以及二次采样

标签: Android


首先我们来了解一下什么是oom?

1.什么是OOM?为什么会引起OOM?

out of Memory(内存溢出)我们都知道Android系统会被每个App分配一个独立的空间,或者说分配一个Dalvik虚拟机(Dalvik是Google公司自己设计用于Android平台的虚拟机。Dalvik虚拟机是Google等厂商合作开发的Android移动设备平台的核心组成部分之一。它可以支持已转换为 .dex(即Dalvik Executable)格式的Java应用程序的运行,.dex格式是专为Dalvik设计的一种压缩格式,适合内存和处理器速度有限的系统。Dalvik 经过优化,允许在有限的内存中同时运行多个虚拟机的实例,并且[1]每一个Dalvik 应用作为一个独立的Linux 进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。)这样每个APP都可以独立运行而不相互影响!而Android对于每个 Dalvik虚拟机都会有一个最大内存限制,如果当前占用的内存加上我们申请的内存资源超过了这个限制 ,系统就会抛出OOM错误!另外,这里别和RAM混淆了,即时当前RAM中剩余的内存有1G多,但是OOM还是会发生!别把RAM(物理内存)和OOM扯到一起!另外RAM不足的话,就是杀应用了,而不是仅仅是OOM了! 而这个Dalvik中的最大内存标准,不同的机型是不一样的

这里可以获取系统分给你App多少内存

ActivityManager activityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
Log.e("HEHE","最大内存:" + activityManager.getMemoryClass());
//或者
Runtime.getRuntime().maxMemory()来获取

一、Bitmap:

Bitmap是Android系统中的图像处理的最重要类之一。用它可以获取图像文件信息,进行图像剪切、旋转、缩放等操作,并可以指定格式保存图像文件。
常用方法:
public void recycle()  // 回收位图占用的内存空间,把位图标记为Dead
public final boolean isRecycled()  //判断位图内存是否已释放
public final int getWidth() //获取位图的宽度
public final int getHeight() //获取位图的高度
public final boolean isMutable() //图片是否可修改
public int getScaledWidth(Canvas canvas) //获取指定密度转换后的图像的度
public int getScaledHeight(Canvas canvas) //获取指定密度转换后的图像的度
public boolean compress(CompressFormat format, int quality, OutputStream stream) //按指定的图片格式以及画质,将图片转换为输出流。
format:压缩图像的格式,如Bitmap.CompressFormat.PNG或 ·Bitmap.CompressFormat.JPEG
quality:画质,0-100.0表示最低画质压缩,100以最高画质压缩。对于PNG等无损格式的图片,会忽略此项设置。
stream: OutputStream中写入压缩数据。
return: 是否成功压缩到指定的流。
public static Bitmap createBitmap(Bitmap src)  //以src为原图生成不可变得新图像
public static Bitmap createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter) //以src为原图,创建新的图像,指定新图像的高宽以及是否可变。
public static Bitmap createBitmap(int width, int height, Config config) //创建指定格式、大小的位图
public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height) //以source为原图,创建新的图片,指定起始坐标以及新图像的高宽。
BitmapFactory.Option可设置参数

Option 参数类:

public boolean inJustDecodeBounds //如果设置为true,不获取图片,不分配 内存,但会返回图片的高度宽度信息。
如果将这个值置为true,那么在解码的时候将不会返回bitmap,只会返回这个bitmap 的尺寸。这个属性的目的是,如果你只想知道一个bitmap的尺寸,但又不想将其加载到内存时。这是一个非常有用的属性。
public int inSampleSize //图片缩放的倍数
这个值是一个int,当它小于1的时候,将会被当做1处理,如果大于1,那么就会按照比例(1 / inSampleSize)缩小bitmap的宽和高、降低分辨率,大于1时这个值将会被处置为2的倍数。例如,width=100,height=100,inSampleSize=2,那么就会将bitmap处理为,width=50,height=50,宽高降为1 / 2,像素数降为1 / 4。
public int outWidth //获取图片的宽度值
public int outHeight //获取图片的高度值
表示这个Bitmap的宽和高,一般和inJustDecodeBounds一起使用来获得Bitmap的宽高,但是不加载到内存。
public int inDensity //用于位图的像素压缩比
public int inTargetDensity //用于目标位图的像素压缩比(要生成的位图)
public byte[] inTempStorage  //创建临时文件,将图片存储
public boolean inScaled //设置为true时进行图片压缩,从inDensity到inTargetDensity
public boolean inDither  //如果为true,解码器尝试抖动解码
public Bitmap.Config inPreferredConfig  //设置解码器
这个值是设置色彩模式,默认值是ARGB_8888,在这个模式下,一个像素点占用4bytes空间,一般对透明度不做要求的话,一般采用RGB_565模式,这个模式下一个像素点占用2bytes。
public String outMimeType  //设置解码图像
public boolean inPurgeable //当存储Pixel的内存空间在系统内存不足时是否可以被回收
public boolean inInputShareable  //inPurgeable为true情况下才生效,是否可以共享一个InputStream
public boolean inPreferQualityOverSpeed  //为true则优先保证Bitmap质量其次是解码速度
public boolean inMutable  //配置Bitmap是否可以更改,比如:在Bitmap上隔几个像素加一条线段
public int inScreenDensity  //当前屏幕的像素密度
从资源中获取位图的方式有两种:通过BitmapDrawable或者BitmapFactory
你可以创建一个构造一个BitmapDrawable对象,比如通过流构建BitmapDrawable:
BitmapDrawable bmpMeizi = new BitmapDrawable(getAssets().open("pic_meizi.jpg"));
Bitmap mBitmap = bmpMeizi.getBitmap();
img_bg.setImageBitmap(mBitmap);

工厂方法:

public static Bitmap decodeFile(String pathName, Options opts)  //从文件读取图片
public static Bitmap decodeFile(String pathName)
public static Bitmap decodeStream(InputStream is)  //从输入流读取图片
public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts)
public static Bitmap decodeResource(Resources res, int id)  //从资源文件读取图片
public static Bitmap decodeResource(Resources res, int id, Options opts)
public static Bitmap decodeByteArray(byte[] data, int offset, int length)  //从数组读取图片
public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts)
public static Bitmap decodeFileDescriptor(FileDescriptor fd) //从文件读取文件 与decodeFile不同的是这个直接调用JNI函数进行读取 效率比较高
public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts)
首先了解一下关于Bitmap的Config的理解
A:透明度 R:红色 G:绿 B:蓝
Bitmap.Config ARGB_4444:每个像素占四位,即A=4,R=4,G=4,B=4,那么一个像素点占4+4+4+4=16位
Bitmap.Config ARGB_8888:每个像素占四位,即A=8,R=8,G=8,B=8,那么一个像素点占8+8+8+8=32位
Bitmap.Config RGB_565:每个像素占四位,即R=5,G=6,B=5,没有透明度,那么一个像素点占5+6+5=16位
Bitmap.Config ALPHA_8:每个像素占四位,只有透明度,没有颜色。

bitmap内存大小 = 图片长度 x 图片宽度 x 单位像素占用的字节数
起决定因素就是最后那个参数了,Bitmap'常见有2种编码方式:ARGB_8888和RGB_565,ARGB_8888每个像素点4个byte,RGB_565是2个byte,一般都采用ARGB_8888这种。那么常见的1080*1920的图片内存占用就是:
1920 x 1080 x 4 = 7.9M

常用的压缩方法:

1.质量压缩

private void compressQuality() {
    Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.test);
    mSrcSize = bm.getByteCount() + "byte";
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    bm.compress(Bitmap.CompressFormat.JPEG, 100, bos);
    byte[] bytes = bos.toByteArray();
    mSrcBitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
}

质量压缩不会减少图片的像素,它是在保持像素的前提下改变图片的位深及透明度,来达到压缩图片的目的,图片的长,宽,像素都不会改变,那么bitmap所占内存大小是不会变的。
我们可以看到有个参数:quality,可以调节你压缩的比例,但是还要注意一点就是,质量压缩堆png格式这种图片没有作用,因为png是无损压缩。

2.采样率压缩

private void compressSampling() {
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inSampleSize = 2;
    mSrcBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test, options);
}

采样率压缩其原理其实也是缩放bitamp的尺寸,通过调节其inSampleSize参数,比如调节为2,宽高会为原来的1/2,内存变回原来的1/4.

3.放缩法压缩

private void compressMatrix() {
    Matrix matrix = new Matrix();
    matrix.setScale(0.5f, 0.5f);
    Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.test);
    mSrcBitmap = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true);
    bm = null;
}

放缩法压缩使用的是通过矩阵对图片进行裁剪,也是通过缩放图片尺寸,来达到压缩图片的效果,和采样率的原理一样

4.RGB_565压缩

private void compressRGB565() {
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inPreferredConfig = Bitmap.Config.RGB_565;
    mSrcBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test, options);
}

这是通过压缩像素占用的内存来达到压缩的效果,一般不建议使用ARGB_4444,因为画质实在是辣鸡,如果对透明度没有要求,建议可以改成RGB_565,相比ARGB_8888将节省一半的内存开销。

5.createScaledBitmap

private void compressScaleBitmap() {
    Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.test);
    mSrcBitmap = Bitmap.createScaledBitmap(bm, 600, 900, true);
    bm = null;
}

将图片的大小压缩成用户的期望大小,来减少占用内存。

为什么要二次采样

默认情况下,bitmap每个像素点占用4个字节(ARGB_8888),比如一张3543×3503的图片差不多在内存中占用47M
安卓系统给每个应用分配的内存都是有限的,可以使用Runtime.getRuntime().maxMemory()来获取
内存有限空间,默认情况下图片存储又需要大量的空间,于是就容易产生OOM(内存溢出)

 private Bitmap getCompressBm(int id, int maxw, int maxh) {
        Bitmap bm = null;
        int iSamplesize = 1;
        //第一次采样
        BitmapFactory.Options bitmapFactoryOptions = new BitmapFactory.Options();
        //该属性设置为true只会加载图片的边框进来,并不会加载图片具体的像素点
        bitmapFactoryOptions.inJustDecodeBounds = true;
        bm = BitmapFactory.decodeResource(getResources(), id, bitmapFactoryOptions);
        Log.e("Tag", "onActivityResult: 压缩之前图片的宽:" + bitmapFactoryOptions.outWidth + "--压缩之前图片的高:"
                + bitmapFactoryOptions.outHeight + "--压缩之前图片大小:" + bitmapFactoryOptions.outWidth * bitmapFactoryOptions.outHeight * 4 / 1024  + "kb");
        int iWidth = bitmapFactoryOptions.outWidth;
        int iHeight = bitmapFactoryOptions.outHeight;
        //对缩放比例进行调整,直到宽和高符合我们要求为止
        while (iWidth > maxw|| iHeight > maxh){
           //如果宽高的任意一方的缩放比例没有达到要求,都继续增大缩放比例
            //sampleSize应该为2的n次幂,如果给sampleSize设置的数字不是2的n次幂,那么系统会就近取
            iSamplesize = iSamplesize*2;//宽高均为原图的宽高的1/2  内存约为原来的1/4
            iWidth = iWidth/iSamplesize;
            iHeight = iHeight/iSamplesize;
        }
        //二次采样开始
        //二次采样时我需要将图片加载出来显示,不能只加载图片的框架,因此inJustDecodeBounds属性要设置为false
        bitmapFactoryOptions.inJustDecodeBounds = false;
        bitmapFactoryOptions.inSampleSize = iSamplesize;
        // 设置像素颜色信息
       // bitmapFactoryOptions.inPreferredConfig = Bitmap.Config.RGB_565;
        bm = BitmapFactory.decodeResource(getResources(),id, bitmapFactoryOptions);
//默认的图片格式是Bitmap.Config.ARGB_8888
        Log.e("Tag", "onActivityResult: 图片的宽:" + bm.getWidth() + "--图片的高:"
                + bm.getHeight() + "--图片大小:" + bm.getWidth() * bm.getHeight() * 4 / 1024  + "kb");
        return bm;//返回压缩后的照片
    }

再一次感谢您花费时间阅读这份文章,祝您在这里记录、阅读、分享愉快!

作者 @windrain_boy
2017年06月06日

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容