Drawable与Bitmap对比

定义对比:
  • Bitmap:称作位图,一般的位图的文件格式扩展名为.bmp,当然编码器也有很多,RGB565,RGB8888,作为一种追个像素的显示对象,执行效率高,但是存储效率低,可以理解成一种存储对象
  • Drawable:Android下的通用的图片形象,它可以装载常用格式的图像,比如GIF,PNG,JPG,BMP,提供一些高级的可视化方法。
属性对比:

属性名

Bitmap

Drawable

显示清晰度

相同

相同

占用内存



支持缩放



支持色相色差的调整



支持旋转



支持透明色



绘制速度



支持像素操作



绘图的便利性比较:
  • 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;
}