Drawable与Bitmap详解

Drawable与Bitmap对比
定义对比:
  • Bitmap:称作位图,一般的位图的文件格式扩展名为.bmp,当然编码器也有很多,RGB565,RGB8888,作为一种追个像素的显示对象,执行效率高,但是存储效率低,可以理解成一种存储对象
  • Drawable:Android下的通用的图片形象,它可以装载常用格式的图像,比如GIF,PNG,JPG,BMP,提供一些高级的可视化方法。
属性对比:
属性名BitmapDrawable
显示清晰度相同相同
占用内存
支持缩放
支持色相色差的调整
支持旋转
支持透明色
绘制速度
支持像素操作
绘图的便利性比较:
  • Drawable有很多的派生类,可以实现渐变,层叠的效果
  • Bitmap一般用来做空白画布来进行绘图
简易性比较:
  • Drawable是自带画笔的,更改画笔的属性是可以直接更新到ShapeDrawable上的,但是Drawable子类使用Canvas并不方便,只能用来完成一些固有的功能,如果要使用Drawable来绘图,可以自定义Drawable
  • Bitmap上可以按照之前绘制那样设置画笔,然后进行绘制
使用方式对比:
  • Bitmap主要靠在View中通过Canvas.drawBitmap()函数画出来
  • Drawable可以在View中通过Drawable.draw(Canvas canvas)函数画出来,也可以通过setImageBackground()、setBackgroundDrawable()来设置Drawable资源
总结:
  • Bitmap在占用内存和绘制速度上不如Drawable的优势
  • Bitmap绘图方便,Drawable调用paint方便调用canvas不方便
  • Drawable有一些子类,可以方便完成一些绘图功能
  • 自定义View的使用:
    • Bitmap只有一种情况
      • 在View中需要自己生成图像时,才会使用Bitmap绘图,绘图后的结果保存在这个Bitmap中,比如根据Bitmap生成它的倒影,在使用Xfermode来融合倒转的图片原图与渐变的图片的时候,就需要根据图片的大小来生成一张同样大小的渐变图片,必须使用Bitmap
    • 当使用Drawable的子类能完成一些固有的功能的时候,优先选择Drawable
    • 当需要使用Drawable的setImageBackground(), setBackgroundDrawable()的设置drawable资源函数的时候,只能选择Draawable
    • 当在自定义View中在指定位置显示图像功能的时候,既可以使用Drawable,也可以使用Bitmap
    • 除drawable和bitmap以外的地方,都可以使用自定义View来实现
Bitmap
  • 之前绘制图像的时候,Canvas中保存着一个Bitmap对象,我们调用Canvas的各种绘制函数的时候,最终时绘制到其保存的Bitmap上,在onDraw(Canvas canvas)函数中,View对应一个Bitmap对象,canvas是通过这个Bitmap创建出来的,所以可以通过直接调用Canvas的函数进行绘制
Bitmap概述
  • Bitmap在绘图中的使用方式:
    • 转换为BitmapDrawable对象使用
    • 当作画布来使用

1.转换为BitmapDrawable对象使用

//先转换为BitmapDrawable对象, 再将其用作资源背景
Bitmap bitmap = BitmapFactory.decodeResource(res, R.drawable.picture);
BitmapDrawable bmp = new BitmapDrawable(bitmap);
image.setImagebackground(bmp);

2.当作画布使用

//方式一:使用默认的画布, 这里面View对应一个默认的Bitmap对象,canvas已经默认使用这个bitmap创建出来,最终绘制在这个Bitmap上
public void onDraw(Canvas canvas){
    ...
    canvas.drawXXX();
}


