一、在说Bitmap之前先说个是,之前一直忘记说了,由于Android的2D渲染现在可以比较好的支持硬件加速了,但是在自定义控件进行绘制是还是有很多api不兼容的,所以在自定义控件的时候,在你不能100%确认你使用的api支持硬件加速的话,最好把硬件加速关闭了,否则有可能出现一些莫名其妙的问题:
1、硬件加速关闭方法
在清单文件的application节点下进行关闭或者打开,这种方式是作用于整个应用的:
<!--false表示关闭,true表示打开-->
android:hardwareAccelerated="false"
2、在activity注册时进行关闭或者打开,这种方式只作用于该activity:
<activity
android:name=".WebViewTest"
android:hardwareAccelerated="false"/>
3、在指定View初始化时关闭或者打开,这种方式只作用于该View控件:
//如果是自定义的view,可在构造方法中调用该方法,即可开启或者关闭硬件加速
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
二、行,那接着说Bitmap,那么咱看看Bitmap是什么,在android中叫位图,Bitmap是一个非常重要的东西,通过这个对象,我们可以很方便的对图片进行操作,下面我们介绍下位图在android中的表示方式。
1、说到bitmap大家听过最多的跟它有关系的东西应该就是内存、OOM、压缩这些个鬼东西了,是的没错,内存溢出永远是一个说不完的话题,那么为什么bitmap那么耗内存?下面我们看看bitmap在内存中的表示方式吧。
首先、我们日常形容图片的大小一般都说这个图片是多少多少像素,android也不例外,但是像素并不代表图片在内存中的大小,这个跟ARGB通道有很大关系,其中A:表示透明度,R:表示三原色中的红,G:表示三原色中的绿,B:表示三原色中的蓝,而在Bitmap的一个内部类Config中就定义了图片的色彩模式,也就确定了图片的在内存中所占的大小,下面看个表格:
Bitmap.Config 常量值 描述 内存消耗(字节/像素)
Bitmap.Config ARGB_8888 32位的ARGB位图 4
Bitmap.Config ARGB_4444 16位的ARGB位图 2
Bitmap.Config RGB_565 16位的RGB位图 2
Bitmap.Config ALPHA_8 8位的Alpha位图 1
由表就可以清晰的看出就算同样像素的图片不同的配置和,对内存的消耗差别是非常的大的,比如说我们要加载一张500*500的图片,配置是ARGB_8888的,那么消耗的内存就是500*500*4=1000000byte,很恐怖吧,接近1M,要知道手机内存可是好珍贵的。
注意:当我们RGB_565是没有alpha通道的,jpg图片就是
2、接下来我们看下构造方法
/**
* Private constructor that must received an already allocated native bitmap
* int (pointer).
*/
// called from JNI
Bitmap(long nativeBitmap, byte[] buffer, int width, int height, int density,
boolean isMutable, boolean requestPremultiplied,
byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) {
}
我们可以看到,构造方法不是public的,也就是说,不能直接的去new Bitmap(),而且注释也说了,called from JNI,看到这个,大家应该为什么我们平时都是使用BitmapFactory了吧,这个就是一个创建bitmap的辅助类了,这个类提供了很多操作bitmap的相关方法下面介绍几个常用的:
BitmapFactory.decodeResource(Resources,int),通过资源文件加载bitmap
BitmapFactory.decodeResource(Resources,int,Options),通过资源文件加载bitmap,并重新指定图片的Options属性
BitmapFactory.decodeFile(String),通过文件路径加载图片
BitmapFactory.decodeFile(String,Options),通过文件路径加载图片,并重新指定图片的Options属性
public static Bitmap decodeByteArray(byte[] data, int offset, int length),从字节数组中解码图片,
---data:图片数据
---offset:解码起始位置
---length:要解码的长度
public static Bitmap decodeByteArray(byte[] data, int offset, int length,Options),从字节数组中解码图片
public static Bitmap decodeStream(InputStream),从流中解码图片数据
public static Bitmap decodeStream(InputStream,Rect,Options),设置Rect跟Options
这些方法没什么特殊的,重点是Options这个东西,弄明白这个东西,那么bitmap就算搞定了,那么下面就开始研究Options
三、Options、除了BitmapFractory之外,这是我们加载图片最重要的一个东西,它能指定图片的很多属性,压缩、宽高控制等等,Options也是解决oom经常用的。它是BitmapFractory的一个内部类,里面定义了若干属性,通过对属性的赋值来达到改变图片的一些配置的效果,下面看下每个属性的效果:
1、inJustDecodeBounds,这是一个boolean的值,当我们对Options设置中的这个属性设置了true那么基于该Options解码返回的bitmap仅会保存图片的宽高,而不会包括图片本身的数据,当我们只需要获取图片的宽高的时候,就可以设置这个属性了,对内存绝对是一种温柔的呵护:
//这时候的bmp只能取到图片的宽高,而获取不到图片的真正内容
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
Bitmap bmp = BitmapFactory.decodeFile(path, options);
2、inSampleSize,这是一个int类型的属性,这个值务必理解,因为这是图片等比例压缩的关键,当它小于1的时候,将会被当做1处理,如果大于1,那么就会按照比例(1 / inSampleSize)缩小bitmap的宽和高、降低分辨率,大于1时这个值将会被处置为2的倍数;
那么我们如何计算这个值?比如说我们一个页面有需要显示很多图片,那么我们这些图片我们需要显示多大的尺寸可以预先设计好,然后用已设计好的尺寸跟即将需要显示的图片尺寸做比较,从而得到inSampleSize值,下面看下代码片段:
/**
* 根据图片求图片采样率inSampleSize的值
* @param ctx
* @param file 图片
* @param reqHeight 图片显示最大高度
* @param reqWidth 图片显示最大宽度
* @return
*/
public static int inSampleSize(Context ctx, Object file, int reqHeight, int reqWidth) {
int inSampleSize = 1;
Bitmap bitmap = null;
//这里我们只需要图片的宽高,而不需要图片的真实数据,所以没必要将图片数据加载到内存中
BitmapFactory.Options ops = new BitmapFactory.Options();
ops.inJustDecodeBounds = true;
if (file instanceof String) {
bitmap = BitmapFactory.decodeFile((String) file, ops);
} else if (file instanceof Integer) {
bitmap = BitmapFactory.decodeResource(ctx.getResources(), (Integer) file, ops);
}
int height, width;
if (bitmap == null) {
return inSampleSize;
}
height = ops.outHeight;
width = ops.outWidth;
if (height > reqHeight || width > reqWidth) {
if (height / reqHeight > width / reqWidth) {
inSampleSize = width / reqWidth;
} else {
inSampleSize = height / reqHeight;
}
}
return inSampleSize;
}
这里要特别注意的是inSampleSize的值最终都会被换算成2的整数倍,也就是如果我们计算出来的是13,最终取值为12,所以准确性上来说回稍有误差,如果不太过于追求性能,可以考虑使用Matrix。
3、inPreferredConfig,这个是解码图片是设置图片色彩模式的,就是上面提到的四种模式,如果不考虑透明通道的话,建议使用RGB_565,内存消耗较小。
4、inPremultiplied,布尔值类型变量,设置为true是可以为返回的bitmap预先加上透明通道。
5、inDither,布尔类型属性,这个用官方语言来说叫抖动解码,那么什么叫抖动解码呢?下面简单介绍下:
通过上面的介绍,我们知道图片不同的色彩模式每个像素都有着不同的内存的消耗,32位的图像比16位的清晰,所保存的颜色数据自然要多得多,那么当我们要把一张32位的图像解码成16位的图像的时候,自然会丢失很多颜色的数据,会使得图片失真,特别是有颜色渐变的时候会产生较为明显的断裂,这时候我们如果使用抖动解码的话能在一定程度上缓解这种现象!至于具体的算法。。。。不懂!
6、inScaled,布尔类型,指定bitmap是否能被缩放,true表示可以被缩放
7、outWidth和outHeight,这两个东西返回的就是bitmap的宽高
四、上面简单介绍了Bitmap相关的两个的辅助类,其实对Bitma的理解就几个步骤,解码(加载)--->操作--->保存--->释放(内存),图片的加载就不说了,使用BitmapFractory就差不多够用了,至于释放也就一个recycle()方法,不过要注意的是要确保要释放的bitmap没有被引用了
那么接下来我们说说bitmap的操作
1、不知道你是否也遇到过这样一个问题,就是你加载本地的一些图片显示出来的时候发现图片是被旋转了90度的倍数的,其实图片的旋转度数是被保存在exif里面的,我们可以通过读取这个值进行判断,然后再重新设置该值,下面看个栗子:
读取图片的选择角度:
/**
* 读取照片exif信息中的旋转角度
*
* @param path 照片路径
* @return角度
*/
private static int readPictureDegree(String path) {
int degree = 0;
try {
ExifInterface exifInterface = new ExifInterface(path);
int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
degree = 90;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
degree = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_270:
degree = 270;
break;
}
} catch (Exception e) {
}
return degree;
}
如果图片的旋转角度不为0,对图片进行矫正,生成正确的bitmap,这样使用到的是Matrix的旋转方法
private static Bitmap rotateBitmap(Bitmap b, float rotateDegree) {
if (b == null) {
return null;
}
Matrix matrix = new Matrix();
matrix.postRotate(rotateDegree);
/**
* 创建新的bitmap
* 参数1:源图bitmap
* 参数2:新位图的第一个像素点的x坐标
* 参数3:新位图的第一个像素点的y坐标
* 参数4:新位图每行的像素点数量
* 参数5:新位图的行数
* 参数6:矩阵
* 参数7:这个不太理解,好像是对源图有所操作!!!
*/
Bitmap rotaBitmap = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), matrix, true);
return rotaBitmap;
}
注意:上面用到的createBitmap()这个api的参数是非常诡异要理解好特别是第四第五个并不是我们以前所认识得getRight,getBottom之类的了
2、图片转二进制数组
public byte[] bitmap2Bytes(Bitmap bm) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.PNG, 100, baos);
return baos.toByteArray();
}
3、直接对本地图片进行等比例压缩解码
/**
* 按照指定比例对图片进行等比例压缩
* @param path 图片路径
* @param reqHeight 期望高度
* @param reqWidth 期望宽度
*/
public static Bitmap getSmallBitmap(String path, int reqHeight, int reqWidth) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
int height = options.outHeight;
int width = options.outWidth;
/**
* 计算采样率
*/
if (height > reqHeight || width > reqWidth) {
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
options.inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
} else {
options.inSampleSize = 1;
}
//真正的加载bitmap
options.inJustDecodeBounds = false;
//获取options配置的bitmap
Bitmap bitmap = BitmapFactory.decodeFile(path, options);
return bitmap;
}
4、使用compress()对bitmap进行压缩
private static Bitmap compressImage(Bitmap image) {
if (image == null) {
return null;
}
ByteArrayOutputStream baos = null;
try {
baos = new ByteArrayOutputStream();
/**
* 参数1:压缩格式
* 参数2:压缩率,30表示图片被压缩了70%,100表示不压缩
* 参数3:将压缩完成后的图片数据存入流中
*/
image.compress(Bitmap.CompressFormat.JPEG, 30, baos);
byte[] bytes = baos.toByteArray();
//根据字节数组获取一个输入流
ByteArrayInputStream isBm = new ByteArrayInputStream(bytes);
//将输入流转换成最终的bitmap
Bitmap bitmap = BitmapFactory.decodeStream(isBm);
return bitmap;
} catch (OutOfMemoryError e) {
} finally {
try {
if (baos != null) {
baos.close();
}
} catch (IOException e) {
}
}
return null;
转载自:android之Bitmap详解