当前位置: 首页 > news >正文

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等,来帮助应用程序更高效地加载图片,并自动处理图片的优化和缓存。

相关文章:

Android性能优化—图片优化

图片优化是内存优化中很重要的一部分&#xff0c;加载Bitmap时往往需要消耗大量的内存&#xff0c;稍不注意就容易导致内存溢出&#xff08;OOM&#xff09;。 一、图片OOM问题产生 1、 一个页面一次加载过多图片&#xff1b; 2、加载大图片没有进行压缩(尺寸&#xff0c;质…...

如何搭建自动化测试框架?资深测试整理的PO模式,一套打通自动化...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 Po模型介绍 1、简…...

软件外包开发的GO语言特点

Go语言&#xff08;也称为Golang&#xff09;是由Google开发的一种编程语言。它具有许多特点&#xff0c;使其成为许多项目范围的优秀选择。Go语言适用于需要高性能、并发和简洁易读的项目&#xff0c;特别是面向网络和分布式应用的项目。今天和大家分享项目的特点及适用的项目…...

【深度学习Week4】MobileNet_ShuffleNet

报错&#xff1a;unsafe legacy renegotiation disabled 解决方案&#xff1a; 尝试了更换cryptography36.0.2版本&#xff0c;以及更换下载链接的方法&#xff0c;都不行&#xff0c;最后采用了手动下载mat文件并上传到colab的方法 高光谱图像分类数据集简介Indian Pines&…...

649. Dota2 参议院

题目描述&#xff1a; 主要思路&#xff1a; 这是一个按照题意模拟的问题&#xff0c;利用队列模拟议员的投票顺序即可。 class Solution { public:string predictPartyVictory(string senate) {queue<int> r,d;int nsenate.length();for(int i0;i<n;i){if(senate[i…...

无人机管控平台,推动电力巡检管理水平提升

各地区无人机作业水平和管理水平存在参差不齐&#xff0c;电力巡检管理要求与业务发展水平不匹配的问题。同时&#xff0c;巡检数据的存储和管理分散&#xff0c;缺乏有效的整合与共享手段&#xff0c;使得内外业脱节&#xff0c;没有形成统一应用和闭环管理。这就导致巡检数据…...

阿里云平台WoSignSSL证书应用案例

沃通CA与阿里云达成合作并在阿里云平台上线WoSign品牌SSL证书。自上线以来&#xff0c;WoSignSSL证书成为阿里云“数字证书管理服务”热销证书产品&#xff0c;获得阿里云平台客户认可&#xff0c;助力阿里云平台政府、金融、教育、供应链、游戏等各类行业客户实现网站系统数据…...

服务器时钟同步

服务器时钟同步 文章目录 服务器时钟同步背景windows时钟同步Linux机器上的时钟同步Centos时钟同步Ubuntu系统时钟同步 查看是否同步的命令 背景 运维&#xff0c;XXX服务器慢了2秒&#xff0c;导致XXX业务没有正常执行&#xff0c;请立即排查为啥会有时钟不同步的问题。 首先…...

AMEYA360:瑞萨电子MCU和MPU产品线将支持Microsoft Visual Studio Code

全球半导体解决方案供应商瑞萨电子宣布其客户现可以使用Microsoft Visual Studio Code&#xff08;VS Code&#xff09;开发瑞萨全系列微控制器&#xff08;MCU&#xff09;和微处理器&#xff08;MPU&#xff09;。瑞萨已为其所有嵌入式处理器开发了工具扩展&#xff0c;并将其…...

JSP--Java的服务器页面

jsp是什么&#xff1f; jsp的全称是Java server pages,翻译过来就是java的服务器页面。 jsp有什么作用&#xff1f; jsp的主要作用是代替Servlet程序回传html页面的数据&#xff0c;因为Servlet程序回传html页面数据是一件非常繁琐的事情&#xff0c;开发成本和维护成本都非常高…...

07 Ubuntu中使用poetry工具管理python环境——巨详细!!!

由于conda和ros2的环境实在太容易冲突了。我真的不敢再使用conda&#xff0c;着实是有些搞不明白这解释器之间的关系。 conda的卸载和ros2的安装暂不赘述&#xff0c;下面着重来说如何在Ubuntu中使用poetry进行包管理及遇到的问题。 1 安装poetry 由于在有写入权限的限制&am…...

射影平面 与 射影变换

射影平面 与 射影变换 射影平面 与 齐次坐标 中心为O 的把 将与一个点O 关联的所有平面和直线构成的集合称为 中心为O的把O[\lambda, p]射影&#xff08;从平面\pi 到把O的一个对应关系&#xff09;O[P, l] [OP, OL] ...截影&#xff08;从把O 到平面\pi 的一个对应关系&…...

(202307)wonderful-sql:决胜秋招(task6)

教程链接&#xff1a;Datawhale - 一个热爱学习的社区 换硬盘重装了系统&#xff0c;后面应该也不会用到mysql&#xff0c;不装环境了&#xff0c;没有截图。 Section A 练习一: 各部门工资最高的员工&#xff08;难度&#xff1a;中等&#xff09; 创建Employee 表&#x…...

Scratch 教程:如何实现文本分割

在平时&#xff0c;我们通常会有分割文本的要求&#xff0c;但扩展却又无法使用scratch离线版打开&#xff0c;咋办呢&#xff1f;我们可以用原版做出来&#xff01; 没关系&#xff0c;我来教你&#xff01; 我们自定义一个函数&#xff0c;之后要分割调用就行了 创建三个变量…...

