默认
打赏 发表评论 14
想开发IM:买成品怕坑?租第3方怕贵?找开源自已撸?尽量别走弯路了... 找站长给点建议
用于IM中图片压缩的Android工具类源码,效果可媲美微信 [附件下载]
阅读(129126) | 评论(14 收藏11 淘帖2
微信扫一扫关注!

前言


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 , 下载次数: 128 , 售价: 2 金币)

附录:全站精品资源下载


[1] 精品源码下载:
轻量级即时通讯框架MobileIMSDK的iOS源码(开源版)[附件下载]
开源IM工程“蘑菇街TeamTalk”2015年5月前未删减版完整代码 [附件下载]
微信本地数据库破解版(含iOS、Android),仅供学习研究 [附件下载]
NIO框架入门(四):Android与MINA2、Netty4的跨平台UDP双向通信实战 [附件下载]
NIO框架入门(三):iOS与MINA2、Netty4的跨平台UDP双向通信实战 [附件下载]
NIO框架入门(二):服务端基于MINA2的UDP双向通信Demo演示 [附件下载]
NIO框架入门(一):服务端基于Netty4的UDP双向通信Demo演示 [附件下载]
用于IM中图片压缩的Android工具类源码,效果可媲美微信 [附件下载]
高仿Android版手机QQ可拖拽未读数小气泡源码 [附件下载]
一个WebSocket实时聊天室Demo:基于node.js+socket.io [附件下载]
Android聊天界面源码:实现了聊天气泡、表情图标(可翻页) [附件下载]
高仿Android版手机QQ首页侧滑菜单源码 [附件下载]
开源libco库:单机千万连接、支撑微信8亿用户的后台框架基石 [源码下载]
分享java AMR音频文件合并源码,全网最全
微信团队原创Android资源混淆工具:AndResGuard [有源码]
一个基于MQTT通信协议的完整Android推送Demo [附件下载]
Android版高仿微信聊天界面源码 [附件下载]
仿微信的IM聊天时间显示格式(含iOS/Android/Web实现)[图文+源码]

[2] 精品文档和工具下载:
计算机网络通讯协议关系图(中文珍藏版)[附件下载]
史上最全即时通讯软件简史(精编大图版)[附件下载]
基于RTMP协议的流媒体技术的原理与应用(技术论文)[附件下载]
独家发布《TCP/IP详解 卷1:协议》CHM版 [附件下载]
良心分享:WebRTC 零基础开发者教程(中文)[附件下载]
MQTT协议手册(中文翻译版)[附件下载]
经典书籍《UNIX网络编程》最全下载(卷1+卷2、中文版+英文版)[附件下载]
音视频开发理论入门书籍之《视频技术手册(第5版)》[附件下载]
国际电联H.264视频编码标准官方技术手册(中文版)[附件下载]
Apache MINA2.0 开发指南(中文版)[附件下载]
网络通讯数据抓包和分析工具 Wireshark 使用教程(中文) [附件下载]
最新收集NAT穿越(p2p打洞)免费STUN服务器列表 [附件下载]
高性能网络编程经典:《The C10K problem(英文)》[附件下载]
即时通讯系统的原理、技术和应用(技术论文)[附件下载]
技术论文:微信对网络影响的技术试验及分析[附件下载]
华为内部3G网络资料: WCDMA系统原理培训手册[附件下载]
网络测试:Android版多路ping命令工具EnterprisePing[附件下载]
Android反编译利器APKDB:没有美工的日子里继续坚强的撸
一款用于P2P开发的NAT类型检测工具 [附件下载]
两款增强型Ping工具:持续统计、图形化展式网络状况 [附件下载]

[3] 精选视频、演讲PPT下载:
QQ空间移动端10亿级视频播放技术优化揭秘(视频+PPT)[附件下载]
RTC实时互联网2017年度大会精选演讲PPT [附件下载]
微信分享开源IM网络层组件库Mars的技术实现(视频+PPT)[附件下载]
微服务理念在微信海量用户后台架构中的实践(视频+PPT)[附件下载]
移动端IM开发和构建中的技术难点实践分享(视频+PPT)[附件下载]
网易云信的高品质即时通讯技术实践之路(视频+PPT)[附件下载]
腾讯音视频实验室:直面音视频质量评估之痛(视频+PPT)[附件下载]
腾讯QQ1.4亿在线用户的技术挑战和架构演进之路PPT[附件下载]
微信朋友圈海量技术之道PPT[附件下载]
手机淘宝消息推送系统的架构与实践(音频+PPT)[附件下载]
如何进行实时音视频的质量评估与监控(视频+PPT)[附件下载]
Go语言构建高并发消息推送系统实践PPT(来自360公司)[附件下载]
网易IM云千万级并发消息处理能力的架构设计与实践PPT [附件下载]
手机QQ的海量用户移动化实践分享(视频+PPT)[附件下载]
钉钉——基于IM技术的新一代企业OA平台的技术挑战(视频+PPT)[附件下载]
微信技术总监谈架构:微信之道——大道至简(PPT讲稿)[附件下载]
Netty的架构剖析及应用案例介绍(视频+PPT)[附件下载]
声网架构师谈实时音视频云的实现难点(视频采访)
滴滴打车架构演变及应用实践(PPT讲稿)[附件下载]
微信海量用户背后的后台系统存储架构(视频+PPT)[附件下载]
在线音视频直播室服务端架构最佳实践(视频+PPT)[附件下载]
从0到1:万人在线的实时音视频直播技术实践分享(视频+PPT)[附件下载]
微信移动端应对弱网络情况的探索和实践PPT[附件下载]
Android版微信从300KB到30MB的技术演进(PPT讲稿)[附件下载]

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

上一篇:微信分享开源IM网络层组件库Mars的技术实现(视频+PPT)[附件下载]下一篇:网易云信的高品质即时通讯技术实践之路(视频+PPT)[附件下载]

本帖已收录至以下技术专辑

推荐方案
评论 14
厉害,学习了
签名: 该会员没有填写今日想说内容.
引用:kezhaoyuan 发表于 2017-02-10 14:44
厉害,学习了

学习了,原来还有这么多的学问!!!!!!!!
签名: 不错!!!!!!!!!!!!!!!
花金币下载了,却发现没有BitmapHelper类
签名: 该会员没有填写今日想说内容.
引用:jackning 发表于 2017-02-12 14:30
花金币下载了,却发现没有BitmapHelper类

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

BitmapCompressHelper.java我今天更新了一下,你再下载一次即可,建议用之前把代码理解一下。
学习啦
签名: 该会员没有填写今日想说内容.
太好了,感觉论坛实际东西太多了,要学习的太多了,自己要好好加油。
签名: 该会员没有填写今日想说内容.
学习了。。
精彩……。
签名: 闲的蛋疼……
效果感觉比Luban好 学习了
签名: 该会员没有填写今日想说内容.
引用:淡云流痕 发表于 2017-08-15 17:08
效果感觉比Luban好 学习了

好东西,回头加到我们的项目中。谢楼主
很感谢
很感谢
打赏楼主 ×
使用微信打赏! 使用支付宝打赏!

返回顶部