Android Bitmap详解

基本概念

什么是Bitmap:

Bitmap位图包括像素以及长、宽、颜色等描述信息。长宽和像素位数是用来描述图片的,可以通过这些信息计算出图片的像素占用内存的大小。

Config:图片像素类型

包括ALPHA_8、RGB_565、ARGB_4444、ARGB_8888
A:透明度;RGB分别是Red、Green、Blue,三种原色

ARGB_8888:四个通道都是8位,每个像素占用4个字节,图片质量是最高的,但是占用的内存也是最大的;

ARGB_4444:四个通道都是4位,每个像素占用2个字节,图片的失真比较严重;

RGB_565:没有A通道,每个像素占用2个字节,图片失真小,但是没有透明度;

ALPHA_8:只有A通道,每个像素占用1个字节大大小,只有透明度,没有颜色值。

ARGB_4444失真严重,基本不用;ALPHA_8使用场景特殊,比如设置遮盖效果等;不需要设置透明度,RGB_565是个不错的选择;既要设置透明度,对图片质量要求又高,就用ARGB_8888。

CompressFormat:压缩格式

Bitmap.CompressFormat.JPEG、Bitmap.CompressFormat.PNG、Bitmap.CompressFormat.WEBP

JPEG:一种有损压缩(JPEG2000既可以有损也可以无损),".jpg"或者".jpeg"; 优点:采用了直接色,有丰富的色彩,适合存储照片和生动图像效果;缺点:有损,不适合用来存储logo、线框类图。

PNG: 一种无损压缩,".png"; 优点:支持透明、无损,主要用于小图标,透明背景等;缺点:若色彩复杂,则图片生成后文件很大;

WEBP:以WebP算法进行压缩;Google开发的新的图片格式,同时支持无损和有损压缩,使用直接色。无损压缩,相同质量的webp比PNG小大约26%;有损压缩,相同质量的webp比JPEG小25%-34% 支持动图,基本取代gif

相关方法

BitMap类

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等无损格式的图片,会忽略此项设置。

常用的静态方法:
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提供静态方法 decodeFile、decodeResource、decodeStream、decodeByteArray

这几个方法最后一个参数都可以添加一个BitmapFactory.Options 对象,表示位创建Bitmap设置一些参数,比如:

BitmapFactory.Options options = new BitmapFactory.Options();  
//inJustDecodeBounds为true,不返回bitmap,只返回这个bitmap的尺寸  
options.inJustDecodeBounds = true; 
BitmapFactory.decodeResource(getResources(), images[position], options);  

Option 参数类:
public boolean inJustDecodeBounds——如果设置为true,不获取图片,不分配内存,但会返回图片的高度宽度信息。
public int inSampleSize——图片缩放的倍数。如果设为4,则宽和高都为原来的1/4,则图是原来的1/16。
public int outWidth——获取图片的宽度值
public int outHeight——获取图片的高度值
public int inDensity——用于位图的像素压缩比
public int inTargetDensity——用于目标位图的像素压缩比(要生成的位图)
public boolean inScaled——设置为true时进行图片压缩,从inDensity到inTargetDensity。

BitmapDrawable

BitmapDrawable可以强转Bitmap
常用的构造函数:
public BitmapDrawable(Resources res) ——创建一个空的drawable。(Response用来指定初始时所用的像素密度)替代** public BitmapDrawable()** 方法(此方法不处理像素密度)
public BitmapDrawable(Resources res, Bitmap bitmap) ——创建以BitmapDrawable通过Bitmap
public BitmapDrawable(Resources res, String filepath) ——创建以BitmapDrawable通过文件路径
public BitmapDrawable(Resources res, java.io.InputStream is) ——创建以BitmapDrawable通过输入流

常用操作

将Bitmap转换成圆角

public Bitmap toRoundCorner(Bitmap bitmap, int pixels) {
        Bitmap roundCornerBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
         Canvas canvas = new Canvas(roundCornerBitmap);
         int color = 0xff424242;//int color = 0xff424242;
         Paint paint = new Paint();
         paint.setColor(color);
         //防止锯齿
         paint.setAntiAlias(true);
         Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
         RectF rectF = new RectF(rect);
         float roundPx = pixels;
         //相当于清屏
         canvas.drawARGB(0, 0, 0, 0);
         //先画了一个带圆角的矩形
         canvas.drawRoundRect(rectF, roundPx, roundPx, paint);
         paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
         //再把原来的bitmap画到现在的bitmap!!!注意这个理解
         canvas.drawBitmap(bitmap, rect, rect, paint);
         return roundCornerBitmap;
         }

获取图片的缩略图

