Android 性能优化系列之bitmap图片优化
作者:涂程 发布时间:2023-11-05 14:03:20
背景
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 < jcs.image_height) {
//取一行数据
uint8_t *pixels = data + jcs.next_scanline * row_stride;
row[0]=pixels;
jpeg_write_scanlines(&jcs,row,1);
}
// 3.6、压缩完成
jpeg_finish_compress(&jcs);
// 3.7、释放jpeg对象
fclose(f);
jpeg_destroy_compress(&jcs);
}
因为涉及到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… 来实现我们图片缓存的逻辑
省略了内存和磁盘的部分。
来源:https://blog.csdn.net/u012165769/article/details/121144893


猜你喜欢
- 解决项目中表单重复提交的问题,在平常的项目中有以下几种可能出现表单重复提交的情况,比如说:1.由于服务器缓慢或者网络延迟的原因,重复点击提交
- 本文实例讲述了Android实现内存中数据保存到sdcard的方法。分享给大家供大家参考,具体如下:public static void w
- 一般而言,Android 应用在请求数据时都是以 Get 或 Post 等方式向远程服务器发起请求,那你有没有想过其实我们也可以在 Andr
- Spring AOP对嵌套方法不起作用今天在调研系统操作记录日志时,好多教程都是借助于Spring AOP机制来实现。于是也采用这种方法来实
- 守护线程在Java中有两类线程User Thread(用户线程)Daemon Thread(守护线程)守护线程的功能非常简单,在其本身是一个
- 前言目前互联网公司,大部分项目都是基于分布式,一个项目被拆分成几个小项目,这些小项目会分别部署在不同的计算机上面,这个叫做微服务。当一台计算
- 介绍超级管理员:系统管理、用户管理、网点管理、运输点管理、快递员管理、网点申请管理(审核)、报价管理(时效报价)等。普通用户:注册登录、个人
- Android权限一般是在AndroidManifest.xml中声明,在安装或首次使用的时候系统会自动提示用户是否提供权限Android官
- 简介java 8 stream作为流式操作有两种操作类型,中间操作和终止操作。这两种有什么区别呢?我们看一个peek的例子:Stream&l
- spring Boot 允许通过外部配置让你在不同的环境使用同一应用程序的代码,简单说就是可以通过配置文件来注入属性或者修改默认的配置。Sp
- 在上篇文章给大家介绍了Android开发之开发者头条(一)启动页实现,感兴趣的朋友可以参考下。title: 带你实现开发者头条(二) 实现左
- 方法的递归调用1. 基本介绍:简单地说,递归就是方法自己调用自己,每次调用时传入不同的变量,递归有助于编程者解决复杂问题的同时让代码变得简洁
- 本文实例讲述了Java基于Swing实现的打猎射击游戏代码。分享给大家供大家参考。具体实现代码如下:package Game;import
- 在学会了java中io流的使用后,我们对于数组的排序,又多了一种使用方法。大家知道流处理数据的效率是比较理想的,那么在具体操作数组排序上,很
- java 中设计模式(值对象)的实例详解应用场景:在Java开发时,需要来回交换大量的数据,比如要为方法传入参数,也要获取方法的返回值,该如
- package com.huateng.readcsv;import java.io.BufferedReader;import java.
- 第一:写Cookies Response.Cookies["UserName"].Value="Guest&q
- 使用Vibrator的vibrate()可调节震动时间;cancel()取消震动。 <!—震动权限--><use
- 本文实例讲述了C#数据绑定(DataBinding)简单实现方法。分享给大家供大家参考。具体实现方法如下:using System;usin
- Interceptor 介绍 * (Interceptor)同 Filter 过滤器一样,它俩都是面向切面编程—&