请选择 进入手机版 | 继续访问电脑版

默认
打赏 发表评论 11
用于IM中图片压缩的Android工具类源码,效果可媲美微信 [附件下载]

前言


Android平台上,一个图片文件解码到内存后所占用的内存空间都是非常可观的。尤其早期各Android手机厂商因为硬件配置所限,允许App的可分配内存极为有限(比如早期的16M、32M等),虽然随着硬件规格的不断提升,APP的可分配内存也有所提升(比如:华为mate7的192M、小米4的128M、红米的128M、三星SM-N7508v的96M等),但对于不断提升的照片品质来说,内存的占用也同样是越来越大。

那么通常情况下,一个主流的Android平台消费级APP(最典型的莫过于移动端IM了)都需要对图片进行压缩(缩减尺寸、压缩质量等),降低内存占用,从而防止出现让用户郁闷的OOM(内存溢出)崩溃。

本文要分享的工具类源码来自IM产品 RainbowChat,压缩效果可媲美微信,详情请参见源码。

图片占用内存计算方法


Android中一张图片(BitMap)占用的内存主要和以下几个参数有关:图片长度、图片宽度、单位像素占用的字节数。一张图片(BitMap)占用的内存=图片长度*图片宽度*单位像素占用的字节数(注:图片长度和图片宽度的单位是像素)。

图片(BitMap)占用的内存应该和屏幕密度(Density)无关,虽然我暂时还拿不出直接证据。创建一个BitMap时,其单位像素占用的字节数由其参数BitmapFactory.Options的inPreferredConfig变量决定。

简单点说,图片所占内存计算方法如下:

图片格式(Bitmap.Config) 占用内存的计算方法 一张100*100的图片占用内存的大小
ALPHA_8图片长度*图片宽度100*100=10000字节
ARGB_4444图片长度*图片宽度*2100*100*2=20000字节
ARGB_8888图片长度*图片宽度*4100*100*4=40000字节
RGB_565图片长度*图片宽度*2100*100*2=20000字节

按照以上计算方法,可以很容易计算出华为P9相机拍出的照片如果载入到内存中,所占用的内存为:
3968 * 2976 * 4 = 47235072字节 = 45M

试想这样的照片,如果未经压缩就在差一点的手机上打开的话,APP很容易就OOM了!

本工具类所采用的压缩方法


第一步:进行图片的尺寸压缩(关键是不能因大图片而导致OOM)
目的:Android上压缩图片尺寸带来最直观结果就是减小内存占用(一张图片的内存占用情况请参见上一节的说明)。
方法:图片尺寸压缩是利用Android平台解析图片对象的最佳实践:即通过设置inSimpleSize值实现,参数阀值都是以微信为参考进行大量采样对比后得出的数值。

第二步:进行图片质量压缩(关键是在保证压缩效果的前提下不能让图片品质降低太多)
目的:图片质量压缩的最直接效果是让图片保存成文件后,文件大小大大降低。
方法:质量压缩参数阀值同样是以微信为参考进行大量采样对比后得出的数值,仅供参考。

本工具类的压缩效果举例


以魅族2手机上拍出的一张 1536*2048 照片为例:

压缩前:文件大小1.63M压缩后:大小是132K

补充说明:一张图片的文件大小,跟图片的色彩和细节丰富度有很大关系,请根据您APP的实际情况适当调整压缩参数,以便达到您的压缩要求即可。

