引言
Bitmap是Android系统中图像处理的重要类之一,通过Bitmap我们可以获取图片的信息,之后可以对图片进行缩放,裁剪等操作。我们可以把他看作一个画架,先把画放到上面,然后进行一些处理,比如获得一些属性,做旋转切割等操作。总之,Bitmap为我们提供了一种像File操作本地文件一样操作图像文件的方式。
官方文档:Bitmap
1. Bitmap加载方式
BitmapFactory
Bitmap的构造方法是私有的,外面不能实例化,只能通过JNI实例化,所以提供了接口BitmapFactory,它可以创建位图对象从各种来源,包括文件、流或字节数组,提供了这些方法,大部分都是decodeXxx。主要用到以下这几种:
- decodeByteArray() ——字节数组
- decodeFile() ————-文件路径
- decodeResource() ——资源ID
- decodeStream() ———流
2.Bitmap常用方法与使用
普通方法
- public boolean compress (Bitmap.CompressFormat format, int quality,OutputStream stream)
将位图的压缩到指定的OutputStream,可以理解成将Bitmap保存到文件中!
-format:格式,PNG,JPG等;
-quality:压缩质量,0-100,0表示最低画质压缩,100最大质量(PNG无损,会忽略品质设定)
-stream:输出流
返回值代表是否成功压缩到指定流! - void recycle():回收位图占用的内存空间,把位图标记为Dead
- boolean isRecycled():判断位图内存是否已释放
- int getWidth():获取位图的宽度
- int getHeight():获取位图的高度
- boolean isMutable():图片是否可修改
- int getScaledWidth(Canvas canvas):获取指定密度转换后的图像的宽度
- int getScaledHeight(Canvas canvas):获取指定密度转换后的图像的高度
静态方法:
- Bitmap createBitmap(Bitmap src):以src为原图生成不可变得新图像
- Bitmap createScaledBitmap(Bitmap src, int dstWidth,int dstHeight, boolean filter):以src为原图,创建新的图像,指定新图像的高宽以及是否变。
- Bitmap createBitmap(int width, int height, Config config):创建指定格式、大小的位图
- Bitmap createBitmap(Bitmap source, int x, int y, int width, int height)以source为原图,创建新的图片,指定起始坐标以及新图像的高宽。
- public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height,Matrix m, boolean filter)
方法使用
//直接读取本地资源(常用)
Bitmap bitmap =BitmapFactory.decodeResource(getResource(),R.xxx.xx,null);
//通过资源ID
private Bitmap getBitmapFromResource(Resources res, int resId) {
return BitmapFactory.decodeResource(res, resId);
}
//文件
private Bitmap getBitmapFromFile(String pathName) {
return BitmapFactory.decodeFile(pathName);
}
//字节数组
public Bitmap Bytes2Bimap(byte[] b) {
if (b.length != 0) {
return BitmapFactory.decodeByteArray(b, 0, b.length);
} else {
return null;
}
}
//输入流
private Bitmap getBitmapFromStream(InputStream inputStream) {
return BitmapFactory.decodeStream(inputStream);
}
3.加载Bitmap
高效的加载Bitmap可以防止内存溢出,节省内存开销,使应用跑得更加流畅。主要是通过静态内部类:BitmapFacotry.Options来进行设置decode时的选项的。我们对这里的某些参数的值进行设置,比如inJustDecodeBounds设置为true避免OOM(内存溢出)等等。
常用设置参数如下:
- boolean inJustDecodeBounds——如果设置为true,不获取图片,不分配内存,
但会返回图片的高宽度信息。
- int inSampleSize——图片缩放的倍数,采样率。如果设为4,则宽和高都为原来的1/4,则
图是原来的1/16。
- int outWidth——获取图片的宽度值
- int outHeight——获取图片的高度值
- int inDensity——用于位图的像素压缩比
- int inTargetDensity——用于目标位图的像素压缩比(要生成的位图)
- boolean inScaled——设置为true时进行图片压缩,从inDensity到inTargetDensity。
4.Bitmap引起的OOM问题
1.什么是OOM?为什么会引起OOM?
Out Of Memory(内存溢出),我们都知道Android系统会为每个APP分配一个独立的工作空间,或者说分配一个单独的Dalvik虚拟机,这样每个APP都可以独立运行而不相互影响!而Android对于每个Dalvik虚拟机都会有一个最大内存限制,如果当前占用的内存加上我们申请的内存资源超过了这个限制,系统就会抛出OOM错误!另外,这里别和RAM混淆了,即时当前RAM中剩余的内存有1G多,但是OOM还是会发生!别把RAM(物理内存)和OOM扯到一起!另外RAM不足的话,就是杀应用了,而不是仅仅是OOM了!
而这个Dalvik(虚拟机)中的最大内存标准,不同的机型是不一样的,可以调用:
ActivityManager activityManager =
(ActivityManager)context.getSystemService(Context.ACTIVITY_SERVIC
Log.e("HEHE","最大内存:" + activityManager.getMemoryClass());
2.避免Bitmap引起的OOM技巧小结
1.采用低内存占用量的编码方式
上面说了BitmapFactory.Options这个类,我们可以设置下其中的
inPreferredConfig属性,
默认是Bitmap.Config.ARGB_8888,我们可以修改成Bitmap.Config.ARGB_4444
Bitmap.Config ARGB_4444:每个像素占四位,即A=4,R=4,G=4,B=4,那么一个像素点占4+4+4+4=16位
Bitmap.Config ARGB_8888:每个像素占八位,即A=8,R=8,G=8,B=8,那么一个像素点占8+8+8+8=32位
默认使用ARGB_8888,即一个像素占4个字节!
2.图片压缩
同样是BitmapFactory.Options,我们通过inSampleSize设置缩放倍数,比如写2,即长宽变为原来的1/2,图片就是原来的1/4,如果不进行缩放的话设置为1即可!但是不能一味的压缩,毕竟这个值太小的话,图片会很模糊,而且要避免图片的拉伸变形,所以需要我们在程序中动态的计算,这个inSampleSize的合适值,而Options中又有这样一个方法:inJustDecodeBounds,将
该参数设置为true后,decodeFiel并不会分配内存空间,但是可以计算出原始图片的长宽,调用
options.outWidth/outHeight获取出图片的宽高,然后通过一定的算法,即可得到适合的inSampleSize.后面会贴上代码实例。
3.及时回收图像
如果引用了大量的Bitmap对象,而应用又不需要同时显示所有图片。可以将暂时不用到
的Bitmap对象及时回收掉。对于一些明确知道图片使用情况的场景可以主动recycle回收,比如引导页的图片,使用完就recycle,帧动画,加载一张,画一张,释放一张!使用时加载,不显示时直接置null或recycle!比如:imageView.setImageResource(0);不过某些情况下会出现特定图片反复加载,释放,再加载等,低效率的事情..
4.采取其他缓存方式
LruCache 缓存方式和DiskLruCache方式这里就不细说了,有兴趣可自行查阅相关资料。
5. 加载优化实战
下面这个是我之前项目中用到的一个加载网络图片的部分代码,爬取网上资源加载在一个RecyclerView中刷新时经常会出现OOM现象,进行一些简单压缩比和像素的调整后,整个应用运行效率提高也没再出现OOM。
//自定义加载网络图片类
public Bitmap getHttpBitmap(String uri){
URL myfileURL;
Bitmap bitmap=null;
try {
myfileURL = new URL(uri);
//获得连接
HttpURLConnection conn=(HttpURLConnection)myfileURL.openConnection();
//设置超时时间为3000毫秒,conn.setConnectionTiem(0);表示没有时间限制
conn.setConnectTimeout(2000);
//连接设置获得数据流
conn.setDoInput(true);
//不使用缓存
conn.setUseCaches(false);
//这句可有可无,没有影响
//conn.connect();
//得到数据流
InputStream is = conn.getInputStream();
BitmapFactory.Options options = new BitmapFactory.Options();
//设置该属性true可以不占用内存,并且能够得到bitmap的宽高等属性,此时得到的bitmap是空
options.inJustDecodeBounds = true;
options.inPreferredConfig=Bitmap.Config.RGB_565;
//将InputStream转为byte数组,可以多次读取
byte[] data1 = inputStream2ByteArr(is);
bitmap = BitmapFactory.decodeByteArray(data1, 0, data1.length, options);
//预加载 获取原始高度和宽度
int originalW=options.outWidth;
int originalH=options.outHeight;
//进行采样,传入原始宽高
options.inSampleSize=getSimpleSize(originalW,originalH);
// options.inSampleSize = 4;
//切记,false此时不再仅仅是加载宽高,而是真正加载像素
options.inJustDecodeBounds = false;
//解析得到新的真正的图片
bitmap = BitmapFactory.decodeByteArray(data1, 0, data1.length, options);
//关闭数据流
is.close();
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
//计算压缩比采样率类
private int getSimpleSize(int originalW, int originalH) {
int simpleSize =4; //默认如果是1 则不压缩
//如果原始宽度大于原始高度并且大于显示的宽度值则以宽度值计算压缩比
if(originalW>originalH && originalW>320){
simpleSize =originalW /320;
//如果原始高度大于原始宽度并且大于显示的高度值则以高度值计算压缩比
}else if (originalH>originalW && originalH>240){
simpleSize =originalH/240;
}
//防止异常
if (simpleSize <=0){
simpleSize =4;
}
return simpleSize;
}
6.小结
简单总结了Bitmap的使用和高效加载Bitmap以及解决OOM问题的方法,对这部分知识有了初步认识,以后的学习中还需要深入了解。