背景:调用相机拍照,结果返回时,导致内存迅速升高,内存不足时会导致OOM
发现的原因:在相机拍照后,会生成很大的照片,分辨率也很高,因此占用的内存会很大,在网上找了一些同学的相机拍照代码块发现,使用到一个BitmapHelper的类,本类的主要作用是:分析拍摄的照片,如果有方向不对了对照片进行旋转,进行旋转需要吧图片加载到内存中,原尺寸的照片通常会很大,会占用两个Bitmap才能够完成旋转,因此bitmap helper在旋转之前进行了尺寸的压缩。
不同的手机或者pad对尺寸进行修改会造成所放的比例计算差异。。。好多废话,下面是我对Bitmap Helper类的简单修改,能够让不同的尺寸或方向的照片进行正确的缩放。
Bitmap Helper能够将拍摄的照片缩放后保存在某个目录中:
public class BitmapHelper {
/**
* get the orientation of the bitmap {@link ExifInterface}
*
* @param path
* @return
*/
public static int getDegress(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 (IOException e) {
e.printStackTrace();
}
return degree;
}
/**
* rotate the bitmap
*
* @param bitmap
* @param degress
* @return
*/
public static Bitmap rotateBitmap(Bitmap bitmap, int degress) {
if (bitmap != null) {
Matrix m = new Matrix();
m.postRotate(degress);
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), m, true);
return bitmap;
}
return bitmap;
}
/**
* caculate the bitmap sampleSize
*
* @param
* @return
*/
public static int caculateInSampleSize(Options options, int rqsW, int rqsH) {
int height = options.outHeight;
int width = options.outWidth;
//主要修改这里,让最宽的对应设定的宽,高同理
if (width > height) {
int temp = width;
width = height;
height = temp;
}
if (rqsW > rqsH) {
int rqsT = rqsH;
rqsH = rqsW;
rqsW = rqsT;
}
int inSampleSize = 1;
if (rqsW == 0 || rqsH == 0) return 1;
if (height > rqsH || width > rqsW) {
int heightRatio = Math.round((float) height / (float) rqsH);
int widthRatio = Math.round((float) width / (float) rqsW);
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
/**
* 根据需要压缩到某尺寸压缩指定路径的图片,并得到图片对象
*
* @param path
* @param rqsW
* @param rqsH
* @return
*/
public static Bitmap compressBitmap(String path, int rqsW, int rqsH) {
Options options = getBitmapOptions(path);
options.inSampleSize = caculateInSampleSize(options, rqsW, rqsH);
return BitmapFactory.decodeFile(path, options);
}
/**
* 压缩指定路径图片,并将其保存在缓存目录中,通过isDelSrc判定是否删除源文件,并获取到缓存后的图片路径
*
* @param context
* @param srcPath
* @param rqsW
* @param rqsH
* @param isDelSrc
* @return
*/
public static String compressBitmap(Context context, String srcPath, int rqsW, int rqsH, boolean isDelSrc) {
int degree = getDegress(srcPath);
Bitmap bitmap = compressBitmap(srcPath, rqsW, rqsH);//根据长宽以及图片的长宽得到缩放图片
File srcFile = new File(srcPath);
String desPath = getImageCacheDir(context) + srcFile.getName();
try {
if (degree != 0) bitmap = rotateBitmap(bitmap, degree);
File file = new File(desPath);
FileOutputStream fos = new FileOutputStream(file);
bitmap.compress(CompressFormat.JPEG, 80, fos);//80是图片质量
fos.close();
if (isDelSrc) srcFile.deleteOnExit();
} catch (Exception e) {
}
bitmap.recycle();
System.gc();
return desPath;
}
/**
* 压缩某个输入流中的图片,可以解决网络输入流压缩问题,并得到图片对象
*
* @return Bitmap {@link Bitmap}
*/
public static Bitmap compressBitmap(InputStream is, int reqsW, int reqsH) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ReadableByteChannel channel = Channels.newChannel(is);
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (channel.read(buffer) != -1) {
buffer.flip();
while (buffer.hasRemaining()) baos.write(buffer.get());
buffer.clear();
}
byte[] bts = baos.toByteArray();
Bitmap bitmap = compressBitmap(bts, reqsW, reqsH);
is.close();
channel.close();
baos.close();
return bitmap;
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
return null;
}
}
/**
* 压缩指定byte[]图片,并得到压缩后的图像
*
* @param bts
* @param reqsW
* @param reqsH
* @return
*/
public static Bitmap compressBitmap(byte[] bts, int reqsW, int reqsH) {
Options options = new Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(bts, 0, bts.length, options);
options.inSampleSize = caculateInSampleSize(options, reqsW, reqsH);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeByteArray(bts, 0, bts.length, options);
}
/**
* 压缩已存在的图片对象,并返回压缩后的图片
*
* @param bitmap
* @param reqsW
* @param reqsH
* @return
*/
public static Bitmap compressBitmap(Bitmap bitmap, int reqsW, int reqsH) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(CompressFormat.PNG, 100, baos);
byte[] bts = baos.toByteArray();
Bitmap res = compressBitmap(bts, reqsW, reqsH);
baos.close();
return res;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return bitmap;
}
}
/**
* 压缩资源图片,并返回图片对象
*
* @param res {@link Resources}
* @param resID
* @param reqsW
* @param reqsH
* @return
*/
public static Bitmap compressBitmap(Resources res, int resID, int reqsW, int reqsH) {
Options options = new Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resID, options);
options.inSampleSize = caculateInSampleSize(options, reqsW, reqsH);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resID, options);
}
/**
* 得到指定路径图片的options
*
* @param srcPath
* @return Options {@link Options}
*/
public static Options getBitmapOptions(String srcPath) {
Options options = new Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(srcPath, options);
options.inJustDecodeBounds = false;
return options;
}
/**
* 获取图片缓存路径
*
* @param context
* @return
*/
private static String getImageCacheDir(Context context) {
String dir = FileUtils.getCacheDir() + "Image" + File.separator;
File file = new File(dir);
if (!file.exists()) file.mkdirs();
return dir;
}
}
压缩的方式
上面不仅用到尺寸压缩,而且用到了图片质量压缩
bitmap.compress(CompressFormat.JPEG, 80, fos);//80是图片质量
这个就是质量压缩。
质量压缩需要把文件加载到内存中,因此大图片质量压缩也会瞬间占用大量内存,因此我使用的是先尺寸压缩,再质量压缩的方式。
关于质量压缩
质量压缩android比IOS的质量压缩采用的据说是同一个压缩方式,但是压缩中的一个参数,android默认设置为false,IOS默认设置为true,结果是设置为相同质量压缩下,iOS文件占用的空间比android少。因此我们可以把压缩的源代码搞过来,把false改为true。
我的实际测试,质量的压缩,true的比false仅仅小了30K~100k(图片越小越不明显) 。
使用方式就是:倒入.so文件和NativeUtil
public class NativeUtil {
private static int DEFAULT_QUALITY = 95;
public static void compressBitmap(Bitmap bit, String fileName,
boolean optimize) {
compressBitmap(bit, DEFAULT_QUALITY, fileName, optimize);
}
public static void compressBitmap(Bitmap bit, int quality, String fileName,
boolean optimize) {
Log.d("native", "compress of native");
if (bit.getConfig() != Config.ARGB_8888) {
Bitmap result = Bitmap.createBitmap(bit.getWidth(), bit.getHeight(),
Config.ARGB_8888);
Canvas canvas = new Canvas(result);
Rect rect = new Rect(0, 0, bit.getWidth(), bit.getHeight());
canvas.drawBitmap(bit, null, rect, null);
saveBitmap(result, quality, fileName, optimize);
result.recycle();
} else {
saveBitmap(bit, quality, fileName, optimize);
}
}
private static void saveBitmap(Bitmap bit, int quality, String fileName,
boolean optimize) {
compressBitmap(bit, bit.getWidth(), bit.getHeight(), quality,
fileName.getBytes(), optimize);
}
private static native String compressBitmap(Bitmap bit, int w, int h,
int quality, byte[] fileNameBytes, boolean optimize);
static {
System.loadLibrary("jpegbither");
System.loadLibrary("bitherjni");
}
}
博主github主页:Github主页 有一个仿IOS的旋转等待View,欢迎使用点星星