Android图片加载内存优化

2023-11-19

利用BitmapFactory.Options实现图片内存优化

通过设置options.inPreferredConfig控制内存占用

  1. 首先准备了一张1280x800的blue_bg.png图片,我们知道这张图片加载到内存默认占用的大小是1280x800x4 = 4096000byte
  Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.blue_bg);
//        默认情况下 BitmapFactory 使用 Bitmap.Config.ARGB_8888 的存储方式来加载图片内容,而在这种存储模式下,每一个像素需要占用 4 个字节
        // 1280x800x4 = 4096000 byte 核算大约4000kb = 4M
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            Log.d("zyl--","bitmap = " + bitmap.getAllocationByteCount() +", ="+bitmap.getByteCount());
        }
  1. 我们知道可以通过设置options.inPreferredConfig,来设置图片加载时候占用的字节ALPHA_8(1byte),RGB_565(2),ARGB_4444(2),ARGB_8888(4)。
 BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPreferredConfig = Bitmap.Config.RGB_565;
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.blue_bg,options);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            Log.d("zyl--","bitmap = " + bitmap.getAllocationByteCount() +", ="+bitmap.getByteCount());
        }
  1. 经过日志分析,发现并没有生效,占用的内存并没有减少一半。大概的原因是
  • 设备加载图片时候,不同android版本对图片编解码的支持不一样
  • 图片的格式也决定了他不能支持通过Bitmap.Config.RGB_565模式去加载,所以会选择默认ARGB_8888模式去加载,所以我们的内存占用并没有减少。
  • 这篇文章会有详细的介绍:文章链接
  • 通过设置不同的inPreferredConfig值真的能减少Bitmap加载时占用的内存么?链接
  1. 我们增加一张one.jpg图片做对比,具体代码如下:
 BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPreferredConfig = Bitmap.Config.RGB_565;
//        options.inSampleSize = 2;
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.blue_bg,options);
//        默认情况下 BitmapFactory 使用 Bitmap.Config.ARGB_8888 的存储方式来加载图片内容,而在这种存储模式下,每一个像素需要占用 4 个字节
        // 1280x800x4 = 4096000 byte 核算大约4000kb = 4M
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            Log.d("zyl--","bitmap = " + bitmap.getAllocationByteCount() +", ="+bitmap.getByteCount());
        }

        Bitmap one = BitmapFactory.decodeResource(getResources(), R.drawable.one,options);
//        默认情况下 BitmapFactory 使用 Bitmap.Config.ARGB_8888 的存储方式来加载图片内容,而在这种存储模式下,每一个像素需要占用 4 个字节
        // 1280x800x4 = 4096000 byte 核算大约4000kb = 4M
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            Log.d("zyl--","one = " + one.getAllocationByteCount() +", ="+one.getByteCount());
        }

通过日志我们发现,jpg图片对于设置inPreferredConfig属性生效了,内存占用减少了一半。进一步证实了图片格式对于inPreferredConfig属性是否生效有一定影响。

通过设置采样率options.inSampleSize来减少图片内存占用

  1. 通过设置采样率options.inSampleSize来减少图片内存占用问题。inSampleSize 参数,可以实现 Bitmap 采样压缩,这个参数的含义是宽高维度上每隔 inSampleSize 个像素进行一次采集。
       BitmapFactory.Options options = new BitmapFactory.Options();
//        options.inPreferredConfig = Bitmap.Config.RGB_565;
        options.inSampleSize = 2;
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.blue_bg,options);
//        默认情况下 BitmapFactory 使用 Bitmap.Config.ARGB_8888 的存储方式来加载图片内容,而在这种存储模式下,每一个像素需要占用 4 个字节
        // 1280x800x4 = 4096000 byte 核算大约4000kb = 4M
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            Log.d("zyl--","bitmap = " + bitmap.getAllocationByteCount() +", ="+bitmap.getByteCount());
        }

通过日志分析,得到当采样率为2的时候,图片占用内存为

: bitmap = 1024000,

内存减少了1/4。

