Android 压缩大图到容量超小的图片

2016过去了,今天是2017年的第一天。虽然2016年发生了很多事,But anyway,过去的事就不再去踌躇了,人总要向前看,向乐观的方向前进。新的一年,新的希望,这篇是新年第一篇文章了,也祝大家新年快乐了。

今天主要记录下压缩大图的方法和封装。首先先分析下压缩图片的重点,最后上封装后的代码。

压缩图片的宽高

现在的手机分辨率一般都是1080P,很多相片拍照出来都是4K左右的分辨率,一张图片的容量就高达4M以上,要是把原图直接上传,那耗费的时间和流量是无比巨大的。原图的分辨率是4K,那么我们就可以把他压缩到1080P级别了,也就是把宽和高都按比例缩小。压缩宽高主要用到了BitmapFactory.Options的inSampleSize字段,压缩比例为 1/inSampleSize,也就是当inSampleSize为2时,压缩为原图宽高的1/2,当inSampleSize为8时,压缩为原图的1/8,以此类推。
首先需要计算当前屏幕的宽高和图片的宽高之间的压缩比,当然也可以按照自己项目的需求去设置压缩比。
先抽象一个比较压缩比的方法:

public static int calculateInSampleSize(BitmapFactory.Options options,int reqWidth,int reqHeight){//计算图片的压缩比
        final int height=options.outHeight;//图片的高度 单位1px 即像素点
        final int width=options.outWidth;//图片的宽度 

        int inSampleSize=1;//压缩比

        if(height>reqHeight||width>reqWidth){
            final int halfHeight=height/2;
            final int halfWidth=width/2;
            while ((halfHeight/inSampleSize)>=reqHeight
                    &&(halfWidth/inSampleSize)>=reqWidth){
                inSampleSize*=2;
            }
        }
        return inSampleSize;
    }

调用calculateInSampleSize计算压缩比,并解码原图为Bitmap:

BitmapFactory.Options options=new BitmapFactory.Options();
options.inJustDecodeBounds=true;//只测量image 不加载到内存
BitmapFactory.decodeFile(imagePath,options);//测量image
options.inPreferredConfig= Bitmap.Config.RGB_565;//设置565编码格式 省内存
options.inSampleSize=calculateInSampleSize(options,displayWidth,displayHeight);//获取压缩比 根据当前屏幕宽高去压缩图片
options.inJustDecodeBounds=false;
Bitmap bitmap=BitmapFactory.decodeFile(imagePath,options);//按照Options配置去加载图片到内存

这里比较重要的inJustDecodeBounds字段,当inJustDecodeBounds为true时,调用BitmapFactory.decode 时并没有把图片加载到内存中去,只是去测量图片的宽高,并不占用内存。当inJustDecodeBounds为false时,调用BitmapFactory.decode时就把图片加载到内存中去了,所以获取bitmap应在在inJustDecodeBounds为false后的BitmapFactory.decode去获取。

设置解码格式

Android中默认的解码格式是ARGB8888,我们解码图片一般可以使用ARGB565来节省图片加载到内存中的大小。

在Android.graphics.Bitmap类里有一个内部类Bitmap.Config类,里面定义了枚举变量
public static final Bitmap.Config ALPHA_8
public static final Bitmap.Config ARGB_4444
public static final Bitmap.Config ARGB_8888
public static final Bitmap.Config RGB_565

这些都是色彩的存储方法,ARGB指的是一种色彩模式,里面A代表Alpha,R表示red,G表示green,B表示blue,其实所有的可见色都是右红绿蓝组成的,所以红绿蓝又称为三原色,每个原色都存储着所表示颜色的信息值。
ALPHA_8就是Alpha由8位组成;
ARGB_4444就是由4个4位组成即16位;
ARGB_8888就是由4个8位组成即32位;
RGB_565就是R为5位,G为6位,B为5位共16位。
由此可见:
ALPHA_8 代表8位Alpha位图
ARGB_4444 代表16位ARGB位图
ARGB_8888 代表32位ARGB位图
RGB_565 代表8位RGB位图
位图位数越高代表其可以存储的颜色信息越多,当然图像也就越逼真

所以使用RGB565 16位比ARGB8888 32位少了一半的位数,减少了存储器的容量的同时,降低了数据量。
使用RGB565解码时直接设置BitmapFactory.Options就可以了。

options.inPreferredConfig= Bitmap.Config.RGB_565;//设置565编码格式 省内存

图片质量压缩

上面的压缩方法只是压缩图片加载到内存中的占用大小,我们还必须将图片进行一次质量压缩,输出字节流数组,才能有效的将图片压缩。

 ByteArrayOutputStream out=new ByteArrayOutputStream();//字节流输出
 bitmap.compress(Bitmap.CompressFormat.JPEG,50,out);//压缩成JPEG格式 压缩像素质量为50%

compress中第一个参数输出文件的格式,在Bitmap枚举类CompressFormat中定义,有JPEG(0),PNG (1), WEBP (2), 一般应当选择JPEG,压缩出来的容量小,经过测试WEBP压缩很耗时,耗时时间比较:WEBP>PNG>JPEG,压缩大小:PNG>WEBP>JPEG。第二个参数是压缩的质量比例,也就是压缩像素的显示色彩,当100时表示不压缩,当为50时表示压缩50%的质量,设置这个参数可以有效的极大的缩小图片的大小,可以按照自己的需求进行设置,但建议一般不要大于60。第三个参数就是想要写入图片数据的字节流数组了。

字节流写出文件

