热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Android性能优化系列之bitmap图片优化

在日常开发的APP,大部分时候需要想用户展示图片信息,图片最终对应Android中的Bitmap对象。而对于APP端来说Bitmap又是一个比较麻烦的问题,主要表现在Bitmap是非常占用内存的对象,处理不当将导致APP运行卡顿甚至出现OOM

背景

Android开发中,加载图片过多、过大很容易引起OutOfMemoryError异常,即我们常见的内存溢出。因为Android对单个应用施加内存限制,默认分配的内存只有几M(具体视不同系统而定)。而载入的图片如果是JPG之类的压缩格式(JPG支持最高级别的压缩,不过该压缩是有损的),在内存中展开会占用大量的内存空间,也就容易形成内存溢出。那么高效的加载Bitmap是很重要的事情。Bitmap在Android中指的是一张图片,图片的格式有.jpg .jpg .webp 等常见的格式。

如何选择图片格式

一个原则: 在保证图片视觉不失真前提下,尽可能的缩小体积

Android目前常用的图片格式有jpg,jpeg和webp

  • png:无损压缩图片格式,支持Alpha通道,Android切图素材多采用此格式
  • jpeg:有损压缩图片格式,不支持背景透明,适用于照片等色彩丰富的大图压缩,不适合logo
  • webp:是一种同时提供了有损压缩和无损压缩的图片格式,派生自视频编码格式VP8,从谷歌官网来看,无损webp平均比jpg小26%,有损的webp平均比jpeg小25%~34%,无损webp支持Alpha通道,有损webp在一定的条件下同样支持,有损webp在Android4.0(API 14)之后支持,无损和透明在Android4.3(API18)之后支持

采用webp能够在保持图片清晰度的情况下,可以有效减小图片所占有的磁盘空间大小

图片压缩

图片压缩可以从三个方面去考虑:

1.质量

质量压缩并不会改变图片在内存中的大小,仅仅会减小图片所占用的磁盘空间的大小,因为质量压缩不会改变图片的分辨率,而图片在内存中的大小是根据widthheight一个像素的所占用的字节数计算的,宽高没变,在内存中占用的大小自然不会变,质量压缩的原理是通过改变图片的位深和透明度来减小图片占用的磁盘空间大小,所以不适合作为缩略图,可以用于想保持图片质量的同时减小图片所占用的磁盘空间大小。另外,由于jpg是无损压缩,所以设置quality无效,

  /**
 * 质量压缩
 *
 * @param format  图片格式 jpeg,png,webp
 * @param quality 图片的质量,0-100,数值越小质量越差
 */