通过设置 Options.inBitmap,使Bitmap 对象重复使用,节省内存

  • 实现点击切换图片的功能,代码如下:
public class MainActivity extends AppCompatActivity {

    private int resIndex;

    int[] resIds = {R.drawable.one, R.drawable.two};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView hello = findViewById(R.id.hello);
        final ImageView iv = findViewById(R.id.iv);
        hello.setOnClickListener(new View.OnClickListener() {
            @RequiresApi(api = Build.VERSION_CODES.KITKAT)
            @Override
            public void onClick(View v) {
                iv.setImageBitmap(getBitmap());

            }
        });
   
        reuseBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.one);
    }

    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    private Bitmap getBitmap() {
        return BitmapFactory.decodeResource(getResources(), resIds[resIndex++ % 2], options);
    }

以上代码运行后,发现当我们切换图片时,内存情况如图:
内存抖动
每次切换图片都需要通过 BitmapFactory 创建一个新的 Bitmap 对象。当方法执行完毕后,这个 Bitmap 又会被 GC 回收,这就造成不断地创建和销毁比较大的内存对象,从而导致频繁 GC(或者叫内存抖动)。像 Android App 这种面相最终用户交互的产品,如果因为频繁的 GC 造成 UI 界面卡顿,还是会影响到用户体验的。可以在 Android Studio Profiler 中查看内存情况。

  • 使用Options.inBitmap,重复利用已经占用内存的 Bitmap 空间,解决内存抖动问题。
public class MainActivity extends AppCompatActivity {

    private int resIndex;

    int[] resIds = {R.drawable.one, R.drawable.two};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView hello = findViewById(R.id.hello);
        final ImageView iv = findViewById(R.id.iv);
        hello.setOnClickListener(new View.OnClickListener() {
            @RequiresApi(api = Build.VERSION_CODES.KITKAT)
            @Override
            public void onClick(View v) {
//                Intent intent = new Intent(MainActivity.this,MyActivity.class);
//                startActivity(intent);
                iv.setImageBitmap(getBitmap());

            }
        });

        BitmapFactory.Options options = new BitmapFactory.Options();
        //设置为true,使这块内存能够复用
        options.inMutable =true;
        reuseBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.one,options);
    }

    /**
     * 重用bitmap
     */
    private Bitmap reuseBitmap;

    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    private Bitmap getBitmap() {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize = 1;
        //为true 只解析bitmap的占用内存大小,不加载bitmap到内存中
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(getResources(), resIds[resIndex % 2], options);
        if (canUseForInBitmap(reuseBitmap, options)) {
            options.inMutable = true;
            options.inBitmap = reuseBitmap;
        }
		//恢复设置
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(getResources(), resIds[resIndex++ % 2], options);
    }

    static boolean canUseForInBitmap(
            Bitmap candidate, BitmapFactory.Options targetOptions) {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            // From Android 4.4 (KitKat) onward we can re-use if the byte size of
            // the new bitmap is smaller than the reusable bitmap candidate
            // allocation byte count.
            int width = targetOptions.outWidth / targetOptions.inSampleSize;
            int height = targetOptions.outHeight / targetOptions.inSampleSize;
            int byteCount = width * height * getBytesPerPixel(candidate.getConfig());
            return byteCount <= candidate.getAllocationByteCount();
        }

        // On earlier versions, the dimensions must match exactly and the inSampleSize must be 1
        return candidate.getWidth() == targetOptions.outWidth
                && candidate.getHeight() == targetOptions.outHeight
                && targetOptions.inSampleSize == 1;
    }

    /**
     * A helper function to return the byte usage per pixel of a bitmap based on its configuration.
     */
    static int getBytesPerPixel(Bitmap.Config config) {
        if (config == Bitmap.Config.ARGB_8888) {
            return 4;
        } else if (config == Bitmap.Config.RGB_565) {
            return 2;
        } else if (config == Bitmap.Config.ARGB_4444) {
            return 2;
        } else if (config == Bitmap.Config.ALPHA_8) {
            return 1;
        }
        return 1;
    }

}

