在Android应用开发中,相信绝大部分应用都会涉及到Bitmap的使用,如果你的应用的用户量不大,可能对Bitmap的使用不会苛刻,但是用户量大的情况下,你就需要斟酌Bitmap这一块的使用,因为它的内存开销不容忽视,常常也会引发很多意想不到的异常;比如 :当你事先不知道图片大小,没对图片处理,而是直接去展示它,带来的问题就是可能直接闪退,有的时候是直接卡着某一帧图片不动,在Android电视应用开发中较容易碰到的,专业术语"OOM"现象;
1 为什么它会引发OOM问题? 原因: 1.1 每个机型在编译ROM时都设置了一个应用堆内存VM值上限dalvik.vm.heapgrowthlimit,用来限定每个应用可用的最大内存,超出这个最大值将会报OOM。这个阀值,一般根据手机屏幕dpi大小递增,dpi越小的手机,每个应用可用最大内存就越低。所以当加载图片的数量很多时,就很容易超过这个阀值,造成OOM。1.2:图片分辨率越高,消耗的内存越大,当加载高分辨率图片的时候,将会非常占用内存,一旦处理不当就会OOM。例如,一张分辨率为:1920x1080的图片。如果Bitmap使用 ARGB_8888 32位来平铺显示的话,占用的内存是1920x1080x4个字节,占用将近8M内存,可想而知,如果不对图片进行处理的话,就会OOM。
如何来计算一张图片所占内存 ?Bitmap所占的内存= 图片长度 x 图片宽度 x 一个像素点占用的字节数;
2 正确使用如下:
2.1 读取图片时,适当压缩
InputStream is = this.getResources().openRawResource(R.drawable.pic1);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
options.inSampleSize = 4; // width,hight设为原来的4分一
Bitmap btp = BitmapFactory.decodeStream(is, null, options);
2.2 及时回收图片占用的内存 ,一般是在Activity/Fragment 中的onStop()或onDestroy中执行
if(imageView != null && imageView.getDrawable() != null){
Bitmap oldBitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
imageView.setImageDrawable(null);
if(oldBitmap != null){
oldBitmap.recycle();
oldBitmap = null;
}
}
// Other code.
System.gc();
3 常见优化策略思路,从图片的质量或尺寸进行考虑:
3.1、BitmapConfig的配置
3.2、使用decodeFile、decodeResource、decodeStream进行解析Bitmap时,配置inDensity和inTargetDensity,两者应该相等,值可以等于屏幕像素密度*0.75f
3.3、使用inJustDecodeBounds预判断Bitmap的大小及使用inSampleS
3.4、Bitmap的回收
4 BitmapFactory解析Bitmap原理
使用如下:
...省略
Bitmap btp = BitmapFactory.decodeStream(is, null, options);
decodeStream的逻辑如下:
public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
// we don't throw in this case, thus allowing the caller to only check
// the cache, and not force the image to be decoded.
if (is == null) {
return null;
}
Bitmap bm = null;
Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");
try {
if (is instanceof AssetManager.AssetInputStream) {
final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();
bm = nativeDecodeAsset(asset, outPadding, opts);
} else {
bm = decodeStreamInternal(is, outPadding, opts);
}
if (bm == null && opts != null && opts.inBitmap != null) {
throw new IllegalArgumentException("Problem decoding into existing bitmap");
}
setDensityFromOptions(bm, opts);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
}
return bm;
}
private static Bitmap decodeStreamInternal(InputStream is, Rect outPadding, Options opts) {
// ASSERT(is != null);
byte [] tempStorage = null;
if (opts != null) tempStorage = opts.inTempStorage;
if (tempStorage == null) tempStorage = new byte[DECODE_BUFFER_SIZE];
return nativeDecodeStream(is, tempStorage, outPadding, opts);
}
从上面的代码可以看出,decodeStream的代码最终会调用以下两个native方法之一
nativeDecodeAsset()
nativeDecodeStream()
5 Bitmap工具类:
public final class BitmapUtils {
public static final String TAG = "BitmapUtil";
private static int sShotScreenWidth = 720;
private static int sShotScreenHeight = 1080;
private static int sShotScreenSize = sShotScreenWidth * sShotScreenHeight;
@SuppressLint("StaticFieldLeak")
private static Context mContext;
@SuppressLint("StaticFieldLeak")
private static Activity mActivity;
public void init(Context context,Activity ac) {
mContext=context;
mActivity=ac;
DisplayMetrics dm = new DisplayMetrics();
ac.getWindowManager().getDefaultDisplay().getMetrics(dm);
//获取屏幕分辨率
sShotScreenWidth = dm.widthPixels;
sShotScreenHeight = dm.heightPixels;
sShotScreenSize = sShotScreenWidth * sShotScreenHeight;
}
/**
* 图片合成
*
* @param bitmap 位图1
* @param mark 位图2
* @return Bitmap
*/
public static Bitmap createBitmap(Bitmap bitmap, Bitmap mark) {
int w = bitmap.getWidth();
int h = bitmap.getHeight();
int mW = mark.getWidth();
int mH = mark.getHeight();
Bitmap newbitmap = Bitmap.createBitmap(w, h, Config.ARGB_8888);// 创建一个长宽一样的位图
Canvas cv = new Canvas(newbitmap);
cv.drawBitmap(bitmap, 0, 0, null);// 在 0,0坐标开始画入bitmap
cv.drawBitmap(mark, w - mW , h - mH , null);// 在右下角画入水印mark
cv.save(Canvas.ALL_SAVE_FLAG);// 保存
cv.restore();// 存储
return newbitmap;
}
/**
* 放大缩小图片
* @param bitmap 位图
* @param w 新的宽度
* @param h 新的高度
* @return Bitmap
*/
public static Bitmap zoomBitmap(Bitmap bitmap, int w, int h) {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
Matrix matrix = new Matrix();
float scaleWidht = ((float) w / width);
float scaleHeight = ((float) h / height);
matrix.postScale(scaleWidht, scaleHeight);
return Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
}
/**
* 旋转图片
* @param bitmap 要旋转的图片
* @param angle 旋转角度
* @return bitmap
*/
public static Bitmap rotate(Bitmap bitmap,int angle) {
Matrix matrix = new Matrix();
matrix.postRotate(angle);
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
bitmap.getHeight(), matrix, true);
}
/**
* 圆形图片
*@param source 位图
* @param strokeWidth 裁剪范围 0表示最大
* @param bl 是否需要描边
* @param bl 画笔粗细
* @param bl 颜色代码
* @return bitmap
*/
public static Bitmap createCircleBitmap(Bitmap source, int strokeWidth, boolean bl,int edge,int color) {
int diameter = source.getWidth() < source.getHeight() ? source.getWidth() : source.getHeight();
Bitmap target = Bitmap.createBitmap(diameter, diameter, Config.ARGB_8888);
Canvas canvas = new Canvas(target);//创建画布
Paint paint = new Paint();
paint.setAntiAlias(true);
canvas.drawCircle(diameter / 2, diameter / 2, diameter / 2, paint);//绘制圆形
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));//取相交裁剪
canvas.drawBitmap(source, strokeWidth, strokeWidth, paint);
if(bl) {
if (color == 0) color = 0xFFFEA248;//默认橘黄色
paint.setColor(color);
paint.setStyle(Paint.Style.STROKE);//描边
paint.setStrokeWidth(edge);
canvas.drawCircle(diameter / 2, diameter / 2, diameter / 2, paint);
}
return target;
}
/**
* 圆角图片
* @param bitmap 位图
* @param rx x方向上的圆角半径
* @param ry y方向上的圆角半径
* @param bl 是否需要描边
* @param bl 画笔粗细
* @param bl 颜色代码
* @return bitmap
*/
public static Bitmap createCornerBitmap(Bitmap bitmap,int rx,int ry,boolean bl,int edge,int color) {
Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(output);//创建画布
Paint paint = new Paint();
paint.setAntiAlias(true);
Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
RectF rectF = new RectF(rect);
canvas.drawRoundRect(rectF, rx, ry, paint);//绘制圆角矩形
paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));//取相交裁剪
canvas.drawBitmap(bitmap, rect, rect, paint);
if(bl) {
if (color == 0) color = 0xFFFEA248;//默认橘黄色
paint.setColor(color);
paint.setColor(color);
paint.setStyle(Paint.Style.STROKE);//描边
paint.setStrokeWidth(edge);
canvas.drawRoundRect(rectF, rx, ry, paint);
}
return output;
}
/**
* 按比例裁剪图片
* @param bitmap 位图
* @param wScale 裁剪宽 0~100%
* @param hScale 裁剪高 0~100%
* @return bitmap
*/
public static Bitmap cropBitmap(Bitmap bitmap, float wScale, float hScale) {
int w = bitmap.getWidth();
int h = bitmap.getHeight();
int wh = (int) (w * wScale);
int hw = (int) (h * hScale);
int retX = (int) (w * (1 - wScale) / 2);
int retY = (int) (h * (1 - hScale) / 2);
return Bitmap.createBitmap(bitmap, retX, retY, wh, hw, null, false);
}
/**
* 获得带倒影的图片方法
* @param bitmap 位图
* @param region 倒影区域 0.1~1
* @return bitmap
*/
public static Bitmap createReflectionBitmap(Bitmap bitmap,float region) {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
Matrix matrix = new Matrix();
matrix.preScale(1, -1);//镜像缩放
Bitmap reflectionBitmap = Bitmap.createBitmap(
bitmap,0
, (int)(height*(1-region))//从哪个点开始绘制
, width
,(int) (height*region)//绘制多高
, matrix, false);
Bitmap reflectionWithBitmap = Bitmap.createBitmap(width,height+ (int) (height*region),
Config.ARGB_8888);
Canvas canvas = new Canvas(reflectionWithBitmap);
canvas.drawBitmap(bitmap, 0, 0, null);
canvas.drawBitmap(reflectionBitmap, 0, height , null);
LinearGradient shader = new LinearGradient(0, bitmap.getHeight(), 0,
reflectionWithBitmap.getHeight()
, 0x70ffffff, 0x00ffffff, TileMode.CLAMP);
Paint paint = new Paint();
paint.setShader(shader);
paint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));//取两层绘制交集。显示下层。
canvas.drawRect(0, height, width, reflectionWithBitmap.getHeight() , paint);
return reflectionWithBitmap;
}
/**
* 图片质量压缩
* @param bitmap
* @param many 百分比
* @return
*/
public static Bitmap compressBitmap(Bitmap bitmap, float many){
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, (int)many*100, baos);
ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
return BitmapFactory.decodeStream(isBm, null, null);
}
/**
* 高级图片质量压缩
*@param bitmap 位图
* @param maxSize 压缩后的大小,单位kb
*/
public static Bitmap imageZoom(Bitmap bitmap, double maxSize) {
// 将bitmap放至数组中,意在获得bitmap的大小(与实际读取的原文件要大)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 格式、质量、输出流
bitmap.compress(Bitmap.CompressFormat.PNG, 70, baos);
byte[] b = baos.toByteArray();
// 将字节换成KB
double mid = b.length / 1024;
// 获取bitmap大小 是允许最大大小的多少倍
double i = mid / maxSize;
// 判断bitmap占用空间是否大于允许最大空间 如果大于则压缩 小于则不压缩
doRecycledIfNot(bitmap);
if (i > 1) {
// 缩放图片 此处用到平方根 将宽带和高度压缩掉对应的平方根倍
// (保持宽高不变,缩放后也达到了最大占用空间的大小)
return scaleWithWH(bitmap,bitmap.getWidth() / Math.sqrt(i),
bitmap.getHeight() / Math.sqrt(i));
}
return null;
}
/***
* 图片缩放
*@param bitmap 位图
* @param w 新的宽度
* @param h 新的高度
* @return Bitmap
*/
public static Bitmap scaleWithWH(Bitmap bitmap, double w, double h) {
if (w == 0 || h == 0 || bitmap == null) {
return bitmap;
} else {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
Matrix matrix = new Matrix();
float scaleWidth = (float) (w / width);
float scaleHeight = (float) (h / height);
matrix.postScale(scaleWidth, scaleHeight);
return Bitmap.createBitmap(bitmap, 0, 0, width, height,
matrix, true);
}
}
/**
* 图片资源文件转bitmap
* @param file 图片的绝对路径
* @return bitmap
*/
public static Bitmap getBitmapResources(Context context,int resId){
return BitmapFactory.decodeResource(context.getResources(),resId);
}
/**
* 将图片路径转Bitmap
* @Param path 图片路径
* @return Bitmap
*/
public static Bitmap getBitmapPath(String path){
return BitmapFactory.decodeFile(path);
}
/**
* bitmap保存到指定路径
* @param file 图片的绝对路径
* @param file 位图
* @return bitmap
*/
public static boolean saveFile(String file, Bitmap bmp) {
if(TextUtils.isEmpty(file) || bmp == null) return false;
File f = new File(file);
if (f.exists()) {
f.delete();
}else {
File p = f.getParentFile();
if(!p.exists()) {
p.mkdirs();
}
}
try {
FileOutputStream out = new FileOutputStream(f);
bmp.compress(Bitmap.CompressFormat.JPEG, 100, out);
out.flush();
out.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return false;
}
return true;
}
/**
* 回收一个未被回收的Bitmap
*@param bitmap
*/
public static void doRecycledIfNot(Bitmap bitmap) {
if (!bitmap.isRecycled()) {
bitmap.recycle();
}
}
/**
* 将图片转换成Base64编码的字符串
*/
public static String imageToBase64(String path){
if(TextUtils.isEmpty(path)){
return null;
}
InputStream is = null;
byte[] data = null;
String result = null;
try{
is = new FileInputStream(path);
//创建一个字符流大小的数组。
data = new byte[is.available()];
//写入数组
is.read(data);
//用默认的编码格式进行编码
result = Base64.encodeToString(data,Base64.DEFAULT);
}catch (Exception e){
e.printStackTrace();
}finally {
if(null !=is){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return result;
}}