public static void compress(Bitmap.CompressFormat format, int quality) {
    File sdFile = Environment.getExternalStorageDirectory();
    File originFile = new File(sdFile, "originImg.jpg");
    Bitmap originBitmap = BitmapFactory.decodeFile(originFile.getAbsolutePath());
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    originBitmap.compress(format, quality, bos);
    try {
        FileOutputStream fos = new FileOutputStream(new File(sdFile, "resultImg.jpg"));
        fos.write(bos.toByteArray());
        fos.flush();
        fos.close();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

2.采样率

采样率压缩是通过设置BitmapFactory.Options.inSampleSize,来减小图片的分辨率,进而减小图片所占用的磁盘空间和内存大小。

设置的inSampleSize会导致压缩的图片的宽高都为1/inSampleSize,整体大小变为原始图片的inSampleSize平方分之一,当然,这些有些注意点:

  • inSampleSize小于等于1会按照1处理
  • inSampleSize只能设置为2的平方,不是2的平方则最终会减小到最近的2的平方数,如设置7会按4进行压缩,设置15会按8进行压缩。
/**
 * 
 * @param inSampleSize  可以根据需求计算出合理的inSampleSize
 */
public static void compress(int inSampleSize) {
    File sdFile = Environment.getExternalStorageDirectory();
    File originFile = new File(sdFile, "originImg.jpg");
    BitmapFactory.Options optiOns= new BitmapFactory.Options();
    //设置此参数是仅仅读取图片的宽高到options中,不会将整张图片读到内存中,防止oom
    options.inJustDecodeBounds = true;
    Bitmap emptyBitmap = BitmapFactory.decodeFile(originFile.getAbsolutePath(), options);

    options.inJustDecodeBounds = false;
    options.inSampleSize = inSampleSize;
    Bitmap resultBitmap = BitmapFactory.decodeFile(originFile.getAbsolutePath(), options);
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    resultBitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);
    try {
        FileOutputStream fos = new FileOutputStream(new File(sdFile, "resultImg.jpg"));
        fos.write(bos.toByteArray());
        fos.flush();
        fos.close();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

3.缩放

通过减少图片的像素来降低图片的磁盘空间大小和内存大小,可以用于缓存缩略图

 /**
     *  缩放bitmap
     * @param context
     * @param id
     * @param maxW
     * @param maxH
     * @return
     */
    public static Bitmap resizeBitmap(Context context,int id,int maxW,int maxH,boolean hasAlpha,Bitmap reusable){
        Resources resources = context.getResources();
        BitmapFactory.Options optiOns= new BitmapFactory.Options();
        // 只解码出 outxxx参数 比如 宽、高
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(resources,id,options);
        //根据宽、高进行缩放
        int w = options.outWidth;
        int h = options.outHeight;
        //设置缩放系数
        options.inSampleSize = calcuteInSampleSize(w,h,maxW,maxH);
        if (!hasAlpha){
            options.inPreferredCOnfig= Bitmap.Config.RGB_565;
        }
        options.inJustDecodeBounds = false;
        //设置成能复用
        options.inMutable=true;
        options.inBitmap=reusable;
        return BitmapFactory.decodeResource(resources,id,options);
    }

    /**
     * 计算缩放系数
     * @param w
     * @param h
     * @param maxW
     * @param maxH
     * @return 缩放的系数
     */
    private static int calcuteInSampleSize(int w,int h,int maxW,int maxH) {
        int inSampleSize = 1;
        if (w > maxW && h > maxH){
            inSampleSize = 2;
            //循环 使宽、高小于 最大的宽、高
            while (w /inSampleSize > maxW && h / inSampleSize > maxH){
                inSampleSize *= 2;
            }
        }
        return inSampleSize;
    }
}
  • 使用JPEG库,在jni层使用哈夫曼算法去压缩图片

Android的图片引擎使用的是阉割版的skia引擎,去掉了图片压缩中的哈夫曼算法

void write_JPEG_file(uint8_t *data, int w, int h, jint q, const char *path) {
//    3.1、创建jpeg压缩对象
    jpeg_compress_struct jcs;
    //错误回调
    jpeg_error_mgr error;
    jcs.err = jpeg_std_error(&error);
    //创建压缩对象
    jpeg_create_compress(&jcs);
//    3.2、指定存储文件  write binary
    FILE *f = fopen(path, "wb");
    jpeg_stdio_dest(&jcs, f);
//    3.3、设置压缩参数
    jcs.image_width = w;
    jcs.image_height = h;
    //bgr
    jcs.input_compOnents= 3;
    jcs.in_color_space = JCS_RGB;
    jpeg_set_defaults(&jcs);
    //开启哈夫曼功能
    jcs.optimize_coding = true;
    jpeg_set_quality(&jcs, q, 1);
//    3.4、开始压缩
    jpeg_start_compress(&jcs, 1);
//    3.5、循环写入每一行数据
    int row_stride = w * 3;//一行的字节数
    JSAMPROW row[1];
    while (jcs.next_scanline 

因为涉及到jni部分,暂时只贴一下使用的代码,后面会写一些jni部分的博客与大家分享。

  • 设置图片可以复用

图片复用主要就是指的复用内存块,不需要在重新给这个bitmap申请一块新的内存,避免了一次内存的分配和回收,从而改善了运行效率。

需要注意的是inBitmap只能在3.0以后使用。2.3上,bitmap的数据是存储在native的内存区域,并不是在Dalvik的内存堆上。

使用inBitmap,在4.4之前,只能重用相同大小的bitmap的内存区域,而4.4之后你可以重用任何bitmap的内存区域,只要这块内存比将要分配内存的bitmap大就可以。这里最好的方法就是使用LRUCache来缓存bitmap,后面来了新的bitmap,可以从cache中按照api版本找到最适合重用的bitmap,来重用它的内存区域。

   BitmapFactory.Options optiOns= new BitmapFactory.Options();
        // 只解码出 outxxx参数 比如 宽、高
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(resources,id,options);
        //根据宽、高进行缩放
        int w = options.outWidth;
        int h = options.outHeight;
        //设置缩放系数
        options.inSampleSize = calcuteInSampleSize(w,h,maxW,maxH);
        if (!hasAlpha){
            options.inPreferredCOnfig= Bitmap.Config.RGB_565;
        }
        options.inJustDecodeBounds = false;
        //设置成能复用
        options.inMutable=true;
        options.inBitmap=reusable;
  • 使用图片缓存

android中有一个LruCache是基于最记最少使用算法实现的一个线程安全的数据缓存类,当超出设定的缓存容量时,优先淘汰最近最少使用的数据LruCache的LRU缓存策略是利用LinkedHashMap来实现的,并通过封装get/put等相关方法来实现控制缓存大小以及淘汰元素,但不支持为null的key和value。 我们可以使用JakeWharton提供的一个开源库github.com/JakeWharton… 来实现我们图片缓存的逻辑

省略了内存和磁盘的部分。

到此这篇关于Android 性能优化系列之bitmap图片优化的文章就介绍到这了,更多相关Android 性能优化内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!


推荐阅读
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 本文将介绍如何编写一些有趣的VBScript脚本,这些脚本可以在朋友之间进行无害的恶作剧。通过简单的代码示例,帮助您了解VBScript的基本语法和功能。 ... [详细]
  • 使用Python在SAE上开发新浪微博应用的初步探索
    最近重新审视了新浪云平台(SAE)提供的服务,发现其已支持Python开发。本文将详细介绍如何利用Django框架构建一个简单的新浪微博应用,并分享开发过程中的关键步骤。 ... [详细]
  • 深入理解Redis的数据结构与对象系统
    本文详细探讨了Redis中的数据结构和对象系统的实现,包括字符串、列表、集合、哈希表和有序集合等五种核心对象类型,以及它们所使用的底层数据结构。通过分析源码和相关文献,帮助读者更好地理解Redis的设计原理。 ... [详细]
  • Explore a common issue encountered when implementing an OAuth 1.0a API, specifically the inability to encode null objects and how to resolve it. ... [详细]
  • 技术分享:从动态网站提取站点密钥的解决方案
    本文探讨了如何从动态网站中提取站点密钥,特别是针对验证码(reCAPTCHA)的处理方法。通过结合Selenium和requests库,提供了详细的代码示例和优化建议。 ... [详细]
  • 本文基于刘洪波老师的《英文词根词缀精讲》,深入探讨了多个重要词根词缀的起源及其相关词汇,帮助读者更好地理解和记忆英语单词。 ... [详细]
  • 本文详细介绍了 Dockerfile 的编写方法及其在网络配置中的应用,涵盖基础指令、镜像构建与发布流程,并深入探讨了 Docker 的默认网络、容器互联及自定义网络的实现。 ... [详细]
  • 本文详细探讨了VxWorks操作系统中双向链表和环形缓冲区的实现原理及使用方法,通过具体示例代码加深理解。 ... [详细]
  • 基因组浏览器中的Wig格式解析
    本文详细介绍了Wiggle(Wig)格式及其在基因组浏览器中的应用,涵盖variableStep和fixedStep两种主要格式的特点、适用场景及具体使用方法。同时,还提供了关于数据值和自定义参数的补充信息。 ... [详细]
  • 基于KVM的SRIOV直通配置及性能测试
    SRIOV介绍、VF直通配置,以及包转发率性能测试小慢哥的原创文章,欢迎转载目录?1.SRIOV介绍?2.环境说明?3.开启SRIOV?4.生成VF?5.VF ... [详细]
  • 本文详细介绍了Java中org.eclipse.ui.forms.widgets.ExpandableComposite类的addExpansionListener()方法,并提供了多个实际代码示例,帮助开发者更好地理解和使用该方法。这些示例来源于多个知名开源项目,具有很高的参考价值。 ... [详细]
  • 使用 Azure Service Principal 和 Microsoft Graph API 获取 AAD 用户列表
    本文介绍了一段通用代码示例,该代码不仅能够操作 Azure Active Directory (AAD),还可以通过 Azure Service Principal 的授权访问和管理 Azure 订阅资源。Azure 的架构可以分为两个层级:AAD 和 Subscription。 ... [详细]
  • 如何配置Unturned服务器及其消息设置
    本文详细介绍了Unturned服务器的配置方法和消息设置技巧,帮助用户了解并优化服务器管理。同时,提供了关于云服务资源操作记录、远程登录设置以及文件传输的相关补充信息。 ... [详细]
  • 在当前众多持久层框架中,MyBatis(前身为iBatis)凭借其轻量级、易用性和对SQL的直接支持,成为许多开发者的首选。本文将详细探讨MyBatis的核心概念、设计理念及其优势。 ... [详细]
author-avatar
雇Anitamia
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有