运行后发现内存抖动的问题解决了:
内存图

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android图片加载内存优化 的相关文章

  • 将非 Android 项目添加到 Android 项目

    我在 Eclipse 中有三个项目 Base Server 和 AndroidClient Base和Server是Java 1 7项目 而AndroidClient显然是一个android项目 基础项目具有在服务器和 Android 客户
  • 如何在 Spring 中使 @PropertyResource 优先于任何其他 application.properties ?

    我正在尝试在类路径之外添加外部配置属性资源 它应该覆盖任何现有的属性 但以下方法不起作用 SpringBootApplication PropertySource d app properties public class MyClass
  • Android应用主题更换流畅

    我正在开发一个提供白天和夜间主题的项目 我正在更改主题 夜间主题 AppCompatDelegate setDefaultNightMode AppCompatDelegate MODE NIGHT YES 日主题 AppCompatDel
  • PhoneGap 1.4 封装 Sencha Touch 2.X - 性能怎么样?

    我正在构建一个多平台平板电脑应用程序 仅使用其 Webview 使用 Phonegap 1 4 对其进行包装 然后使用 Sencha Touch 2 框架发挥我的魔力 我所说的多平台是指 iOS 5 X 和 Android 3 0 目前 到
  • 从jar中获取资源

    我有包含文件的 jar myJar res endingRule txt myJar wordcalculator merger Marge class 在 Marge java 中我有代码 private static final Str
  • Java继承,扩展类如何影响实际类

    我正在查看 Sun 认证学习指南 其中有一段描述了最终修饰符 它说 如果程序员可以自由地扩展我们所知的 String 类文明 它可能会崩溃 他什么意思 如果可以扩展 String 类 我是否不会有一个名为 MyString 的类继承所有 S
  • Jetpack Compose 中复选框中的透明复选标记

    在我的 Compose 应用程序中 我需要创建一个圆形复选框 我已经通过下面的代码实现了这一点 Composable fun CircleCheckBox isChecked Boolean modifier Modifier Modifi
  • Android:如何创建模态进度“轮”叠加层?

    我想在我的视图上显示模式进度 轮子 叠加层 ProgressDialog 很接近 但我不想要对话框背景或边框 我尝试设置对话框窗口的背景可绘制 this progressDialog new ProgressDialog Main this
  • 如何将 HTML 链接放入电子邮件正文中?

    我有一个可以发送邮件的应用程序 用 Java 实现 我想在邮件中放置一个 HTML 链接 但该链接显示为普通字母 而不是 HTML 链接 我怎样才能将 HTML 链接放入字符串中 我需要特殊字符吗 太感谢了 Update 大家好你们好 感谢
  • Android 4.4 Kitkat 自定义视图操作栏未填充整个宽度

    我试图拥有一个带有自定义视图的简单操作栏 但我得到以下结果 为了演示 我创建了一个带有黄色背景颜色的简单 xml 它应该占据整个宽度 这是 XML
  • 轻松的反应

    我有一个与这里描述的类似的案例 动态更改RESTEasy服务返回类型 https stackoverflow com questions 3786781 dynamically change resteasy service return
  • hibernate 6.0.2.Final 和 spring boot 2.7.0 的entityManagerFactory bean 未配置问题

    所以最近我想升级我的 Spring Boot 项目项目的一些依赖项 特别是这些组件 雅加达 EE 9 弹簧靴2 7 休眠 6 0 2 Final 完成此操作后 所有更新和代码折射 更新将 javax 导入到 jakarta 以及一些 hib
  • 在java中以原子方式获取多个锁

    我有以下代码 注意 为了可读性 我尽可能简化了代码 如果我忘记了任何关键部分 请告诉我 public class User private Relations relations public User relations new Rela
  • android系统用户和linux root用户有什么区别

    当我将手机连接到电脑并使用adb shell与我的手机通信并输入的命令ps命令输出当前在我的手机上运行的进程信息 我发现有两个特殊用户 一个是root 另一个是system 据我所知 Android是基于linux的 所以root用户是最大
  • Java 正则表达式中的逻辑 AND

    是否可以在 Java Regex 中实现逻辑 AND 如果答案是肯定的 那么如何实现呢 正则表达式中的逻辑 AND 由一系列堆叠的先行断言组成 例如 foo bar glarch 将匹配包含所有三个 foo bar 和 glarch 的任何
  • JetPack Compose - 卡中行中的weight() 不起作用

    创建 Android 应用程序时 我将一些可组合项放在卡片的一行中 如下所示 但它没有按我的预期工作 我添加 weight 1f 的可组合项不再显示 data class Test val title String val text Str
  • 子类构造函数(JAVA)中的重写函数[重复]

    这个问题在这里已经有答案了 为什么在派生类构造函数中调用超类构造函数时 id 0 当创建子对象时 什么时候在堆中为该对象分配内存 在基类构造函数运行之后还是之前 class Parent int id 10 Parent meth void
  • 在线性布局内的 ScrollView 内并排对齐 TextView

    我有一个带有滚动视图的线性布局 我想保留它的当前格式 但只需将 textView2a 和 textView3a 并排放置 而不会破坏我当前的布局格式 我已经包含了我最近的尝试 但它们似乎不正确 提前致谢 Java菜鸟 当前有效的 XML
  • Android Espresso 单击按钮时出现错误

    我正在尝试使用 espresso 框架为 Android 应用程序编写一些 UI 测试 现在我只是检查启动屏幕上是否存在所有元素 然后尝试单击登录按钮 单击按钮时 测试由于错误而失败 我似乎无法理解为什么会发生这种情况 我的测试代码是 Ru
  • java'assert'和'if(){}else exit;'之间的区别

    java和java有什么区别assert and if else exit 我可以用吗if else exit代替assert 也许有点谷歌 您应该记住的主要事情是 if else 语句应该用于程序流程控制 而assert 关键字应该仅用于