主要源码预览


        /** 压缩质量:发送前要压缩的图片质量(0~100值) */
        public static final int COMPRESS_QUALITY = 75;
        /**
         * 此项将用于计算BitmapFactory.Opts的inSimpleSize值,目的是保
         * 证加载到内存中的图片不至于过大,此值将会与requestHeight一同计算出最终的inSimpleSize
         * ,从而使得加载到内存中的Bitmap不至于过大而导致OOM. 
         */
        public static final int mRequestWidth = 648;
        /**
         * 此项将用于计算BitmapFactory.Opts的inSimpleSize值,目的是保
         * 证加载到内存中的图片不至于过大,此值将会与requestWidth一同计算出最终的inSimpleSize
         * ,从而使得加载到内存中的Bitmap不至于过大而导致OOM. 
         */
        public static final int mRequestHeight = 864;

        /**
         * 图片裁剪、压缩实现方法。
         * 
         * @param imageFilePath 原始(未裁剪尺寸、未压缩质量前)图片的保存路径
         * @param savedPath 压缩处理完成后的图片将要保存的路径
         * @param savedPath 获取质量压缩后将要保存到的路径(目前的实现即是覆盖原始图片)
         * @exception Exception 处理过程中发生任何问题都将抛出异常
         */
        public static void doCompress(String imageFilePath, File savedPath) throws Exception
        {
                Bitmap decreasedBm = null;

                // 【【第一步:压缩尺寸,防止超高分辨率相机拍出的大尺寸图片导致APP出现OOM而崩溃】】
                decreasedBm = loadLocalBitmap(imageFilePath
                                // 调整inSimpleSize值,确保在用户载入巨大尺寸时不致于OOM!
                                , computeSampleSize2(imageFilePath, mRequestWidth, mRequestHeight));

                // 【【第二步:降低图片质量(从而减小文件大小以便节省网络传输数据量)】】
                try
                {
                        if(savedPath != null)
                        {
                                boolean compressOk = BitmapHelper.saveBitmapToFile(decreasedBm, COMPRESS_QUALITY, savedPath);
                                if(compressOk)
                                        Log.d(TAG, "【SendPic】质量压缩完成,压缩质量为:"+COMPRESS_QUALITY+", 临时文件保存路径是:"+savedPath);
                                else
                                        Log.w(TAG, "【SendPic】质量压缩失败!!!压缩质量为:"+COMPRESS_QUALITY+", 将要保存路径是:"+savedPath);
                        }
                        else
                                Log.e(TAG, "【SendPic】质量压缩时,压缩完成后将要保存的路径居然是null ?!savedPath="+savedPath);
                }
                catch (Exception e)
                {
                        Log.e(TAG, "【SendPic】降低图片质量的过程中出错了!", e);
                }
        }

        private static BitmapFactory.Options computeSampleSize2(String filePath, 
                        int reqWidth, int reqHeight)
        {
                BitmapFactory.Options opts = new BitmapFactory.Options();
                try
                {
                        opts.inJustDecodeBounds = true;
                        BitmapFactory.decodeFile(filePath, opts);
                        opts.inSampleSize = computeSampleSize2(opts, reqWidth, reqHeight);  
                }
                catch (Exception e)
                {
                        Log.e("computeSampleSize", "计算图片1的inSampleSize时出错.", e.getCause());
                }
                finally
                {
                        opts.inJustDecodeBounds = false;
                }

                Log.d("computeSampleSize", ">> inSampleSize算法[2]计算完成,计算结果是【"+opts.inSampleSize+"】,reqWidth="+
                                reqWidth+", reqHeight="+reqHeight+", filePath="+filePath);

                return opts;
        }

        private static int computeSampleSize2(BitmapFactory.Options options, 
                        int reqWidth, int reqHeight) 
        {
                // 计算原始图像的高度和宽度
                final int height = options.outHeight;
                final int width = options.outWidth;

                Log.d("computeSampleSize", ">> inSampleSize算法[2]计算中,[原始options.outWidth="+options.outWidth
                                +", o原始ptions.outHeight="+options.outHeight
                                +"],目标reqWidth="+reqWidth+", 目标reqHeight="+reqHeight+", options="+options);

                int inSampleSize = 1;

                //判定,当原始图像的高和宽大于所需高度和宽度时
                if (height > reqHeight || width > reqWidth) 
                {
                        final int heightRatio = Math.round((float) height / (float) reqHeight);
                        final int widthRatio = Math.round((float) width / (float) reqWidth);

                        //算出长宽比后去比例小的作为inSamplesize,保障最后imageview的dimension比request的大
                        inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
                        //计算原始图片总像素
                        final float totalPixels = width * height;
                        // Anything more than 2x the requested pixels we'll sample down further
                        //所需总像素*2,长和宽的根号2倍
                        final float totalReqPixelsCap = reqWidth * reqHeight * 2;

                        //如果遇到很长,或者是很宽的图片时,这个算法比较有用 
                        while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap)
                        {
                                inSampleSize++;
                        }
                }
                return inSampleSize;
        }

附件下载


BitmapCompressHelper.java (7.84 KB , 下载次数: 61 , 售价: 2 金币)

即时通讯网 - 即时通讯开发者社区! 来源: - 即时通讯开发者社区!

上一篇:微信分享开源IM网络层组件库Mars的技术实现(视频+PPT)[附件下载]下一篇:网易云信的高品质即时通讯技术实践之路(视频+PPT)[附件下载]
推荐方案
评论 11
厉害,学习了
签名: 该会员没有填写今日想说内容.
引用:kezhaoyuan 发表于 2017-02-10 14:44
厉害,学习了

签名: 《QQ音乐团队分享:Android中的图片压缩技术详解(下篇)》http://www.52im.net/thread-1212-1-1.html
学习了,原来还有这么多的学问!!!!!!!!
签名: 不错!!!!!!!!!!!!!!!
花金币下载了,却发现没有BitmapHelper类
签名: 该会员没有填写今日想说内容.
引用:jackning 发表于 2017-02-12 14:30
花金币下载了,却发现没有BitmapHelper类

你说的是BtimatHelper. saveBitmapToFile()方法吧,这个类里我忘了改,但saveBitmapToFile代码本身就已拷到BitmapCompressHelper.java中,貌似你们下载完也不看代码就直接撸了。

BitmapCompressHelper.java我今天更新了一下,你再下载一次即可,建议用之前把代码理解一下。
签名: 《QQ音乐团队分享:Android中的图片压缩技术详解(下篇)》http://www.52im.net/thread-1212-1-1.html
学习啦
签名: 该会员没有填写今日想说内容.
太好了,感觉论坛实际东西太多了,要学习的太多了,自己要好好加油。
签名: 该会员没有填写今日想说内容.
学习了。。
精彩……。
签名: 闲的蛋疼……
效果感觉比Luban好 学习了
签名: 该会员没有填写今日想说内容.
引用:淡云流痕 发表于 2017-08-15 17:08
效果感觉比Luban好 学习了

签名: 《QQ音乐团队分享:Android中的图片压缩技术详解(下篇)》http://www.52im.net/thread-1212-1-1.html
打赏楼主 ×
使用微信打赏! 使用支付宝打赏!

Processed in 0.156250 second(s), 39 queries , Gzip On.

返回顶部