private Bitmap getBitmapThumbnail(String filePath) {
         BitmapFactory.Options options = new BitmapFactory.Options();
         //true那么将不返回实际的bitmap对象,不给其分配内存空间但是可以得到一些解码边界信息即图片大小等信息
         options.inJustDecodeBounds = true;
         //此时rawBitmap为null
         Bitmap rawBitmap = BitmapFactory.decodeFile(filePath, options);
         if (rawBitmap == null) {
         System.out.println("此时rawBitmap为null");


         //inSampleSize表示缩略图大小为原始图片大小的几分之一,若该值为3
         //则取出的缩略图的宽和高都是原始图片的1/3,图片大小就为原始大小的1/9
         //计算sampleSize
         int sampleSize = computeSampleSize(options, 150, 200 * 200);
         //为了读到图片,必须把options.inJustDecodeBounds设回false
         options.inJustDecodeBounds = false;
         options.inSampleSize = sampleSize;
         //原图大小为625x690 90.2kB
         //测试调用computeSampleSize(options, 100, 200*100);
         //得到sampleSize=8
         //得到宽和高位79和87
         //79*8=632 87*8=696
         Bitmap thumbnailBitmap = BitmapFactory.decodeFile(filePath, options);
         //保存到SD卡方便比较
         this.compressAndSaveBitmapToSDCard(thumbnailBitmap, "15.jpg", 80);
         return thumbnailBitmap;

    }

         //参考资料:
         //http://my.csdn.net/zljk000/code/detail/18212
         //第一个参数:原本Bitmap的options
         //第二个参数:希望生成的缩略图的宽高中的较小的值
         //第三个参数:希望生成的缩量图的总像素


    public static int computeSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
         int initialSize = computeInitialSampleSize(options, minSideLength, maxNumOfPixels);
         int roundedSize;
         if (initialSize <= 8) {
             roundedSize = 1;
             while (roundedSize < initialSize) {
                 roundedSize <<= 1;

            }

        } else {
           roundedSize = (initialSize + 7) / 8 * 8;

        }
        return roundedSize;

    }



    private static int computeInitialSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
         //原始图片的宽
         double w = options.outWidth;
         //原始图片的高
         double h = options.outHeight;
         System.out.println("========== w=" + w + ",h=" + h);
         int lowerBound = (maxNumOfPixels == -1) ? 1 : (int) Math.ceil(Math
              .sqrt(w * h / maxNumOfPixels));
         int upperBound = (minSideLength == -1) ? 128 : (int) Math.min(
             Math.floor(w / minSideLength), Math.floor(h / minSideLength));
         if (upperBound < lowerBound) {
            // return the larger one when there is no overlapping zone.
            return lowerBound;
        }
        if ((maxNumOfPixels == -1) && (minSideLength == -1)) {
            return 1;
        } else if (minSideLength == -1) {
            return lowerBound;
        } else {
            return upperBound;
        }
    }

压缩且保存图片到SDCard

private void compressAndSaveBitmapToSDCard(Bitmap rawBitmap, String fileName, int quality) {
         String saveFilePaht = this.getSDCardPath() + File.separator + fileName;
         File saveFile = new File(saveFilePaht);
         if (!saveFile.exists()) {
             try {
                 saveFile.createNewFile();
                 FileOutputStream fileOutputStream = new FileOutputStream(saveFile);
                 if (fileOutputStream != null) {
                    //imageBitmap.compress(format, quality, stream);
                    //把位图的压缩信息写入到一个指定的输出流中
                    //第一个参数format为压缩的格式
                    //第二个参数quality为图像压缩比的值,0-100.0 意味着小尺寸压缩,100意味着高质量压缩
                    //第三个参数stream为输出流
                    rawBitmap.compress(Bitmap.CompressFormat.JPEG, quality, fileOutputStream);

                }
                fileOutputStream.flush();
                fileOutputStream.close();

            } catch (IOException e) {
                 e.printStackTrace();


            }

        }

    }

变换Bitmap

        // 新建立矩阵
         Matrix matrix = new Matrix();
         matrix.postScale(heightScale, widthScale);
         // 设置图片的旋转角度
         //matrix.postRotate(-30);
         // 设置图片的倾斜
         //matrix.postSkew(0.1f, 0.1f);

        Bitmap newBitmap = Bitmap.createBitmap(rawBitmap, 0, 0, rawWidth, rawWidth, matrix, true);

存储

Bitmap 2.3之前的像素存储需要的内存是在native上分配的,并且生命周期不太可控,可能需要用户自己回收。 2.3-7.1之间,Bitmap的像素存储在Dalvik的Java堆上,当然,4.4之前的甚至能在匿名共享内存上分配(Fresco采用),而8.0之后的像素内存又重新回到native上去分配,不需要用户主动回收,8.0之后图像资源的管理更加优秀,极大降低了OOM。native的内存不影响java虚拟机的OOM我们知道Java虚拟机一般是有一个上限,但是由于Android同时能运行多个APP,这个上限一般不会太高,如果没有在AndroidManifest中启用largeheap,那么Java 堆内存达到192M的时候就会崩溃,但是将Bitmap保存在native中,Bitmap的大小几乎可以使用系统可用的所有内存。不过,内存无限增长的情况下,也会导致APP崩溃,但是这种崩溃已经不是OOM崩溃了,Java虚拟机也不会捕获,按道理说,应该属于linux的OOM了


[图片上传中...(bitmap8.0以后.png-3f4ce-1551938836884-0)]

bitmap8.0以后.png

NativeAllocationRegistry是Android 8.0引入的一种辅助自动回收native内存的一种机制,当Java对象因为GC被回收后,NativeAllocationRegistry可以辅助回收Java对象所申请的native内存

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

推荐阅读更多精彩内容