//方式二:自建画布, 有的时候需要一块空白的画布进行加水印等操作,就需要自己创建画布
Bitmap bitmap = Bitmap.createBitmap(200, 100, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(Color.YELLOW);
Bitmap格式
如何存储像素点:
  • 一张位图所占用的内存:图片长度(px) x 图片宽度(px)x 一个像素点所占的字节数

  • 在Android中,存储一个像素点所占用的字节数是使用枚举类型Bitmap.Config中的各个参数来表示的参数如下:

    • ALPHA_8:表示8位Alpha位图,即A=8,不存储其他颜色,一个像素点占用1字节,只有透明度没有颜色
    • ARGB_4444:表示16位ARGB图,A、R、G、B各占用4位,一个像素点占4+4+4+4=16位,2字节
    • ARGB_8888:表示32位ARGB图,A、R、G、B各占用8位,一个像素点占8+8+8+8=32位,4字节
    • RGB_565:表示16位RGB位图,即R占5位,G占6位,B占5位,没有透明度,一个像素点占5+6+5 = 16位,2字节
  • 每个色值所占的位数越大,颜色越艳丽:

    • 取值数越多,可以表示的颜色就越多,颜色也就越艳丽
注意
  • 一般使用ARGB_8888的格式来存储Bitmap
  • 由于ARGB_4444格式的画质太糟糕,在API13中遗弃
  • 假如对图片的透明度没有要求,可以改成RGB_565的格式,相比于占用内存最大的ARGB_8888可以节省一半的内存开销
计算Bitmap所占内存的大小:
  • 内存中存储的Bitmap对象与文件中存储 的Bitmap图片不是一个概念,文件中存储的Bitmap图像是经过图片压缩算法压缩过的,而内存中存储的Bitmap对象是通过BitmapFactory加载或者Bitmap.createBitmap()创建的,保存 于内存中,具有明确的宽和高,所以内存中的bitmap大小的计算:Bitmap.getWidth() * Bitmap.getHeight() * 每个像素所占的内存大小
  • OOM的原因:我使用的手机是2340x1080的分辨率,所以创建一个整个屏幕的Bitmap画布,假设以ARGB_8888格式保存,大致需要2340* 1080* 32B = 80870400B = 77MB,如果循环创建这么大的画布,很容易造成OOM的发生
相关像素点之间可否进行压缩:
  • 如果想要将Bitmap存储到硬盘上,一定存在压缩问题,在Android中压缩格式使用枚举类Bitmap.CompressFormat中的成员变量表示
    • Bitmap.CompressFormat.JPEG
    • Bitmap.CompressFormat.PNG
    • Bitmap.CompressFormat.WEBP
创建Bitmap
BitmapFactory
  • BitmapFactory用于从各种资源,文件,数据流和字节数组中创建Bitmap对象,BitmapFactory是一个工具类,提供了大量的函数,可以用于从不同的数据源中解析、创建Bitmap对象
public static Bitmap decodeResource(Resource res, int id);
public static Bitmap decodeResource(Resource res, int id, Options opts)
public static Bitmap decodeFile(String pathName);
public static Bitmap decodeFile(String pathName, 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);
public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts);
public static Bitmap decodeStream(InputStream is);
public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts);
public static Bitmap decodeResourceStream(Resources res, TypeValue value, InputStream is, Rect padding, Options opts);
  • BitmapFactory可以从资源,文件,字节数组、FileDescriptor和InputStream数据流解析出对应的Bitmap对象,如果解析不出来,则返回null,而且每个函数都有两个实现,区别可以很明显的看出
decodeResource(Resources res, int id)
//从资源中解码一张位图,主要以R.drawable.xxx的形式从本地资源中加载
//res:包含图像数据的资源对象,一般通过Context.getResource()函数获得
id:包含图像数据的资源id
public static Bitmap decodeResource(Resource res, int id);

eg:

Bitmap bitmap = Bitmap.decodeRespurce(getResource(), R.drawable.xxx);
decodeFile(String pathName)
//主要通过文件路径来加载图片
//pathName:解码文件的全路径名,必须是路径全名
public static Bitmap decodeFile(String pathName);

eg:

String filename = "/data/dataa/demo.jpg"
Bitmap bitmap = BitmapFactory.decodeFile(filename);
decodeByteArray(byte[] data, int offset, int length)
//根据byte数组 解析出来Bitmap
//data:压缩图像数据的字节数组
//offset:图像数据偏移量,用于解码器定位从哪里开始解析
//length:字节数,从偏移量开始,指定取多少字节进行解析

  • 使用步骤:
    • 开启线程去获取网络图片
    • 网络返回InputStream
    • 把InputStream转换成byte[]
    • 解析:Bitmap bm = BitmapFactory.decodeByteArray(byte,0 , byte.length)
      eg:
new Thread(new Runnable(){
    @Override
    public void run(){
        try{
            byte[] data = getImage(path);
            int length = data.length;
            final Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, length);
            ....
        }catch(Exception e){
            e.printStackTrace();
        }
   }
}).start()
注意:
  • 请求网络必须在子线程中
  • 在子线程中不能对UI进行更新,可以使用View.post()函数来更新UI
public static byte[] readStream(InputStream in) throws Exception{
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    byte[] buffer = new byte[1024];
    int len = -1;
    while((len = in,read(buffer)) !=-1){
        outputStream.write(buffer, 0, len)
    }
    outputStream.close();
    in.close;
    //因为data字节数组是把输入流转换为字节内存输出流的字节数组格式,如果不进行outputStream进行转换,则返回结果会一直为null
    return outputStreamm.toByteArray();
}


