Android性能优化—图片优化

图片优化是内存优化中很重要的一部分,加载Bitmap时往往需要消耗大量的内存,稍不注意就容易导致内存溢出(OOM)。

一、图片OOM问题产生

1、 一个页面一次加载过多图片;

2、加载大图片没有进行压缩(尺寸,质量);

3、列表页面加载大量bitmap没有使用缓存。

了解图片产生OOM问题的原因,接下来我们将要通过这几个方面对图片进行优化,在此之前我们还需要知道加载一张图片到APP中需要消耗多大的内存,是什么计算的?

二、获取Bitmap的大小

1、getByteCount()

getByteCount()方法是在API12加入的,代表存储Bitmap的色素需要的最少内存。API19开始
getAllocationByteCount()方法代替了getByteCount()。

2、getAllocationByteCount()

API19之后,Bitmap加了一个Api:getAllocationByteCount();代表在内存中为Bitmap分配的内存大小。

public final int getAllocationByteCount() {if (mBuffer == null) {//mBuffer代表存储Bitmap像素数据的字节数组。return getByteCount();}return mBuffer.length;
}

3、getByteCount()与getAllocationByteCount()的区别

一般情况下两者是相等的;通过复用Bitmap来解码图片,如果被复用的Bitmap的内存比待分配内存的Bitmap大,那么getByteCount()表示新解码图片占用内存的大小(并非实际内存大小,实际大小是复用的那个Bitmap的大小),getAllocationByteCount()表示被复用Bitmap真实占用的内存大小(即mBuffer的长度)。

三、Bitmap占用内存大小计算

Bitmap作为位图,需要读入一张图片每一个像素点的数据,其主要占用内存的地方也正是这些像素数据。对于像素数据总大小,我们可以猜想为:像素总数量 × 每个像素的字节大小,而像素总数量在矩形屏幕表现下,应该是:横向像素数量 × 纵向像素数量,

结合得到:

Bitmap内存占用 ≈ 像素数据总大小 = 横向像素数量 × 纵向像素数量 × 每个像素的字节大小

注意:上面的计算方法适用于网络和本地等图片计算,不适用于加载APP项目的drawable和mipmap文件的图片。

在android源码中,加载drawable和mipmap图片,跟density有关,而density和图片存放的资源文件的目录有关,同一张图片放置在不同目录下会有不同的值:

可以验证几个结论:

1. 图片放在drawable中,等同于放在drawable-mdpi中,原因为:drawable目录不具有屏幕密度特
性,所以采用基准值,即mdpi

2. 图片放在某个特定drawable中,比如drawable-hdpi,如果设备的屏幕密度高于当前drawable目
录所代表的密度,则图片会被放大,否则会被缩小放大或缩小比例 = 设备屏幕密度 / drawable目录所代表的屏幕密度因此,关于Bitmap占用内存大小的公式,

从之前:

Bitmap内存占用 ≈ 像素数据总大小 = 横向像素数量 × 纵向像素数量 × 每个像素的字节大小

可以更细化为:

Bitmap内存占用 ≈ 像素数据总大小 = 图片宽 × 图片高× (设备分辨率/资源目录分辨率)^2 × 每个像
素的字节大小 

知道图片内存大小的计算后,我们便可以从图片宽、高和每个像素点占用的字节数等方面对图片进行压缩优化处理。

四、图片存储优化

Android系统加载Bitmap给我们提供了很多API,常用的BitmapFactory工厂类:

Option 参数类:

public boolean inJustDecodeBounds

如果设置为 true ,在不获取图片,不分配内存时,可以返回图片的高度宽度信息。即设置为 true ,在解码的时将不会返回 bitmap ,只返回这个 bitmap 的尺寸。

public int inSampleSize

图片缩放的倍数, 这个值是一个 int ,当它小于1的时候,将会被当做1处理,如果大于1,那么就会按照比例 (1 / inSampleSize) 缩小 bitmap 的宽和高、降低分辨率,inSampleSize只能设置为2的倍数。

public int outWidth

获取图片的宽度值

public int outHeight

获取图片的高度值 ,表示这个 Bitmap 的宽和高,一般和inJustDecodeBounds 一起使用来获得 Bitmap 的宽高,但是不加载到内存。

public Bitmap.Config inPreferredConfig

设置解码器,这个值是设置色彩模式,默认值是 ARGB_8888 ,在这个模式下,一个像素点占用4bytes空间,一般对透明度不做要求的话,一般采用 RGB_565 模式,这个模式下一个像素点占用2bytes。

Bitmap类:

bitmap.compress(Bitmap.CompressFormat.JPEG, 30, baos);

30 是压缩率,表示压缩70%; 如果不压缩是100,表示压缩率为0。

BitmapRegionDecoder类:

decoder.decodeRegion(rect, null);

按坐标分部加载需要显示的部分图像。

1、尺寸(采样率)压缩

我们在加载Bitmap显示到ImageView的时候往往是不需要加载原图的,当ImageView宽高小于Bitmap时,我们可以将Bitmap宽高压缩到mageView宽高相似大小,再加载到内存中。

代码实现:

public static Bitmap pathToBitmap(String srcPath) {BitmapFactory.Options newOpts = new BitmapFactory.Options();// 开始读入图片,此时把options.inJustDecodeBounds 设回true了newOpts.inJustDecodeBounds = true;Bitmap bitmap = BitmapFactory.decodeFile(srcPath, newOpts);// 此时返回bm为空int w = newOpts.outWidth;int h = newOpts.outHeight;// 假设这里ImageView的宽高为400*400,这里可以根据ImageView动态计算float hh = 400f;float ww = 400f;// 缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可int be = 1;// be=1表示不缩放if (w > h && w > ww) {// 如果宽度大的话根据宽度固定大小缩放be = (int) (newOpts.outWidth / ww);} else if (w < h && h > hh) {// 如果高度高的话根据宽度固定大小缩放be = (int) (newOpts.outHeight / hh);}if (be <= 0)be = 1;newOpts.inSampleSize = be;// 设置缩放比例newOpts.inJustDecodeBounds = false;// 重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了bitmap = BitmapFactory.decodeFile(srcPath, newOpts);return bitmap;}

2、解码率压缩

Bitmap解码器默认是 ARGB_8888 ,我们可以将解码器设置为RGB_565,再减少一倍的内存。

public static Bitmap pathToBitmap(String srcPath) {BitmapFactory.Options newOpts = new BitmapFactory.Options();// 开始读入图片,此时把options.inJustDecodeBounds 设回true了newOpts.inJustDecodeBounds = true;Bitmap bitmap = BitmapFactory.decodeFile(srcPath, newOpts);// 此时返回bm为空int w = newOpts.outWidth;int h = newOpts.outHeight;// 假设这里ImageView的宽高为400*400,这里可以根据ImageView动态计算float hh = 400f;float ww = 400f;// 缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可int be = 1;// be=1表示不缩放if (w > h && w > ww) {// 如果宽度大的话根据宽度固定大小缩放be = (int) (newOpts.outWidth / ww);} else if (w < h && h > hh) {// 如果高度高的话根据宽度固定大小缩放be = (int) (newOpts.outHeight / hh);}if (be <= 0)be = 1;newOpts.inSampleSize = be;// 设置缩放比例newOpts.inPreferredConfig = Config.RGB_565; // 降低图片从ARGB888到RGB565newOpts.inJustDecodeBounds = false;// 重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了bitmap = BitmapFactory.decodeFile(srcPath, newOpts);return bitmap;}

3、质量压缩

质量压缩法:不减少图片本身的像素,它在保持像素的前提下该变图片的位深以及透明度,来达到压缩图片的目的,压缩后的文件大小会有所改变,但是导入成 bitmap后所占内存是不会变化的。

public static Bitmap zipBitmap(Bitmap image, int size) {try {ByteArrayOutputStream baos = new ByteArrayOutputStream();image.compress(CompressFormat.JPEG, 100, baos);int options = 100;System.out.println("options" + options + ",baos" + baos.toByteArray().length / 1024);while (baos.toByteArray().length / 1024 > size) {    //循环判断如果压缩后图片是否大于size kb,大于继续压缩baos.reset();//重置baos即清空baosoptions -= 10;//每次都减少10image.compress(CompressFormat.JPEG, options, baos);//这里压缩options%,把压缩后的数据存放到baos中}ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);return bitmap;//压缩好比例大小后再进行质量压缩} catch (Exception e) {e.printStackTrace();}return image;}

注意:第一个参数不能为CompressFormat.PNG,PNG格式是无损的,它无法再进行质量压缩,quality这个参数就没有作用了,会被忽略,所以最后图片保存成的文件大小不会有变化;
可以设置为CompressFormat.JPEG和CompressFormat.WEBP;质量压缩不会改变Bitmap本身的内存大小,改变的是压缩后保存成文件的大小。

4、分部加载超大图:

在特殊场景我们需要清晰的显示一张超大图的时候,我们可以使用自定义View,通过滑动去分部加载超大图。避免一次性将整张大图加载到内存中而导致OOM问题。

InputStream inputStream = ...; // 输入流,可以是网络流或本地文件流
BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(inputStream, false); // 创建BitmapRegionDecoder对象
int width = decoder.getWidth(); // 获取完整图像的宽度
int height = decoder.getHeight(); // 获取完整图像的高度
Rect rect = new Rect(0, 0, width / 2, height / 2); // 需要显示的部分图像的矩形范围
Bitmap bitmap = decoder.decodeRegion(rect, null); // 加载需要显示的部分图像
imageView.setImageBitmap(bitmap); // 显示加载的部分图像

5、多级缓存

使用图片缓存:通过使用图片缓存,可以避免重复加载图片,从而提高应用程序的性能。可以使用LruCache或DiskLruCache来实现图片缓存。 

1)LruCache

LruCache是Android中的缓存类,用于缓存对象并在缓存满时自动移除最近最少使用的对象。LruCache使用了LRU(Least Recently Used)算法来维护缓存的对象,即将最近最少使用的对象移除,以便为新对象腾出空间。

LruCache的实现代码:

int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); // 获取应用程序最大可用内存
int cacheSize = maxMemory / 8; // 设置缓存大小为最大可用内存的1/8
LruCache<String, Bitmap> memoryCache = new LruCache<String, Bitmap>(cacheSize) {@Overrideprotected int sizeOf(String key, Bitmap bitmap) {return bitmap.getByteCount() / 1024; // 返回图片占用的内存大小(单位:KB)}
}; // 创建LruCache对象String url = "http://www.example.com/image.jpg";
Bitmap bitmap = memoryCache.get(url); // 从缓存中获取图片
if (bitmap == null) {// 如果缓存中不存在该图片,则从网络加载该图片bitmap = loadImageFromNetwork(url);// 将加载的图片添加到缓存中memoryCache.put(url, bitmap);
}
imageView.setImageBitmap(bitmap); // 显示图片
2)DiskLruCache

DiskLruCache是一个非Google官方编写,但获得官方认证的三方库,用于缓存数据到磁盘上,并在缓存满时自动移除最近最少使用的数据。与LruCache类似,DiskLruCache也使用了LRU(Least Recently Used)算法来维护缓存的数据。

DiskLruCache的使用代码:

File cacheDir = getExternalCacheDir(); // 缓存目录
int cacheVersion = 1; // 缓存版本号
long cacheSize = 10 * 1024 * 1024; // 缓存大小(10MB)DiskLruCache diskCache = DiskLruCache.open(cacheDir, cacheVersion, 1, cacheSize); // 创建DiskLruCache对象String key = "example";
String value = "Hello, world!";// 将数据添加到缓存中
DiskLruCache.Editor editor = diskCache.edit(key);
OutputStream os = editor.newOutputStream(0);
os.write(value.getBytes());
editor.commit();// 从缓存中获取数据
DiskLruCache.Snapshot snapshot = diskCache.get(key);
if (snapshot != null) {String result = snapshot.getString(0);Log.d("DiskLruCache", result);
}
3)三方图片加载库

除了使用上述的图片优化方法,在项目开发中,我们可以使用常用的三方图片加载库包括Glide和Picasso等,来帮助应用程序更高效地加载图片,并自动处理图片的优化和缓存。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/22231.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux Ubuntu 更新镜像源、安装sudo、nvtop

1.更换镜像源 vi ~/.pip/pip.conf在打开的文件中输入: pip.conf [global] index-url https://pypi.tuna.tsinghua.edu.cn/simple按下:wq保存并退出。 2.安装nvtop 如果输入指令apt install nvtop报错&#xff1a; E: Unable to locate package nvtop 需要更新一下apt&a…

国产GOWIN实现低成本实现CSI MIPI转换DVP

CSI MIPI转换DVP&#xff0c;要么就是通用IC操作&#xff0c;如龙讯芯片和索尼芯片&#xff0c;但是复杂的寄存器控制器实在开发太累。对于FPGA操作&#xff0c;大部分都是用xilinx的方案&#xff0c;xilinx方案成本太高&#xff0c;IP复杂。 而用国产GOWIN已经实现了直接mipi …

Emacs之set-face-attribute与font-lock-add-keywords用法区别(一百二十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

JVM GC ROOT分析

GC root原理:通过对枚举GCroot对象做引用可达性分析,即从GC root对象开始,向下搜索,形成的路径称之为 引用链。如果一个对象到GC roots对象没有任何引用,没有形成引用链,那么该对象等待GC回收,换而言之,如果减少内存泄漏,也就是切断引用链,常见的GCRoot对象如下: 1、…

19-2.vuex

目录 1 安装 2 挂载 2.1 vue2写法 2.2 vue3写法 3 state 3.1 声明数据 3.2 使用数据 3.3 处理数据 4 mutations 4.1 基本使用 4.2 传递参数 4.3 mutations中不能写异步的代码 5 actions 5.1 基本使用 5.2 传递参数 6 getters Vuex是做全局数据…

Spring:JDBCTemplate

JDBCTemplate 概述 概述 JDBC&#xff08;Java DataBase Connectivity&#xff0c;Java 数据库连接&#xff09;&#xff0c; 一 种用于执行 SQL 语句的 Java API&#xff08;Application Programming Interface &#xff0c; 应用程序设计接口 &#xff09;&#xff0c;可以为…

2023年08月IDE流行度最新排名

点击查看最新IDE流行度最新排名&#xff08;每月更新&#xff09; 2023年08月IDE流行度最新排名 顶级IDE排名是通过分析在谷歌上搜索IDE下载页面的频率而创建的 一个IDE被搜索的次数越多&#xff0c;这个IDE就被认为越受欢迎。原始数据来自谷歌Trends 如果您相信集体智慧&am…

玄子Share - Mybatis 项目模板使用指南

玄子Share - Mybatis 项目模板使用指南 项目结构图 mybatis-config.xml 配置模板设置 参数 <?xml version"1.0" encoding"UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "https://mybatis.…

【深度学习_TensorFlow】激活函数

写在前面 上篇文章我们了解到感知机使用的阶跃函数和符号函数&#xff0c;它们都是非连续&#xff0c;导数为0的函数&#xff1a; 建议回顾上篇文章&#xff0c;本篇文章将介绍神经网络中的常见激活函数&#xff0c;这些函数都是平滑可导的&#xff0c;适合于梯度下降算法。 写…

组合总和 II——力扣40

文章目录 题目描述法一 回溯 题目描述 法一 回溯 class Solution{ public:vector<pair<int, int>>freq;vector<vector<int>> res;vector<int> seq;void dfs(int pos, int rest){//如果目标值为0&#xff0c;说明可能有一个组合或者rest本身为0 …

落地数字化管理,提升企业市场竞争力

数字化企业管理方案是一种利用数字技术和信息系统来提升企业管理效率和运营效果的策略。 潜在的数字化企业管理方案 1、企业资源规划&#xff08;ERP&#xff09;系统&#xff1a;建立一个集成的ERP系统来统一管理企业的各项业务流程&#xff0c;包括采购、销售、库存管理、财…

使用上 Spring 的事件机制

本文主要是简单的讲述了Spring的事件机制&#xff0c;基本概念&#xff0c;讲述了事件机制的三要素事件、事件发布、事件监听器。如何实现一个事件机制&#xff0c;应用的场景&#xff0c;搭配Async注解实现异步的操作等等。希望对大家有所帮助。 Spring的事件机制的基本概念 …

npm -v无法显示版本号

情况&#xff1a; 删除C盘下.npmrc文件后解决。路径 C:\Users\Dell 记录一下这个解法。

Linux CentOS系统怎么下载软件

Linux CenOS系统想要下载软件可以在Linux内置的应用商店&#xff0c;并通过Yum 包管理器来下载&#xff08;直接使用yum命令下载软件&#xff09; 在Linux系统中&#xff0c;Yum&#xff08;Yellowdog Updater, Modified&#xff09;是用于管理RPM软件包的一个包管理器。 安装…

npm更新和管理已发布的包

目录 1、更改包的可见性 1.1 将公共包设为私有 ​编辑 使用网站 使用命令行 1.2 将私有包公开 使用网站 使用命令行 2、将协作者添加到用户帐户拥有的私有包 2.1 授予对Web上私有用户包的访问权限 2.2 从命令行界面授予私有包访问权限 2.3 授予对私有组织包的访问权限…

shell命令

#!/bin/bash read -p "请输入一个文件名&#xff1a;" fileName posexpr index $fileName \. typeexpr substr $fileName $((pos1)) 2if [ $type sh ] thenif [ -x $fileName ]thenbash $fileNameelsechmod ax $fileNamefi firead -p "请输入第一个文件名&…

运输层---概述

目录 运输层主要内容一.概述和传输层服务1.1 概述1.2 传输服务和协议1.3 传输层 vs. 网络层1.4 Internet传输层协议 二. 多路复用与多路分解&#xff08;解复用&#xff09;2.1 概述2.2 无连接与面向连接的多路分解&#xff08;解复用&#xff09;2.3面向连接的多路复用*2.4 We…

arcgis字段计算器

1、两字段叠加。要求待叠加的字段类型为文本或字符串类型。如下&#xff1a; 2、字符串部分提取。

坚鹏:中国邮储银行金融科技前沿技术发展与应用场景第2期培训

中国邮政储蓄银行金融科技前沿技术发展与应用场景第2期培训圆满结束 中国邮政储蓄银行拥有优良的资产质量和显著的成长潜力&#xff0c;是中国领先的大型零售银行。2016年9月在香港联交所挂牌上市&#xff0c;2019年12月在上交所挂牌上市。中国邮政储蓄银行拥有近4万个营业网点…

性能测试怎么做?测试工具怎么选择?

在当前软件测试行业&#xff0c;熟练掌握性能测试已经是测试工程师们面试的敲门砖了&#xff0c;当然还有很多测试朋友们每天的工作更多的是点点点&#xff0c;性能方面可能也只是做过简单的并发测试&#xff0c;对于编写脚本&#xff0c;搭建环境方面也比较陌生。今天这篇文章…