zoukankan      html  css  js  c++  java
  • Android Bitmap史上最详细全解

    深度解剖Bitmap

    一.Bitmap的相关使用

    关于Bitmap,之前以为它和Drawable差不多,就是一种图片,直到泪水打湿了我胸前的红领巾,我决定整理一波关于Bitmap的姿势!
    在这里插入图片描述
    Bitmap相关的使用主要有两种:

    • 1.给ImageView设置背景
    • 2.当做画布来使用

    分别对应下面两个方法

    imageView.setImageBitmap(Bitmap bm);
     Canvas canvas = new Canvas(Bitmap bm)

    二.Bitmap的格式

    我们知道Bitmap是位图,是由像素点组成的,这就涉及到两个问题,

    • 第一:如何存储每个像素点?
    • 第二:怎么压缩像素点?
      在这里插入图片描述

    2.1 存储格式

    Bitmap有四种存储方式,对应Bitmap.Config中的四个常量

    • ALPHA_8:只存储透明度,不存储色值,1个像素点占1个字节
    • ARGB_4444:ARGB各用4位存储,1个像素点16位占2个字节
    • ARGB_8888:ARGB各用8位存储,1个像素点32位占4个字节
    • RGB_565:只存储色值,不存储透明度,默认不透明,RGB分别占5,6,5位,一个像素点占用16位2个字节。

    一般情况下,我们不会使用ALPHA_8,他只存储透明度,没啥用处。对于ARGB_4444,它的画质又太感人了,ARGB_8888画质高但是占内存,RGB_565还行,就是不可以设置透明度。
    注意以下三点即可:

    • 1.一般情况下用ARGB_8888格式存储Bitmap
    • 2.ARGB_4444画面惨不忍睹,被弃用
    • 3.假如对图片没有透明度要求,可以使用RGB_565,比ARGB_8888节省一半的内存开销

    2.2 压缩格式

    我们不妨来计算一下,如果一张和手机屏幕大小一样的Bitmap图片,采用ARGB_8888格式存储需要多大的内存!

    按照1024*768的屏幕大小来计算,每个像素需要32位也就是4个字节,

    result = 1024*768*32B=25165824B=24MB

    在这里插入图片描述

    一张手机屏幕大小的Bitmap图片竟然要24M? 那就不奇怪我的app为什么一直闪退了,只不过用for循环创建了几十个用在滑动列表里面。

    所以我们必须要对图片进行压缩呀,压缩格式使用枚举类Bitmap.CompressFormat中,有以下三种:

    Bitmap.CompressFormat.JPEG:采用JPEG压缩算法,是一种有损压缩格式,会在压缩过程中改变图像原本质量,画质越差,对原来的图片质量损伤越大,但是得到的文件比较小,而且JPEG不支持透明度,当遇到透明度像素时,会以黑色背景填充。

    Bitmap.CompressFormat.PNG:采用PNG算法,是一种支持透明度的无损压缩格式。

    Bitmap.CompressFormat.WEBP:WEBP是一种同时提供了有损压缩和无损压缩的图片文件格式,在14<=api<=17时,WEBP是一种有损压缩格式,而且不支持透明度,在api18以后WEBP是一种无损压缩格式,而且支持透明度,有损压缩时,在质量相同的情况下,WEBP格式的图片体积比JPEG小40%,但是编码时间比JPEG长8倍。在无损压缩时,无损的WEBP图片比PNG压缩小26%,但是WEBP的压缩时间是PNG格式压缩时间的5倍。

    三.Bitmap创建方法

    3.1 Bitmap.Options

    想要创建一个Bitmap有很多种方法,其中很多方法都要求传入一个Bitmap.Options,它是什么呢,有什么作用呢?

    在这里插入图片描述
    这个参数的作用非常大,他可以设置Bitmap的采样率,通过改变图片的宽度高度和缩放比例等,以达到减少图片像素数的目的,一言以蔽之,通过设置这个参数我们可以很好的控制显示和使用Bitmap。实际开发过程中,可以灵活设置该值,以降低OOM发生的概率。

    介绍几个重要的成员变量

    • inJustDecodeBounds:boolean类型,设为true时,无需要把图片加载入内存就可以获取图片的高度,宽度和图片的MIME类型。
      高度通过options.outWidth获取 宽度通过options.outHeight获取
      MIME通过options.outMineType获取

    • inSampleSize:这个字段表示采样率,打个比方说,设置为4,则是从原本图片的四个像素中取一个像素作为结果返回。其余的都被丢弃。可见,采样率越大,图片越小,失真越严重。 如何计算采样率呢?看一下这段代码你就会明白

    public int getSampleSize(BitmapFactory.Options options , int dstWidth,int dstHeight){
            //dstWidth:表示目前ImageView的宽度
            //dstHeight:表示目标ImageView的高度
            //option中获取bitmap图片的信息
            int  rawWidth = options.outWidth;
            int  rawHeight = options.outHeight;
            int sampleSize=1;
            if(rawWidth>dstWidth||rawHeight>dstHeight){
                float ratioHeight = (float) (rawHeight/dstHeight);
                float ratioWidth = (float) (rawWidth/dstWidth);
                sampleSize = (int) Math.min(rawHeight, ratioWidth);
            }
            return sampleSize;
        }

    为了更清楚的介绍下面的知识,先补充几点:

    1. 不同名称的资源文件夹是为了适配不同的屏幕分辨率的,当屏幕分辨率与文件所在资源文件夹对应的分辨率相等时,直接使用图片,不需要进行放缩。
    2. 当屏幕分辨率与图片所在文件夹所对应的分辨率不同时,会进行缩放,缩放比例是屏幕分辨率/文件夹所对应的分辨率。
    3. 从本地文件中加载图片时,不会对图片进行缩放噢。

    inScald:这个参数表示,在可以缩放时,是否对当前文件进行放缩,如果设置为false就不放缩。设置为true,则会根据文件夹分辨率和屏幕分辨率进行动态缩放。

    inPreferredConfig:这个参数是用来设置像素的存储格式的。

    关于Options就介绍这几个关键的字段,下面进入重头戏,创建Bitmap。

    在这里插入图片描述

    3.2 BitmapFactory

    BitmapFactory提供了多种创建bitmap的静态方法

    //从资源文件中通过id加载bitmap
    //Resources res:资源文件,可以context.getResources()获得
    //id:资源文件的id,如R.drawable.xxx
    public static Bitmap decodeResources(Resources res,int id)
    //第二种只是第一种的重载方法,多了个Options参数
    public static Bitmap decodeResources(Resources res,int id,Options opt)
    //传入文件路径加载,比如加载sd卡中的文件
    //pathName:文件的全路径名
    public static Bitmap decodeFile(String pathName);
    public static Bitmap decodeFile(String pathName,Options opt);
    //从byte数组中加载
    //offset:对应data数组的起始下标
    //length:截取的data数组的长度
    public static Bitmap decodeByteArray(byte[] data,int offset , int length);
    public static Bitmap decodeByteArray(byte[] data,int offset , int length,Options opt);
    //从输入流中加载图片
    //InputStream is:输入流
    //Rect outPadding:用于返回矩形的内边距
    public static Bitmap decodeStream(InputStream is);
    public static Bitmap decodeStream(InputStream is,Rect outPadding,Options opt);
    //FileDescriptor :包含解码位图的数据文件的路径
    //通过该方式从路径加载bitmap比decodeFile更节省内存,原因不解释了。
    public static Bitmap decodeFileDescriptor(FileDescriptor fd);
    public static Bitmap decodeFileDescriptor(FileDescriptor fd,Rect outPadding,Options opt);

    平时用这些函数都是糊里糊涂的,今天整理了一遍发现其实有规律可寻,也更加清楚了。

    3.3 Bitmap静态方法

    //width和height是长和宽单位px,config是存储格式
    static Bitmap createBitmap(int width , int height Bitmap.Config config)
    // 根据一幅图像创建一份一模一样的实例
    static Bitmap createBitmap(Bitmap bm)
    //截取一幅bitmap,起点是(x,y),width和height分别对应宽高
    static Bitmap createBitmap(Bitmap bm,int x,int y,int width,int height)
    //比上面的裁剪函数多了两个参数,Matrix:给裁剪后的图像添加矩阵 boolean filter:是否给图像添加滤波效果
    static Bitmap createBitmap(Bitmap bm,int x,int y,int width,int height,Matrix m,boolean filter);
    //用于缩放bitmap,dstWidth和dstHeight分别是目标宽高
    createScaledBitmap(Bitmap bm,int dstWidth,int dstHeight,boolean filter)

    3.4 创建Bitmap的总结

    1.加载图像可以使用BitmapFactoryBitmap.create系列方法
    2.可以通过Options实现缩放图片,获取图片信息,配置缩放比例等功能
    3.如果需要裁剪或者缩放图片,只能使用create系列函数
    4.注意加载和创建bitmap事通过try catch捕捉OOM异常
    在这里插入图片描述

    四.常见函数

    4.1 函数及其参数

    copy(Config config,boolean isMutable)
    //根据原图像创建一个副本,但可以指定副本的像素存储格式
    //参数含义。
    //  config:像素在内存中的存储格式,但可以指定副本的像素存储格式
    //  boolean isMutable:新建的bitmap是否可以修改其中的像素值
    extractAlpha()
    //主要作用是从bitmap中获取Alpha值,生成一幅只有Alpha值得图像,存储格式是ALPHA_8
    getByteCount()//获取bitmap的字节数
    recycle()://不用的bitmap必须要及时回收,以免造成oom
    isRecycled()//判断bitmap是否被回收,被收回不可使用会造成crash

    4.2 综合案例演示

    String items[] = {"copy","extractAlpha 1","extractAlpha 2","bitmap大小","recycle","isRecycled()"};
            ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item,items);
            adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
            spinner.setAdapter(adapter);
            spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
                @Override
                public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                    switch (position){
                        case 0:
                            //copy
                            Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.photo);
                            Bitmap copy = bm.copy(Bitmap.Config.ARGB_8888, true);
                            imageView.setImageBitmap(copy);
                            bm.recycle();
                            break;
                        case 1:
                            //extractAlpha 不带参数
                            Bitmap bp = BitmapFactory.decodeResource(getResources(), R.drawable.photo);
                            Bitmap alpha = bp.extractAlpha();
                            imageView.setImageBitmap(alpha);
                            bp.recycle();
                            break;
                        case 2:
                            //extractAlpha 带参数
                            Bitmap bp1 = BitmapFactory.decodeResource(getResources(), R.drawable.photo);
                            Paint paint = new Paint();
                            BlurMaskFilter blurMaskFilter = new BlurMaskFilter(6, BlurMaskFilter.Blur.NORMAL);
                            paint.setMaskFilter(blurMaskFilter);
                            int[] offsetXY = new int[2];
                            Bitmap alpha1 = bp1.extractAlpha(paint, offsetXY);
                            imageView.setImageBitmap(alpha1);
                            break;
                        case 3:
                            //获取bitmap大小
                            Bitmap b = BitmapFactory.decodeResource(getResources(), R.drawable.photo);
                            Toast.makeText(getApplicationContext(), "图片大小为:"+b.getByteCount()+"字节", Toast.LENGTH_SHORT).show();
                            break;
                        case 4:
                            //回收bitmap
                            Bitmap b1 = BitmapFactory.decodeResource(getResources(), R.drawable.photo);
                            b1.recycle();
                            if(b1.isRecycled()){
                                Toast.makeText(getApplicationContext(), "已经被回收", Toast.LENGTH_SHORT).show();
                            }
                            //isRecycled()判断是否被回收
                            break;
                    }
    
                }
    
                @Override
                public void onNothingSelected(AdapterView<?> parent) {
    
                }
            });

    五.常见问题

    5.1 Bitmap与Canvas,View,Drawable的关系

    1.我们在创建一个Canvas时,可以传入一个BitmapPaintCanvas上的绘制实际上就是绘制在Bitmap对象上的。

    2.我们自定义空间所显示的View也是通过Canvas中的Bitmap来显示的。

    3.Drawable在内存占用和绘制速度这两个非常关键的点上胜过Bitmap.
    在这里插入图片描述

    5.2 使用Bitmap如何造成内存溢出的?

    个人认为,Bitmap容易造成内存溢出是由于位图较大,一张屏幕大小的ARGB_8888存储格式的图片竟然有24M,如果有几个这种量级的图片在内存中,并且没有及时回收,那会非常容易造成OOM

    5.3怎么解决或者避免Bitmap内存溢出?

    1.我们可以对位图进行压缩,压缩手段有PNG,JPEG,WEBP
    2.对不使用的Bitmap一定要及时回收。
    3.在创建Bitmap时使用try catch步骤OOM异常,使程序更健壮,即使发生了OOM也不会闪退,造成不好的使用体验.

    5.4Bitmap与Drawable的转换

    5.4.1 Drawable转换成Bitmap
    public static Bitmap drawableToBitmap(Drawable drawable) {  
            // 取 drawable 的长宽  
            int w = drawable.getIntrinsicWidth();  
            int h = drawable.getIntrinsicHeight();  
      
            // 取 drawable 的颜色格式  
            Bitmap.Config config = drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888  
                    : Bitmap.Config.RGB_565;  
            // 建立对应 bitmap  
            Bitmap bitmap = Bitmap.createBitmap(w, h, config);  
            // 建立对应 bitmap 的画布  
            Canvas canvas = new Canvas(bitmap);  
            drawable.setBounds(0, 0, w, h);  
            // 把 drawable 内容画到画布中  
            drawable.draw(canvas);  
            return bitmap;  
        }
    5.4.2 Bitmap转换成Drawable
    Bitmap bm=Bitmap.createBitmap(xxx); 
    BitmapDrawable bd= new BitmapDrawable(getResource(), bm);

    六.小结

    以前使用bitmap全靠cv,现在掌握了这么多知识,bitmap随便用都不会出现问题,妈妈再也不用担心我内存溢出,太棒了!

    相关阅读:

    抖音dou+

    抖音养号

    抖音怎么合拍

  • 相关阅读:
    第二类斯特林数学习笔记
    [ZJOI2017]树状数组
    「LibreOJ Round #6」花火
    [Ynoi2016]这是我自己的发明 莫队
    codeforces706E
    扩展CRT
    PKUSC2018游记
    「PKUWC 2018」Minimax
    「SHOI2015」(LOJ2038)超能粒子炮・改
    Codeforces712E
  • 原文地址:https://www.cnblogs.com/hzcya1995/p/13309121.html
Copyright © 2011-2022 走看看