随机推荐

  • 深度学习(1):BP神经网络实现银行客户流失预测

    目的 针对银行客户行为和统计数据实现客户流失预测任务 一 数据准备 1 数据集 select data csv 作为训练样本 数据预处理方式 归一化 数值化 CreditScore 信用分数 EB 存贷款情况 EstimatedSalary
  • centos 建立回收站

    linux下的回收站在每一个当前用户目录 local share Trash中 也可以给linux添加一个回收站 mkdir tmp trash tmp 建立一个回收站目录 vi bin trash 编辑一个文件 mv tmp trash
  • python之浅拷贝、深拷贝

    什么是浅拷贝 深拷贝 理论来自python基础教程 在 Python 中 对象赋值实际上是对象的引用 当创建一个对象 然后把它赋给另一个变量的时候 Python 并没有拷贝这个对象 而只是拷贝了这个对象的引用 我们称之为浅拷贝 在 Pyth
  • 腾讯云 Finops Crane 开发者集训营 - 云原生如何助力企业搞定成本优化

    引言 随着docker的技术普及 越来越多的企业加入了云计算发展进程 云原生产业发展迅猛 云原生建设投入比例明显 面对大规模的集群投入 部署 维护等问题也逐渐产生 越来越多的企业对云原生不断提出更高要求 同时云原生技术简化运维的效能提 升开
  • .Net WebAPI 高速下载文件接口实现

    接触WebAPI一年多了 感觉是个承上启下 开创未来的技术 老一辈程序员写接口就像写方法一样 不需要了解太多网页的知识 却可以在浏览器中访问这些接口 由于是基于HTTP协议 因此不管是PC 手机还是嵌入式均可顺利访问 对于当下软件多终端的设
  • Spark 【分区与并行度】

    RDD 并行度和分区 SparkConf setMaster local 我们在创建 SparkContext 对象时通常会指定 SparkConf 参数 它包含了我们运行时的配置信息 如果我们的 setMaster 中的参数是 local
  • 服务器维护中轩辕,轩辕服务器为什么老是-轩辕服务器为什么 – 手机爱问

    网三轩辕为什么上不去了啊 网三轩 朋友 我先问下 有以下的情况吗 第一 你的号上去后 选线的时候是不是请重从连接 要是的话这是卡号了 第二 你上号的时候 写账号和密码 就提事说 从请从新登陆 这不是卡号 这也是卡线了 这是你卡线的时候总来回
  • xray扫描器的使用 (长亭科技公司创造)

    简介 xray是一款可以使用HTTP HTTPS代理进行被动扫描的安全工具 支持功能如下 独立的 URL 扫描 基于 HTTP 的被动代理扫描 同时支持HTTPS SQL注入检测模块 命令注入检测模块 任意重定向检测模块 路径遍历模块 Xr
  • c# Newtonsoft.Json 常用方法总结

    1 实体类的 Json 序列化和反序列化 我们以如下的 Person 类举例 其中包含了常用的数据类型 public class Person public int ID get set public string Name get set
  • Kubernetes之kubectl命令详解及常用示例

    文章目录 一 kubectl语法 二 子命令详解 1 command 2 type 3 flags 4 kubectl的输出格式 三 kubectl常用命令 1 查看类命令 2 操作类命令 3 其他操作 一 kubectl语法 kubect
  • 容器与云的碰撞——一次对MinIO的测试

    事先声明 本次测试过程完全处于本地或授权环境 仅供学习与参考 不存在未授权测试过程 本文提到的漏洞 MinIO未授权SSRF漏洞 CVE 2021 21287 已经修复 也请读者勿使用该漏洞进行未授权测试 否则作者不承担任何责任 随着工作和
  • OAuth2.0-授权码模式

    解决问题 OAuth2 0授权码模式主要解决了信任问题 一个第三方网站需要访问我们Github上的数据 例如用户头像 那Github为什么要信任该网站 该对网站信任到什么程度 如果彻底信任该网站 那么将Github的用户名和密码直接交给该网
  • 计算机网路基础 - 一些基本概念与网络结构

    1 基本概念 计算机网络 通信技术 计算机技术 是两项技术紧密结合的产物 通信系统的基础模型 计算机网络 是指将地理位置不同 具有独立功能的多台计算机及其外部设备 通过通信线路连接 在网络操作系统 网络管理软件及网络通信协议的管理和协调下
  • 【mysql批量插入或更新方法实现】

    自定义批量插入或更新 1 创建接口 替代baseMapper public interface RootMapper
  • CentOS搭建vsftp

    VSFTPD Very Secure FTP Daemon 是一个在 CentOS 中常用的 FTP 服务器软件 它是一个轻量级 安全且高性能的 FTP 服务器 基于 RFC 959 协议 并实现了大多数 FTP 协议的功能 VSFTPD
  • ObjectARX学习

    VS2010 VS2012 VS2013 VS2015 VS2017各版本产品激活秘钥 ObjectARX简介 ObjectARX解压包内各文件代表什么 VS ObjectARX SDK AutoCAD对应关系 1 VS2010 VS201
  • Flutter websocket 实现消息推送

    没什么可说的 直接上代码吧 Flutter 中的消息管理工具 class MessageUtils static WebSocket webSocket static num id 0 static void connect Future
  • NGINX实现TCP加密代理

    NGINX实现TCP加密代理 NGINX实现TCP代理 源码安装NGINX 修改配置文件 重启NGINX 测试 实现转发MySql Redis 矿池ssl 以及各种TCP转发 NGINX实现TCP代理 源码安装NGINX NGINX官网源码
  • 数据挖掘(全书的知识点都包括了)

    数据挖掘 第一章 1 什么是数据挖掘 数据挖掘是从数据中 发现其有用的信息 从而帮助我们做出决策 广义角度 数据挖掘是从大量的 不完全的 有噪声的 模糊的 随机的实际应用数据中 提取隐含在其中的 人们事先不知道的 但又是潜在有用的信息和知识
  • Android图片加载内存优化

    Android图片加载内存优化 利用BitmapFactory Options实现图片内存优化 通过设置options inPreferredConfig控制内存占用 通过设置采样率options inSampleSize来减少图片内存占用