但我们经过上述步骤后,就拿到了字节流数据了,此时我们可以根据项目需求直接上传字节流或者保存为本地图片再上传。

        ByteArrayOutputStream out=new ByteArrayOutputStream();//字节流输出
        bitmap.compress(Bitmap.CompressFormat.JPEG,50,out);//压缩成JPEG格式 压缩像素质量为50%

        String fileName=imagePath.substring(imagePath.lastIndexOf("/")+1,imagePath.lastIndexOf("."));//获取文件名称
        File outFile=new File("/storage/emulated/0/PhotoPickTemp",fileName+"_temp.jpeg");//创建压缩后的image文件
        try {
            if(!outFile.exists()){//判断新文件是否存在
                if(outFile.createNewFile()){//判断创建新文件是否成功
                    FileOutputStream fos=new FileOutputStream(outFile);//创建一个文件输出流
                    byte[] bytes=out.toByteArray();//字节数组
                    int count=bytes.length;//字节数组的长度
                    fos.write(bytes,0,count);//写到文件中
                    fos.close();//关闭流
                    out.close();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

获取当前屏幕的宽高

首先需要获取当前屏幕的宽高,我们抽取一个工具类出来。

public class ScreenUtil {
    private int displayWidth;
    private int displayHeight;

    private ScreenUtil() {
        DisplayMetrics metric = new DisplayMetrics();//获取系统屏幕信息
        WindowManager windowManager = (WindowManager) MyApplication.getInstance().getSystemService(Context.WINDOW_SERVICE);
        windowManager.getDefaultDisplay().getMetrics(metric);
        this.displayWidth = metric.widthPixels;     // 屏幕宽度(像素)
        this.displayHeight = metric.heightPixels;   // 屏幕高度(像素)
    }

    private static class ScreenHolder {
        private final static ScreenUtil screenUtil = new ScreenUtil();
    }

    public static ScreenUtil getInstance() {
        return ScreenHolder.screenUtil;
    }

    public int getDisplayWidth() {
        return displayWidth;
    }

    public void setDisplayWidth(int displayWidth) {
        this.displayWidth = displayWidth;
    }

    public int getDisplayHeight() {
        return displayHeight;
    }

    public void setDisplayHeight(int displayHeight) {
        this.displayHeight = displayHeight;
    }
}

获取Application对象:

public class MyApplication extends Application {
    private static MyApplication myApplication;

    @Override
    public void onCreate(){
        super.onCreate();
        myApplication=this;
    }

    public static MyApplication getInstance(){
        return myApplication;
    }
}

自定义Application需要在AndroidManifest中Application节点进行name字段声明:

"application.MyApplication"//声明自定义Application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

封装ImageUtil

public class ImageUtil {

    public ImageUtil(){

    }

    public static File compressImage(String imagePath,int displayWidth,int displayHeight){
        BitmapFactory.Options options=new BitmapFactory.Options();
        options.inJustDecodeBounds=true;//只测量image 不加载到内存
        BitmapFactory.decodeFile(imagePath,options);//测量image

        options.inPreferredConfig= Bitmap.Config.RGB_565;//设置565编码格式 省内存
        options.inSampleSize=calculateInSampleSize(options,displayWidth,displayHeight);//获取压缩比 根据当前屏幕宽高去压缩图片

        options.inJustDecodeBounds=false;
        Bitmap bitmap=BitmapFactory.decodeFile(imagePath,options);//按照Options配置去加载图片到内存

        ByteArrayOutputStream out=new ByteArrayOutputStream();//字节流输出
        bitmap.compress(Bitmap.CompressFormat.JPEG,50,out);//压缩成JPEG格式 压缩像素质量为50%

        String fileName=imagePath.substring(imagePath.lastIndexOf("/")+1,imagePath.lastIndexOf("."));//获取文件名称
        File outFile=new File("/storage/emulated/0/PhotoPickTemp",fileName+"_temp.jpeg");//创建压缩后的image文件
        try {
            if(!outFile.exists()){//判断新文件是否存在
                if(outFile.createNewFile()){//判断创建新文件是否成功
                    FileOutputStream fos=new FileOutputStream(outFile);//创建一个文件输出流
                    byte[] bytes=out.toByteArray();//字节数组
                    int count=bytes.length;//字节数组的长度
                    fos.write(bytes,0,count);//写到文件中
                    fos.close();//关闭流
                    out.close();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return outFile;
    }

    public static int calculateInSampleSize(BitmapFactory.Options options,int reqWidth,int reqHeight){//计算图片的压缩比
        final int height=options.outHeight;//图片的高度
        final int width=options.outWidth;//图片的宽度 单位1px 即像素点

        int inSampleSize=1;//压缩比

        if(height>reqHeight||width>reqWidth){
            final int halfHeight=height/2;
            final int halfWidth=width/2;
            while ((halfHeight/inSampleSize)>=reqHeight
                    &&(halfWidth/inSampleSize)>=reqWidth){
                inSampleSize*=2;
            }
        }
        return inSampleSize;
    }
}

在子线程中使用 千万记得在子线程中调用,因为这是耗时操作,主线程调用很可能会导致ANR发生,如果图片比较多,建议开启多线程去操作,可以参考我上一篇博客 多文件上传 ,在UploadFile中run()中调用:

File outFile=ImageUtil.compressImage(filePath,
                    ScreenUtil.getInstance().getDisplayWidth(),
                    ScreenUtil.getInstance().getDisplayHeight());

经过上面的压缩后,一张4.41M的图片可以压缩到255KB,一张2.8M的图片可以压缩到111KB,压缩比例高达2000%左右。

你可能感兴趣的:(Android)