作者 | ༺ IF ༻
责编 | 王晓曼
出品 | CSDN博客
Bitmap的相关使用
关于 Bitmap ,之前以为它和 Drawable 差不多,就是一种图片,直到泪水打湿了我胸前的红领巾,我决定整理一波关于 Bitmap 的姿势!
Bitmap相关的使用主要有两种:
1.给 ImageView 设置背景;
2.当做画布来使用 。
分别对应下面两个方法:
imageView.setImageBitmap(Bitmap bm);
Canvas canvas = new Canvas(Bitmap bm)
Bitmap的格式
我们知道 Bitmap 是位图,是由像素点组成的,这就涉及到两个问题:
第一:如何存储每个像素点?
第二:怎么压缩像素点?
1、存储格式
Bitmap有四种存储方式,对应Bitmap.Config中的四个常量:
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 还行,就是不可以设置透明度。
注意以下三点即可:
ARGB_4444画面惨不忍睹,被弃用;
假如对图片没有透明度要求,可以使用RGB_565,比ARGB_8888节省一半的内存开销。
2、压缩格式
我们不妨来计算一下,如果一张和手机屏幕大小一样的Bitmap图片,采用ARGB_8888格式存储需要多大的内存!
按照1024*768的屏幕大小来计算,每个像素需要32位也就是4个字节:
result = 1024*768*32B=25165824B=24MB
一张手机屏幕大小的 Bitmap 图片竟然要 24M ? 那就不奇怪我的 APP 为什么一直闪退了,只不过用 for 循环创 建了几十个用在滑动列表里面。
所以我们必须要对图片进行压缩呀,压缩格式使用枚举类 Bitmap.CompressFormat 中,有以下三种:
Bitmap创建方法
1、Bitmap.Options
想要创建一个Bitmap有很多种方法,其中很多方法都要求传入一个Bitmap.Options,它是什么呢,有什么作用呢?
这个参数的作用非常大,他可以设置 Bitmap 的采样率,通过改变图片的宽度高度和缩放比例等,以达到减少图片像素数的目的,一言以蔽之,通过设置这个参数我们可以很好的控制显示和使用 Bitmap 。实际开发过程中,可以灵活设置该值,以降低 OOM 发生的概率。
介绍几个重要的成员变量:
高度通过options.outWidth获取 宽度通过options.outHeight获取;
MIME通过options.outMineType获取。
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;
}
为了更清楚的介绍下面的知识,先补充几点:
当屏幕分辨率与图片所在文件夹所对应的分辨率不同时,会进行缩放,缩放比例是屏幕分辨率/文件夹所对应的分辨率。
从本地文件中加载图片时,不会对图片进行缩放噢。
inScald :这个参数表示,在可以缩放时,是否对当前文件进行放缩,如果设置为 false 就不放缩。设置为 true,则会根据文件夹分辨率和屏幕分辨率进行动态缩放。
inPreferredConfig :这个参数是用来设置像素的存储格式的。
关于 Options 就介绍这几个关键的字段,下面进入重头戏,创建Bitmap。
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、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)
4、创建Bitmap的总结
加载图像可以使用BitmapFactory和Bitmap.create系列方法
可以通过Options实现缩放图片,获取图片信息,配置缩放比例等功能
如果需要裁剪或者缩放图片,只能使用create系列函数
注意加载和创建bitmap事通过try catch捕捉OOM异常
常见函数
1、函数及其参数
copy(Config config,boolean isMutable)
//根据原图像创建一个副本,但可以指定副本的像素存储格式
//参数含义。
// config:像素在内存中的存储格式,但可以指定副本的像素存储格式
// boolean isMutable:新建的bitmap是否可以修改其中的像素值
extractAlpha
//主要作用是从bitmap中获取Alpha值,生成一幅只有Alpha值得图像,存储格式是ALPHA_8
getByteCount//获取bitmap的字节数
recycle://不用的bitmap必须要及时回收,以免造成oom
isRecycled//判断bitmap是否被回收,被收回不可使用会造成crash
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) {
}
});
常见问题
1、Bitmap与Canvas,View,Drawable的关系:
我们在创建一个Canvas时,可以传入一个Bitmap,Paint在Canvas上的绘制实际上就是绘制在Bitmap对象上的。
我们自定义空间所显示的View也是通过Canvas中的Bitmap来显示的。
Drawable在内存占用和绘制速度这两个非常关键的点上胜过Bitmap。
2、使用Bitmap如何造成内存溢出的?
个人认为,Bitmap 容易造成内存溢出是由于位图较大,一张屏幕大小的ARGB_8888 存储格式的图片竟然有24M,如果有几个这种量级的图片在内存中,并且没有及时回收,那会非常容易造成 OOM。
3、怎么解决或者避免Bitmap内存溢出?
对不使用的 Bitmap 一定要及时回收。
在创建 Bitmap 时使用 try catch 步骤 OOM 异常,使程序更健壮,即使发生了 OOM 也不会闪退,造成不好的使用体验。
4、Bitmap与Drawable的转换
(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;
}
(2)Bitmap转换成Drawable
Bitmap bm=Bitmap.createBitmap(xxx);
BitmapDrawable bd= new BitmapDrawable(getResource, bm);
小结
以前使用 Bitmap 全靠 CV,现在掌握了这么多知识,Bitmap 随便用都不会出现问题,妈妈再也不用担心我内存溢出,太棒了!
版权声明:本文为CSDN博主「༺IF ༻」的原创文章,遵循CC4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:
https://blog.csdn.net/weixin_43927892/article/details/106209563
☞程序员之痛:六次创业五回失败了
☞Linux 之父怒删工程师提交的补丁,称“太蠢了”网友:怼得好!
☞张一鸣是如何练就字节跳动的
☞性能超越最新序列推荐模型,华为诺亚方舟提出记忆增强的图神经网络
☞DevOps 在移动应用程序开发中扮演什么角色?
☞稳定币经济:十大稳定币简史