一般Android项目都有从相册选取照片并上传到服务器的需求,但是不能对file文件直接上传,因为对于一些像素特别高的手机,拍出来的相片体积也会特别巨大,比如1300万的相机最大能达到9-10m,这样在上传之前就需要进行压缩,而压缩氛围图像大小的压缩和品质的压缩,在压缩过程中很有可能会出现OOM的情况,下面是一个图片压缩工具类,思路是先进行图像宽高的压缩,将比较大的图片压缩到1280*1280的大小范围,以减少oom,然后进行品质的压缩,一般品质保持在70-100之间,肉眼几乎无法看出差别,而大小能够明显改善,压缩后大小范围在100k-200k之间,便于向服务器上传。宽高压缩和品质压缩各执行一遍,没有循环压缩,因为我发现一些循环压缩的方案往往导致图像质量显著下降,俗称照片糊了。
package com.example.administrator.jnidemo; import android.content.Context; import android.content.res.AssetFileDescriptor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.graphics.Rect; import android.os.Build; import android.util.Log; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; /** * Created by Administrator on 2016/4/6. */ public class AlxBitMapUtils { /** * 传入一个bitmap,根据传入比例进行大小缩放 * @param bitmap * @param widthRatio 宽度比例,缩小就比1小,放大就比1大 * @param heightRatio * @return */ public static Bitmap scaleBitmap(Bitmap bitmap, float widthRatio, float heightRatio) { Matrix matrix = new Matrix(); matrix.postScale(widthRatio,heightRatio); return Bitmap.createBitmap(bitmap,0,0,bitmap.getWidth(),bitmap.getHeight(),matrix,true); } /** * 传入图片路径,根据图片进行压缩,仅压缩大小,不压缩质量 * @param oriFile 源文件 * @param targetFile 这个和 stream传一个就行 * @param ifDel 是否需要在压缩完毕后删除原图 */ public static void compressImage(File oriFile, File targetFile, OutputStream stream,boolean ifDel) { if(oriFile ==null)return; Log.i("Alex","源图片为"+oriFile); Log.i("Alex","目标地址为"+targetFile); try { BitmapFactory.Options opts = new BitmapFactory.Options(); opts.inJustDecodeBounds = true; // 不读取像素数组到内存中,仅读取图片的信息,非常重要 BitmapFactory.decodeFile(oriFile.getAbsolutePath(), opts);//读取文件信息,存放到Options对象中 // 从Options中获取图片的分辨率 int imageHeight = opts.outHeight; int imageWidth = opts.outWidth; int longEdge = Math.max(imageHeight,imageWidth);//取出宽高中的长边 int pixelCount = (imageWidth*imageHeight)>>20;//看看这张照片有多少百万像素 Log.i("Alex","图片宽为"+imageWidth+"图片高为"+imageHeight+"图片像素数为"+pixelCount+"百万像素"); long size = oriFile.length(); Log.i("Alex","f.length 图片大小为"+(size)+" B"); //走到这一步的时候,内存里还没有bitmap Bitmap bitmap = null; if(pixelCount > 4){//如果超过了4百万像素,那么就首先对大小进行压缩 float compressRatio = longEdge /1280f; int compressRatioInt = Math.round(compressRatio); if(compressRatioInt%2!=0 && compressRatioInt!=1)compressRatioInt++;//如果是奇数的话,就给弄成偶数 Log.i("Alex","长宽压缩比是"+compressRatio+" 偶数化后"+compressRatioInt); //尺寸压缩 BitmapFactory.Options options = new BitmapFactory.Options(); //目标出来的大小1024*1024 1百万像素,100k左右 options.inSampleSize = Math.round(compressRatioInt);//注意,此处必须是偶数,根据计算好的比例进行压缩,如果长边没有超过1280*1.5,就不去压缩,否则就压缩成原来的一半 options.inJustDecodeBounds = false;//在decode file的时候,不仅读取图片的信息,还把像素数组到内存 options.inPreferredConfig = Bitmap.Config.RGB_565;//每个像素占四位,即R=5,G=6,B=5,没有透明度,那么一个像素点占5+6+5=16位 //现在开始将bitmap放入内存 bitmap = BitmapFactory.decodeFile(oriFile.getAbsolutePath(), options);//根据压缩比取出大小已经压缩好的bitmap //此处会打印出存入内存的bitmap大小 }else {//如果是长图或者长边短于1920的图,那么只进行质量压缩 // 现在开始将bitmap放入内存 bitmap = BitmapFactory.decodeFile(oriFile.getAbsolutePath()); //此处会打印出bitmap在内存中占得大小 } if(targetFile!=null)compressMethodAndSave(bitmap, targetFile); if(stream!=null)compressBitmapToStream(bitmap,stream); if(ifDel) oriFile.delete();//是否要删除源文件 System.gc(); }catch (Exception e){ Log.d("Alex",""+e.getMessage().toString()); } } /** * 获取一个bitmap在内存中所占的大小 * @param image * @return */ private static int getSize(Bitmap image){ int size=0; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { //API 19 size = image.getAllocationByteCount(); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {//API 12 size = image.getByteCount(); } else { size = image.getRowBytes() * image.getHeight(); } return size; } /** * 根据传来的bitmap的大小计算一个质量压缩率,并且保存到指定路径中去,只压缩质量,不压缩大小 * @param image * @param targetFile */ public static void compressMethodAndSave(Bitmap image,File targetFile){ try { OutputStream stream = new FileOutputStream(targetFile); int size = compressBitmapToStream(image,stream); if(size==0)return; long afterSize = targetFile.length(); Log.i("Alex","压缩完后图片大小"+(afterSize>>10)+"KB 压缩率:::"+afterSize*100/size+"%"); }catch (Exception e){ Log.i("Alex","压缩图片出现异常",e); } } public static int compressBitmapToStream(Bitmap image,OutputStream stream){ if(image==null || stream==null)return 0; try { Bitmap.CompressFormat format = Bitmap.CompressFormat.JPEG; int size = getSize(image); Log.i("Alex","存入内寸的bitmap大小是"+(size>>10)+" KB 宽度是"+image.getWidth()+" 高度是"+image.getHeight()); int quality = getQuality(size);//根据图像的大小得到合适的有损压缩质量 Log.i("Alex","目前适用的有损压缩率是"+quality); long startTime = System.currentTimeMillis(); image.compress(format, quality, stream);//压缩文件并且输出 if (image != null) { image.recycle();//此处把bitmap从内存中移除 image = null; } Log.i("Alex","压缩图片并且存储的耗时"+(System.currentTimeMillis()-startTime)); return size; }catch (Exception e){ Log.i("Alex","压缩图片出现异常",e); } return 0; } /** * 根据图像的大小得到合适的有损压缩质量,因为此时传入的bitmap大小已经比较合适了,靠近1000*1000,所以根据其内存大小进行质量压缩 * @param size * @return */ private static int getQuality(int size){ int mb=size>>20;//除以100万,也就是m int kb = size>>10; Log.i("Alex","准备按照图像大小计算压缩质量,大小是"+kb+"KB,兆数是"+mb); if(mb>70){ return 17; }else if(mb>50){ return 20; }else if(mb>40){ return 25; }else if(mb>20){ return 40; }else if(mb>10){ return 60; }else if(mb>3){//目标压缩大小 100k,这里可根据实际情况来判断 return 60; }else if(mb>=2){ return 60; }else if(kb > 1500){ return 70; }else if(kb > 1000){ return 80; }else if(kb>500){ return 85; }else if(kb>100){ return 90; } else{ return 100; } } /** * 从assets文件夹中根据文件名得到一个Bitmap * @param fileName * @return */ public static Bitmap getDataFromAssets(Context context,String fileName){ Log.i("Alex","准备从assets文件夹中读取文件"+fileName); try { //可以直接使用context.getResources().getAssets().open(fileName);得到一个InputStream再用BufferedInputStream通过缓冲区获得字符数组 AssetFileDescriptor descriptor = context.getResources().getAssets().openFd(fileName);//此处获得文件描述之后可以得到FileInputStream,然后使用NIO得到Channel long fileSize = descriptor.getLength(); Log.i("Alex","要读取的文件的长度是"+fileSize);//注意这个地方如果文件大小太大,在decodeStream需要设置参数进行裁剪 Bitmap bitmap = BitmapFactory.decodeStream(context.getResources().getAssets().open(fileName)); // Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);//注意,AssetFileDescriptor只能用来获取文件的大小,不能用来获取inputStream,用FileDescriptor获取的输入流BitmapFactory.decodeStream不能识别 if(bitmap==null)Log.i("Alex","decode bitmap失败"); return bitmap; } catch (Exception e) { Log.i("Alex","读取文件出现异常",e); e.printStackTrace(); } return null; } public static boolean copyFile(File sourceFile,File targetFile){ //NIO中读取数据的步骤:1)从FileInputStream中得到Channel对象;2)创建一个buffer对象;3)从Channel中读数据到Buffer中; FileInputStream fin = null; FileOutputStream fout = null; FileChannel fcin = null; FileChannel fcout = null; try { fin = new FileInputStream( sourceFile ); fout = new FileOutputStream( targetFile ); fcin = fin.getChannel(); fcout =fout.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(1024); int r = 0; while ((r = fcin.read(buffer))!=-1) { buffer.clear(); buffer.flip();//反转一下,从写入状态变成读取状态 fcout.write(buffer); } return true; } catch (Exception e){ Log.i("Alex","复制文件发生错误",e); }finally { if(fin!=null) try { fin.close(); if(fout!=null)fout.close(); if(fcin!=null)fcin.close(); if(fcout!=null)fcout.close(); } catch (IOException e) { e.printStackTrace(); } } return false; } }
private byte[] getCompressedUploadPhotoBitmapByteArray(String filePath, int inSampleSize, int quality) { byte[] bitmapDataByteArray = {}; if (!JDataUtils.isEmpty(filePath)) { File file = new File(filePath); if (file.exists()) { ByteArrayOutputStream baos = null; try { baos = new ByteArrayOutputStream();//这个输出流好,输出到内存,然后可以直接转换成数组 AlxBitmapUtils.compressImage(file,null,baos,false);//这一句是关键,在这里进行压缩 bitmapDataByteArray = baos.toByteArray(); Log.i("Alex","压缩完后的字符串数组是大小是"+(bitmapDataByteArray.length>>10)+" KB"); } catch (Throwable throwable) { } finally { try { if (baos != null) { baos.close(); } } catch (Throwable throwable) { } } } else { bitmapDataByteArray = null; } } else { bitmapDataByteArray = null; } return bitmapDataByteArray; }