public static byte[] getImage(String path) throws Exception{
    URL url = new URL(path);
    HttpURLConnection httpURLconnection = (HttpURLConnection)url.openConnection();
    httpURLconnection.setRequestMethod("GET");
    httpURLconnection.setReadTimeout(1000);
    InputStream in = null;
    if(httURLconnection.getResponseCode() == 200){
        in = httpURLconnection.getInputStream();
        byte[] result = readStream(in);
        in.close();
        return result;
    }
    return null;
}
decodeFileDescriptor(FileDescriptor fd)
//fd:包含解码位图数据的文件路径
//outPadding:用于返回矩形的内边距,如果bitmap没有解析成功,则返回(-1, -1, -1, -1),如果不需要,可以传入null, 这个参数一般不使用
public static Bitmap decodeFileDescriptor(FileDescriptor fd);
public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts);

eg:

String path = "/data/data/demo.jpg";
FileInputStream is = new FileInputStream(path);
bitmap = BitmapFactory.decodeFileDescriptor(is.getFD());
if(bitmap==null){
    ....
}
  • 使用decodeFileDescriptor()比使用decodeFile()更加节省内存
  • 原因:
    • 首先看一些decodeFileDescriptor()的源码:
      • 可以看出时调用Native里面的函数,被封装在SO里,直接从native中返回bitmap
public static Bitmap decodeFileDescriptor(FIleDescriptor fd, Rect outPadding, Options opts){
    Bitmap bitmap = nativeDecodeFileDescriptor(fd, outPadding, opts);
    if(bitmap == nnull && opts != null && opts.inBitmap != null){
        throw new IllegalArgmentException("...");
    }
    return finishDecode(bitmap, outPadding,opts);
}
  • 而decodeFile()函数的源码如下:
public static Bitmap decodeFile(String pathName, Options opts) {
    validate(opts);
    Bitmap bm = null;
    InputStream stream = null;
    try {
        stream = new FileInputStream(pathName);
        bm = decodeStream(stream, null, opts);
    } catch (Exception e) {
        /*  do nothing.
            If the exception happened on open, bm will be null.
        */
        Log.e("BitmapFactory", "Unable to decode stream: " + e);
    } finally {
        if (stream != null) {
            try {
                stream.close();
            } catch (IOException e) {
                // do nothing here
            }
        }
    }
    return bm;
}





@Nullable
public static Bitmap decodeStream(@Nullable InputStream is, @Nullable Rect outPadding,
        @Nullable Options opts) {
    if(!is.markSupported()){
        is = new BufferedInputSttream(is, 16 * 1024);
    }
    is.mark(1024);
    Bitmap bmp;
    byte[] tempStorage = null;
    if(opts != null){
        tempStorage =  opts.inTempStorage;
    }
    if(tempStorage == null){
        tempStorage = new byte[16 * 1024];
    }
    bmp = nativeDecodeStream(is, tempStorage, outPadding, opts);
    return finishDecode(bmp, outPadding, opts);
}

  • 可以从decodeStream的源码中看出在调用nativeDecodeDtream()之前,会申请两次内存, 就会增加OOM的风险:
is = new BufferedInnputStream(is, 16 * 1024);
tempStorage = new byte[16 * 1024];
decodeStream()
//is:用于解码位图的原始数据输入流
//outPadding:与decodeFileDescriptor中的 outPadding参数含义相同,用于返回矩形的内边距,如果没有解析成功,则返回(-1, -1, -1,-1),这个参数一般不使用,可以传入null
//一般用来从网络上获取图片
public static Bitmap decodeStream(InputStream is);
public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts);

eg:

new Thread(new Runnable(){
    @Override
    public void run(){
        try{
            InputStream inputStream = getImage(path);
            final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
            ....
        }catch(Exception e){
            e.printStackTrace();
        }
   }
}).start()


//通过getImmage()函数返回一个InputStream对象
public static byte[] getImage(String path) throws Exception{
    URL url = new URL(path);
    HttpURLConnection httpURLconnection = (HttpURLConnection)url.openConnection();
    httpURLconnection.setRequestMethod("GET");
    httpURLconnection.setReadTimeout(1000);
    if(httURLconnection.getResponseCode() == 200){
        return httpURLconnection.getInputStream();
    }
    return null;
}
  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wjxbless

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值