Android-Bitmap优化
概述
在日常开发中我们经常遇到加载图片报出oom的错误,我们要解决这个问题,首先要明白oom代表out of memory 内存溢出,因为手机内存有限,分给每个应用的内存有限,所以要解决这个问题就是要解决图片占用内存问题 android 中图片是以bitmap的形式存在的,那么bitmap中所占的内存,直接影响到了是否oom,我们了解一下bitmap的占用内存的计算方法
Bitmap到底占多大内存
从本地加载或者从网络加载可以用下面的公式计算
图片的长度 * 图片的宽度 * 一个像素点占用的字节数
如果从资源文件夹加载,会怎么样?
首先把同一张图片放进不同的资源文件夹会发生什么?
- 同一张图片放进不同的文件夹,图片会被压缩
看下源码
if (env->GetBooleanField(options, gOptions_scaledFieldID)) {const int density = env->GetIntField(options, gOptions_densityFieldID);const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);if (density != 0 && targetDensity != 0 && density != screenDensity) {scale = (float) targetDensity / density;}
}
...
int scaledWidth = decoded->width();
int scaledHeight = decoded->height();if (willScale && mode != SkImageDecoder::kDecodeBounds_Mode) {scaledWidth = int(scaledWidth * scale + 0.5f);scaledHeight = int(scaledHeight * scale + 0.5f);
}
...
if (willScale) {const float sx = scaledWidth / float(decoded->width());const float sy = scaledHeight / float(decoded->height());bitmap->setConfig(decoded->getConfig(), scaledWidth, scaledHeight);bitmap->allocPixels(&javaAllocator, NULL);bitmap->eraseColor(0);SkPaint paint;paint.setFilterBitmap(true);SkCanvas canvas(*bitmap);canvas.scale(sx, sy);canvas.drawBitmap(*decoded, 0.0f, 0.0f, &paint);
}
我们可以看到压缩比例是由下面的公式得出
scale = (float) targetDensity / density;
及缩放的比例和targetDensity,density有关,那么这个俩个变量又代表着什么呢?
- targetDensity:设备屏幕像素密度 dpi
- density:图片对应的文件夹的像素密度 dpi
其中density和Bitmap存放的资源目录有关,不同的资源目录有不同的值
density | 0.75 | 1 | 1.5 | 2 | 3 3.5 | 4 |
---|---|---|---|---|---|---|
densityDpi | 120 | 160 | 240 | 320 | 480 | 560 |
DpiFolder | ldpi | mdpi | hdpi | xhdpi | xxhdpi | xxxhdpi |
可以得出以下结论
- 同一张图片放在不同的资源目录下,其分辨率会有变化
- Bitmap的分辨率越高,其解析后的宽高越小,甚至小于原有的图片(及缩放),从而内存也响应的减少
- 图片不放置任何资源目录时,其使用默认分辨率mdpi:160
- 资源目录分辨率和屏幕分辨率一致时,图片尺寸不会缩放
所以Bitmap在资源目录中的计算方式为
Bitmap内存占用 ≈ 像素数据总大小 = 图片宽 × 图片高× (当前设备密度dpi/图片所在文件夹对应的密度dpi)^2 × 每个像素的字节大小
Bitmap内存优化从下面四个方面进行优化
- 编码
- 采样
- 复用
- 匿名共享区
下面我们一个个的来讲这些优化
编码
Android 中提供一下几种编码
其中,A代表透明度;R代表红色;G代表绿色;B代表蓝色。
- ALPHA_8 表示8位Alpha位图,即A=8,一个像素点占用1个字节,它没有颜色,只有透明度
- ARGB_4444 表示16位ARGB位图,即A=4,R=4,G=4,B=4,一个像素点占4+4+4+4=16位,2个字节
- ARGB_8888 表示32位ARGB位图,即A=8,R=8,G=8,B=8,一个像素点占8+8+8+8=32位,4个字节
- RGB_565 表示16位RGB位图,即R=5,G=6,B=5,它没有透明度,一个像素点占5+6+5=16位,2个字节
也即是说我们可以通过改变图片格式,来改变每个像素占用字节数,来改变占用的内存,看下面代码
BitmapFactory.Options options = new BitmapFactory.Options();//不获取图片,不加载到内存中,只返回图片属性options.inJustDecodeBounds = true;BitmapFactory.decodeFile(photoPath, options);//图片的宽高int outHeight = options.outHeight;int outWidth = options.outWidth;Log.d("mmm", "图片宽=" + outWidth + "图片高=" + outHeight);//图片格式压缩options.inPreferredConfig = Bitmap.Config.RGB_565;options.inJustDecodeBounds = false;Bitmap bitmap = BitmapFactory.decodeFile(photoPath, options);float bitmapsize = getBitmapsize(bitmap);Log.d("mmm","压缩后:图片占内存大小" + bitmapsize + "MB / 宽度=" + bitmap.getWidth() + "高度=" + bitmap.getHeight());
看下log
07-09 11:10:46.042 15312-15312/com.example.jh.rxhapp D/mmm: 原图:图片占内存大小=45.776367MB / 宽度=4000高度=3000
07-09 11:10:46.043 15312-15312/com.example.jh.rxhapp D/mmm: 图片宽=4000图片高=3000
07-09 11:10:46.367 15312-15312/com.example.jh.rxhapp D/mmm: 压缩后:图片占内存大小22.887695MB / 宽度=4000高度=3000
宽高没变,我们改变了图片的格式,从ARGB_8888 变成了RGB_565 ,像素占用字节数减少了一般,根据log 内存也减少了一半,这种方式可行
注意:由于ARGB_4444的画质惨不忍睹,一般假如对图片没有透明度要求的话,可以改成RGB_565,相比ARGB_8888将节省一半的内存开销。
采样
我们了解到了计算bitmap的占用内存的方法 ,是以bitmap的宽高和每个像素占用的字节数决定的,下面我们分别讲一下俩个 的概念
1 bitmap的宽高
顾名思义,图片的大小就是bitmap的宽高,按公式我们可以缩减bitmap的宽高来达到压缩图片占用内存的目的,看下面代码,以缩减宽高来达到压缩的目的
BitmapFactory.Options options = new BitmapFactory.Options();//不获取图片,不加载到内存中,只返回图片属性options.inJustDecodeBounds = true;BitmapFactory.decodeFile(photoPath, options);//图片的宽高int outHeight = options.outHeight;int outWidth = options.outWidth;Log.d("mmm", "图片宽=" + outWidth + "图片高=" + outHeight);//计算采样率int i = utils.computeSampleSize(options, -1, 1000 * 1000);//设置采样率,不能小于1 假如是2 则宽为之前的1/2,高为之前的1/2,一共缩小1/4 一次类推options.inSampleSize = i;Log.d("mmm", "采样率为=" + i);//图片格式压缩//options.inPreferredConfig = Bitmap.Config.RGB_565;options.inJustDecodeBounds = false;Bitmap bitmap = BitmapFactory.decodeFile(photoPath, options);float bitmapsize = getBitmapsize(bitmap);Log.d("mmm","压缩后:图片占内存大小" + bitmapsize + "MB / 宽度=" + bitmap.getWidth() + "高度=" + bitmap.getHeight());
看下打印信息
07-09 11:02:11.714 8010-8010/com.example.jh.rxhapp D/mmm: 原图:图片占内存大小=45.776367MB / 宽度=4000高度=3000
07-09 11:02:11.715 8010-8010/com.example.jh.rxhapp D/mmm: 图片宽=4000图片高=3000
07-09 11:02:11.715 8010-8010/com.example.jh.rxhapp D/mmm: 采样率为=4
07-09 11:02:11.944 8010-8010/com.example.jh.rxhapp D/mmm: 压缩后:图片占内存大小1.4296875MB / 宽度=1000高度=750
这种我们根据BitmapFactory 的采样率进行压缩 设置采样率,不能小于1 假如是2 则宽为之前的1/2,高为之前的1/2,一共缩小1/4 一次类推,我们看到log ,确实起到了压缩的目的
复用
图片复用指的是inBitmap这个属性
这个属性又什么作用?
不使用这个属性,你加载三张图片,系统会给你分配三份内存空间,用于分别储存这三张图片
如果用了inBitmap这个属性,加载三张图片,这三张图片会指向同一块内存,而不用开辟三块内存空间
inBitmap的限制
- 3.0-4.3
- 复用的图片大小必须相同
- 编码必须相同
- 4.4以上
- 复用的空间大于等于即可
- 编码不必相同
- 不支持WebP
- 图片复用,这个属性必须设置为true; options.inMutable = true;
匿名共享内存(Ashmem)
Android 系统为了进程间共享数据开辟的一块内存区域,由于这块区域不受应用的Head的大小限制,相当于可以绕开oom,FaceBook的Fresco首次应用到实际中
限制:5.0以后就限制了匿名共享内存的使用
图片到底储存在哪里?
2.3- | 3.0-4.4 | 5.0-7.1 | 8.0 |
---|---|---|---|
Bitmap对象 | java Heap | java Heap | java Heap |
像素数据 | Native Heap | java Heap | Native Heap |
迁移原因 | - | 解决Native Bitmap内存泄露 | 共享整个系统的内存减少OOM |
8.0Bitmap的像素数据存储在Native,为什么又改为Native存储呢?
因为8.0共享了整个系统的内存,测试8.0手机如果一直创建Bitmap,如果手机内存有1G,那么你的应用加载1G也不会oom
LRU管理Bitmap
我们可以利用LRU开管理Bitmap,给他设置内存最大值,及时回收
图片的压缩
图片的压缩一般有俩种
- 通过采样压缩,上边已经讲过了
- 质量压缩
bitmap.compress(Bitmap.CompressFormat.JPEG, 20,
new FileOutputStream("sdcard/result.jpg"));
这个大家用该都用过,这个压缩是保持像素的前提下改变图片的位深及透明度,来达到压缩的目的,不过这种压缩不会改变图片在内存中的带下,而且这种压缩会导致图片的失真,但是有没有压缩到100k左右,还不失真的方法?
如何加载高清图
如果有需求,要求我们既不能压缩图片,又不能发生oom怎么办,这种情况我们需要加载图片的一部分区域来显示,下面我们来了解一下BitmapRegionDecoder这个类,加载图片的一部分区域,他的用法很简单
//支持传入图片的路径,流和图片修饰符等BitmapRegionDecoder mDecoder = BitmapRegionDecoder.newInstance(path, false);
//需要显示的区域就有由rect控制,options来控制图片的属性Bitmap bitmap = mDecoder.decodeRegion(mRect, options);
由于要显示一部分区域,所以要有手势的控制,方便上下的滑动,需要自定义控件,而自定义控件的思路也很简单 1 提供图片的入口 2 重写onTouchEvent, 根据手势的移动更新显示区域的参数 3 更新区域参数后,刷新控件重新绘制
下面是完整代码
public class BigImageView extends View {private BitmapRegionDecoder mDecoder;private int mImageWidth;private int mImageHeight;//图片绘制的区域private Rect mRect = new Rect();private static final BitmapFactory.Options options = new BitmapFactory.Options();static {options.inPreferredConfig = Bitmap.Config.RGB_565;}public BigImageView(Context context) {super(context);init();}public BigImageView(Context context, AttributeSet attrs) {super(context, attrs);init();}public BigImageView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}private void init() {}/*** 自定义view的入口,设置图片流** @param path 图片路径*/public void setFilePath(String path) {try {//初始化BitmapRegionDecodermDecoder = BitmapRegionDecoder.newInstance(path, false);BitmapFactory.Options options = new BitmapFactory.Options();//便是只加载图片属性,不加载bitmap进入内存options.inJustDecodeBounds = true;BitmapFactory.decodeFile(path, options);//图片的宽高mImageWidth = options.outWidth;mImageHeight = options.outHeight;Log.d("mmm", "图片宽=" + mImageWidth + "图片高=" + mImageHeight);requestLayout();invalidate();} catch (IOException e) {e.printStackTrace();}}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);//获取本view的宽高int measuredHeight = getMeasuredHeight();int measuredWidth = getMeasuredWidth();//默认显示图片左上方mRect.left = 0;mRect.top = 0;mRect.right = mRect.left + measuredWidth;mRect.bottom = mRect.top + measuredHeight;}//第一次按下的位置private float mDownX;private float mDownY;@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:mDownX = event.getX();mDownY = event.getY();break;case MotionEvent.ACTION_MOVE:float moveX = event.getX();float moveY = event.getY();//移动的距离int xDistance = (int) (moveX - mDownX);int yDistance = (int) (moveY - mDownY);Log.d("mmm", "mDownX=" + mDownX + "mDownY=" + mDownY);Log.d("mmm", "movex=" + moveX + "movey=" + moveY);Log.d("mmm", "xDistance=" + xDistance + "yDistance=" + yDistance);Log.d("mmm", "mImageWidth=" + mImageWidth + "mImageHeight=" + mImageHeight);Log.d("mmm", "getWidth=" + getWidth() + "getHeight=" + getHeight());if (mImageWidth > getWidth()) {mRect.offset(-xDistance, 0);checkWidth();//刷新页面invalidate();Log.d("mmm", "刷新宽度");}if (mImageHeight > getHeight()) {mRect.offset(0, -yDistance);checkHeight();invalidate();Log.d("mmm", "刷新高度");}break;case MotionEvent.ACTION_UP:break;default:}return true;}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);Bitmap bitmap = mDecoder.decodeRegion(mRect, options);canvas.drawBitmap(bitmap, 0, 0, null);}/*** 确保图不划出屏幕*/private void checkWidth() {Rect rect = mRect;int imageWidth = mImageWidth;int imageHeight = mImageHeight;if (rect.right > imageWidth) {rect.right = imageWidth;rect.left = imageWidth - getWidth();}if (rect.left < 0) {rect.left = 0;rect.right = getWidth();}}/*** 确保图不划出屏幕*/private void checkHeight() {Rect rect = mRect;int imageWidth = mImageWidth;int imageHeight = mImageHeight;if (rect.bottom > imageHeight) {rect.bottom = imageHeight;rect.top = imageHeight - getHeight();}if (rect.top < 0) {rect.top = 0;rect.bottom = getHeight();}}
}
代码行有注释,应该很好理解了
Android-Bitmap优化相关推荐
- 【Android 内存优化】Bitmap 硬盘缓存 ( Google 官方 Bitmap 示例 | DiskLruCache 开源库 | 代码示例 )
文章目录 一.Google 官方 Bitmap 相关示例参考 二.磁盘缓存类 DiskLruCache 三.磁盘缓存初始化 四.存储数据到磁盘缓存中 五.从磁盘缓存中读取数据 六. Android 1 ...
- 【Android 内存优化】Bitmap 内存缓存 ( Bitmap 内存复用 | 弱引用 | 引用队列 | 针对不同 Android 版本开发不同的 Bitmap 复用策略 | 工具类代码 )
文章目录 一.Bitmap 复用池 二.弱引用 Bitmap 内存释放 三.从 Bitmap 复用池中获取对应可以被复用的 Bitmap 对象 1.Android 2.3.3(API 级别 10)及以 ...
- 【Android 内存优化】Bitmap 图像尺寸缩小 ( 考虑像素密度、针对从不同像素密度资源中解码对应的 Bitmap 对象 | inDensity | inTargetDensity )
文章目录 一.像素密度对解码图片的影响 二.不考虑像素密度会导致图片缩小尺寸不准确 三.DisplayMetrics 源码阅读.研究手机资源获取规则 四.像素密度参数设置取值 ( inDensity ...
- 【Android 内存优化】Android 工程中使用 libjpeg-turbo 压缩图片 ( JNI 传递 Bitmap | 获取位图信息 | 获取图像数据 | 图像数据过滤 | 释放资源 )
文章目录 一.Bitmap 图像数据处理 二.Java 层 Bitmap 对象转为 JNI 层 bitmap 对象 三.获取 bitmap 中的图像数据 四.过滤 bitmap 中的图像数据 ( 获取 ...
- Android系统性能优化(71)---关于Bitmap图片资源优化的小事
Android性能优化:那些关于Bitmap图片资源优化的小事 前言 在 Android开发中,性能优化策略十分重要 本文主要讲解性能优化中的Bitmap 使用优化,希望你们会喜欢 目录 1. 优化原 ...
- Android 系统性能优化(39)---Android内存优化之三:打开MAT中的Bitmap原图
Android内存优化之三:打开MAT中的Bitmap原图 在使用MAT查看应用程序内存使用情况的时候,我们经常会碰到Bitmap对象以及BitmapDrawable$BitmapState对象,而且 ...
- Android性能优化系列之Bitmap图片优化
在Android开发过程中,Bitmap往往会给开发者带来一些困扰,因为对Bitmap操作不慎,就容易造成OOM(Java.lang.OutofMemoryError - 内存溢出),本篇博客,我们将 ...
- Android内存优化(二)之Bitmap的内存申请与回收(Android N和O的对比)
在Android O上大面积的爆了大量native Bitmap相关的泄漏问题,最大能达到几十MB,开始怀疑是出现了native内存泄漏问题,但经分析后发现是Android N和Android O在处 ...
- Android性能优化系列:Bitmap
文章目录 Bitmap 简介 Bitmap 的创建 不同系统版本 Bitmap 的内存分配策略 Bitmap 内存占用计算 在电脑查看的图片大小和运行内存大小区别 图片占用内存计算 Bitmap 内存 ...
- Android性能优化:那些关于Bitmap图片资源优化的小事
前言 在 Android开发中,性能优化策略十分重要 本文主要讲解性能优化中的Bitmap 使用优化,希望你们会喜欢 Carson带你学Android性能优化系列文章: Android性能优化:性能优 ...
最新文章
- 一个Web页面的问题分析
- python中time模块获取时间的使用
- 我逛了一下JDK一条街,发现了不少好东西!
- 有没有高效的记视频笔记方法?--天若OCR文字识别记视频笔记
- R语言观察日志(part7)--RMarkdwon之代码块
- SQL Server 2008 R2——使用数字辅助表(master..spt_values)实现用计数字段对记录进行重复显示...
- 【iVX 初级工程师培训教程 10篇文拿证】09 聊天室制作
- 关于public class 类名{ public static void main(String[] args)}的一些说明
- 两种获取python版本的方法
- 信息收集--空间搜索引擎/网盘
- 软件各种版本的含义!例如RC,M,GA等等
- ov5640摄像头使用心得
- MIMO基本技术原理
- 计划的主体部分应有哪些内容_计划练习题
- Echocardiography Image Segmentation: A Survey(超声心动图图像分割方法综述--中文翻译)
- poj2187 旋转卡(qia)壳(ke)
- 离职原因该怎么说才比较好 ?
- 利用win10镜像文件安装.net framework 3.5 简单快速
- HDU 2708.Vertical Histogram
- 推荐一款可快速全量交付 Kubernetes 集群分布式应用的神器 Sealer