记一次安卓图片压缩的调优

需求背景:安卓Pad(webApp)上拍完照缓存本地,并且把图片base64上传服务器。
遇到问题:由于调用格式都是前端调用原生壳方法,需要走JS桥(自己公司实现的一套android、js交互方法),走JS桥的时候会比直接走原生慢,然后拍的照如果不做处理,直接转Base64(图片越大,Base64越长)返给前端的时候,JS桥会卡住,回调不回去。

引言中是这次调优的背景,然后上网搜了下图片压缩分为质量压缩和尺寸压缩,简单了解了一下质量压缩和尺寸压缩的区别如下。

质量压缩

通过算法扣掉(同化)了 图片中的一些某个点附近相近的像素,达到降低质量 减少 文件大小的目的。这里需要注意的是:这么压缩图片本身的像素宽高不会变化,但是图片文件的大小确实会变小,因此Base64长度也会缩短,这样的话,原则上用质量压缩就能满足我的诉求。但是如果我原生需要用这张图片,还是会占用一样大的内存,原生用的话无非是以bitmap形式显示,bitmap对内存的占用是按照图片像素来计算的

尺寸压缩

这个压缩就是实实在在地减小图片的像素值。

结合以上两点,我最终决定是以两者结合的方式去实现这个需求。先进行尺寸压缩,压缩到目标大小之下,然后再进行质量压缩,减小Base64长度(关于base64这块的传输,一直在想能不能用其他的代替,因为Base64确实太长了,很多地方还是会有影响,例如打日志啥的,希望有大佬能指点下

最终我按照尺寸压缩压到1M左右以下,质量压缩到100K以下,代码如下:

/**
     * 图片压缩  尺寸+ 质量压缩(先用尺寸压缩至一个目标值以下,再用质量压缩至最终目标值以下)
     *
     * @param ref     尺寸压缩目标大小  单位KB    ref1    质量压缩目标大小  单位KB   
     */
    private String dimensionCompress(File file, long ref,int ref1) {
        long length = file.length() / 1024;
        String result = "";
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;//设置为true之后,能获取到图片信息,同时返回的bitmap为null
        BitmapFactory.decodeFile(file.getAbsolutePath(), options);
        int inSampleSize = 2;//如果小于目标值,就压缩一倍。
        if (length > ref) { // 如果原始图像的最小边长大于目标长度 单位px
            float ratio = (float) length / ref; // 计算像素压缩比例
            inSampleSize = (int) ratio;
        }
        options.inJustDecodeBounds = false; // 计算好压缩比例后,这次可以去加载原图了
        options.inSampleSize = inSampleSize;
        Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath(), options); // 解码文件

        BufferedOutputStream bos = null;
        ByteArrayOutputStream byteArrayOutputStream = null;
        int quality = 100;
        try {
            if (bitmap != null) {
                byteArrayOutputStream = new ByteArrayOutputStream();
                bos = new BufferedOutputStream(byteArrayOutputStream);
                bitmap.compress(Bitmap.CompressFormat.JPEG, quality, bos);
                if(byteArrayOutputStream.toByteArray().length/1024>ref1){
                    quality = 100*ref1/(byteArrayOutputStream.toByteArray().length/1024);
                    byteArrayOutputStream.reset();
                    bitmap.compress(Bitmap.CompressFormat.JPEG, quality, bos);
                }
                bos.flush();
                bos.close();
                byteArrayOutputStream.flush();
                byteArrayOutputStream.close();
                byte[] bitmapBytes = byteArrayOutputStream.toByteArray();
                result = Base64.encodeToString(bitmapBytes, Base64.NO_WRAP);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (bos != null) {
                    bos.flush();
                    bos.close();
                }
                if (byteArrayOutputStream != null) {
                    byteArrayOutputStream.flush();
                    byteArrayOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return result;
    }

在写这个方法的时候我发现BitmapFactory.decodeFile方法调用一次需要200ms左右的时间,感觉有点长,如果有更好的方法希望有人能告诉我。由于业务场景复杂,本着尽量减少耗时的宗旨,我这边是先根据目标大小算了下我的大概需要压缩多少倍然后只进行一次压缩,而不是采用网上的根据目标像素宽高进行多次压缩。同时,这里需要注意的是options.inSampleSize这个值永远以2的倍数来算,例如,你设置9,他会按8(2的三次方)进行计算。至于质量压缩的目标值我设置的100K,是基于以下考虑:进行尺寸压缩之后的图片大小不会大于1M,那么质量压缩率这样就不会大于90%,也是最后再留个底,保证一下失真率吧。

至此,本次调优就结束了,调用一次方法耗时在800ms不到,这块可能后面还能找到方法优化,下次再来了。

2019018修订
经测试发现,那些内容比较多的图片例如拍的excel的图片(里面全是文字数据信息),阈值设为1M,100K,会导致严重的失真,后来设置成3000K,1000K的时候不管是这种图片,还是其他的图片,都能正常显示。所以目前是更改了阈值,只能继续研究改进了。

你可能感兴趣的:(工作记录(Android))