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内存

推荐阅读更多精彩内容