安全基础 --- 编码(02)+ form表单实现交互

浏览器解析机制和XSS向量编码 <!-- javascript伪协议不能被urlcode编码&#xff0c;但可以被html实体编码:也是js协议的一部分&#xff0c;不能被编码js协议被解码后&#xff0c;URL解析器继续解析链接剩下的部分unicode编码可识别实现解码但符号不能被编码&#xff0c;编码…...

华为OD机考真题--五子棋--带答案

2023华为OD统一考试&#xff08;AB卷&#xff09;题库清单-带答案&#xff08;持续更新&#xff09;or2023年华为OD真题机考题库大全-带答案&#xff08;持续更新&#xff09; 项目描述&#xff1a; 张兵和王武是五子棋迷&#xff0c;工作之余经常切磋棋艺。这不&#xff0c;这…...

把网站改为HTTPS访问方法

HTTPS是使用TSL/SSL加密超文本传输协议的扩展&#xff0c;用于跨网络的安全传输。网站更改为HTTPS&#xff0c;直接在网站形象上可以得到提升&#xff0c;更重要的是您的网站肯定会在排名和提升方面受益。机密信息的交换需要受到保护&#xff0c;以阻止未经授权的访问。 加密&a…...

【BASH】回顾与知识点梳理(四)

【BASH】回顾与知识点梳理 四 四. Bash Shell 的操作环境4.1 路径与指令搜寻顺序4.2 bash 的进站与欢迎讯息&#xff1a; /etc/issue, /etc/motd4.3 bash 的环境配置文件login与non-login shell/etc/profile (login shell 才会读)~/.bash_profile (login shell 才会读)source &…...

阿里云安全组设置

简介​ 云主机安全组必须打开如下端口&#xff1a; ssh&#xff1a;22http&#xff1a;80https&#xff1a;443ftp&#xff1a;21、20000&#xff5e;30000 阿里云安全组端口开放教程​ 腾讯云安全组端口开放教程​ 华为云安全组端口开放教程​...

QT图形视图系统 - 使用一个项目来学习QT的图形视图框架 - 终篇

QT图形视图系统 - 终篇 接上一篇&#xff0c;我们需要继续完成以下的效果&#xff1b; 先上个效果图&#xff1a; 修改背景&#xff0c;使之整体适配 上一篇我们绘制了标尺&#xff0c;并且我们修改了放大缩小和对应的背景&#xff0c;整体看来&#xff0c;我们的滚动条会和…...

vscode里如何用git

打开vs终端执行如下&#xff1a; 1 初始化 Git 仓库&#xff08;如果尚未初始化&#xff09; git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...

云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?

大家好&#xff0c;欢迎来到《云原生核心技术》系列的第七篇&#xff01; 在上一篇&#xff0c;我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在&#xff0c;我们就像一个拥有了一块崭新数字土地的农场主&#xff0c;是时…...

TDengine 快速体验(Docker 镜像方式)

简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能&#xff0c;本节首先介绍如何通过 Docker 快速体验 TDengine&#xff0c;然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker&#xff0c;请使用 安装包的方式快…...

反向工程与模型迁移:打造未来商品详情API的可持续创新体系

在电商行业蓬勃发展的当下&#xff0c;商品详情API作为连接电商平台与开发者、商家及用户的关键纽带&#xff0c;其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息&#xff08;如名称、价格、库存等&#xff09;的获取与展示&#xff0c;已难以满足市场对个性化、智能…...

uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖

在前面的练习中&#xff0c;每个页面需要使用ref&#xff0c;onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入&#xff0c;需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...

Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; 目前2025年06月05日更新到&#xff1a; AI炼丹日志-28 - Aud…...

抽象类和接口(全)

一、抽象类 1.概念&#xff1a;如果⼀个类中没有包含⾜够的信息来描绘⼀个具体的对象&#xff0c;这样的类就是抽象类。 像是没有实际⼯作的⽅法,我们可以把它设计成⼀个抽象⽅法&#xff0c;包含抽象⽅法的类我们称为抽象类。 2.语法 在Java中&#xff0c;⼀个类如果被 abs…...

保姆级【快数学会Android端“动画“】+ 实现补间动画和逐帧动画!!!

目录 补间动画 1.创建资源文件夹 2.设置文件夹类型 3.创建.xml文件 4.样式设计 5.动画设置 6.动画的实现 内容拓展 7.在原基础上继续添加.xml文件 8.xml代码编写 (1)rotate_anim (2)scale_anim (3)translate_anim 9.MainActivity.java代码汇总 10.效果展示 逐帧…...

【把数组变成一棵树】有序数组秒变平衡BST,原来可以这么优雅!

【把数组变成一棵树】有序数组秒变平衡BST,原来可以这么优雅! 🌱 前言:一棵树的浪漫,从数组开始说起 程序员的世界里,数组是最常见的基本结构之一,几乎每种语言、每种算法都少不了它。可你有没有想过,一组看似“线性排列”的有序数组,竟然可以**“长”成一棵平衡的二…...

Yii2项目自动向GitLab上报Bug

Yii2 项目自动上报Bug 原理 yii2在程序报错时, 会执行指定action, 通过重写ErrorAction, 实现Bug自动提交至GitLab的issue 步骤 配置SiteController中的actions方法 public function actions(){return [error > [class > app\helpers\web\ErrorAction,],];}重写Error…...