Android进阶知识(二十五):Bitmap简介及其高效加载
Android进阶知识(二十五):Bitmap简介及其高效加载
一、Bitmap
Bitmap代表一个位图,在Android中指的是一张图片,可以是png、jpg等格式的图片。BitmapDrawable里封装的图片就是Bitmap对象。
- Bitmap的创建
在Bitmap类中,Bitmap的构造方法是默认权限,因此开发者无法通过new来创建一个Bitmap对象。Bitmap提供了静态方法createBitmap用于创建Bitmap对象,这些方法可以分为4类(Android API 29)。
类型 | 最终调用方法 |
---|---|
根据已有Bitmap创建新的Bitmap | createBitmap(Bitmap source, int x, int y, int width, int height, Matrix m, boolean filter); |
通过像素点数组创建空的Bitmap | createBitmap(DisplayMetrics display, int width, int height, Config config, boolean hasAlpha, ColorSpace colorSpace); |
根据Picture对象创建Bitmap | createBitmap(Picture source, int width, int height, Config config); |
创建缩放的Bitmap | createBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter); |
- Bitmap的像素格式
Bitmap的像素格式有五种,在Bitmap的内部枚举类Config中有定义,具体含义如下表。
格式 | 单位像素所占字节数(byte) | 描述 |
---|---|---|
ALPHA_8 | 1byte | 只有一个alpha通道 |
RGB_565 | 2byte | RGB三通道,每个像素占2Byte,其中红色占5bit,绿色占6bit,蓝色占5bit |
ARGB_4444 | 2byte | 这个从API 13开始不建议使用,因为质量太差 |
ARGB_8888 | 4byte | ARGB四个通道,每个通道8bit |
RGBA_F16 | 8byte | RGBA四通道,每个通道用16bit的float存储 |
每个格式的命名除了RGBA_F16比较特殊之外,名字都包含了格式的通道数和每个通道的比特数。以ARGB_8888为例子,如下图所示。
二、BitmapFactory
BitmapFactory是一个工具类,其提供了一系列用于不同的数据源来解析、创建Bitmap对象的方法。
以下对常用的几个方法做简单的介绍,如下表。
方法 | 描述 |
---|---|
decodeByteArray(byte[] data, int offset, int length, Options opts) | 从指定字节数组的offset位置开始,将长度length的字节数据解析成Bitmap对象 |
decodeFile(String pathName, Option opts) | 从patchName指定的文件中解析、创建Bitmap对象 |
decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts) | 用于FileDescriptor对应的文件中解析、创建Bitmap对象 |
decodeResource(Resources res, int id, Options opts) | 用于给定的资源ID指定的资源中解析、创建Bitmap对象 |
decodeStream(InputStream is, Rect outPadding, Options opts) | 用于从指定输入流中解析、创建Bitmap对象 |
另外,decodeFile和decodeResource间接调用了decodeStream来解析Bitmap。而有意思的是,在decodeResource调用decodeStream之前还会调用decodeResourceStream方法。这里笔者就不深入介绍了,有兴趣的读者可以参照博客:深入理解Android Bitmap的各种操作。从该博客的解析中有这样的结论:
1) decodeResource 在解析时会对 Bitmap 根据当前设备屏幕密度 densityDpi 的值进行缩放适配操作,使得解析出来的 Bitmap 与当前设备分辨率匹配,并且一般来说,这时 Bitmap 的大小将比原始的 Bitmap 大。
2) decodeFile、decodeStream 在解析时不会对 Bitmap 进行一系列的屏幕适配,解析出来的将是原始大小的图。
BitmapFactory中有一个很重要的静态内部类Options,其中包含了很多常用的属性,这里笔者仅仅介绍最为常用的三个属性,具体如下表所示。
属性 | 描述 |
---|---|
inJustDecodeBounds | 这个值为true,解码时将不会返回Bitmap,只返回Bitmap的尺寸。 |
outWidth和outHeight | 表示这个Bitmap的宽和高 |
inSampleSize | 压缩图片时采样率的值,如果这个值大于1,那么按照比例(1 / inSampleSize)来缩小Bitmap的宽和高。采样率必须大于1才会有缩小效果,并且同时作用于宽高。当采样率小于1,作用等同于1,即无缩放效果。 |
三、Bitmap大小的获取及缩放
在Bitmap中提供了一个获取Bitmap大小的API——getByteCount()方法。
public final int getByteCount() {if (mRecycled) {Log.w(TAG, "Called getByteCount() on a recycle()'d bitmap! "+ "This is undefined behavior!");return 0;}// int result permits bitmaps up to 46,340 x 46,340return getRowBytes() * getHeight();
}
可以看出它是通过getRowBytes和getHeight方法实现的,而getRowBytes方法调用的是native方法。从中可以得知,Bitmap占用的内存大小计算公式为:
值得一提的是,由于decodeResource方法会根据分辨率的不同而缩放Bitmap,因此其内存的计算中需要使用缩放后的图片宽/高。
Bitmap的缩放有三种方式,分别为:质量压缩、采样压缩、矩阵压缩。
- 质量压缩
质量压缩不会改变图片的像素点,这就意外着使用质量压缩之后Bitmap所占用的内存依旧不会减小,但是可以减小存储在本地文件的大小。质量压缩的代码如下。
/**
* 质量压缩方法,并不能减小加载到内存时所占用内存的空间,应该是减小的所占用磁盘的空间
* @param image
* @param compressFormat
* @return
*/
public static Bitmap compressbyQuality(Bitmap image, Bitmap.CompressFormat compressFormat) {ByteArrayOutputStream output = new ByteArrayOutputStream();//质量压缩方法,这里100表示不压缩,把压缩后的数据存放到output中image.compress(compressFormat, 100, output);int quality = 100;while ( output.toByteArray().length / 1024 > 100) { //循环判断如果压缩后图片是否大于100kb,大于继续压缩output.reset();if(quality > 10){quality -= 20;//每次都减少20} else {break;}// 压缩image.compress(Bitmap.CompressFormat.JPEG, quality, output);}ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray());BitmapFactory.Options options = new BitmapFactory.Options();options.inPreferredConfig = Bitmap.Config.RGB_565;Bitmap bitmap = BitmapFactory.decodeStream(input, null, options);return bitmap;
}
- 采样压缩
采样压缩主要用于在图片资源本身较大,或者适当地采样不会影响视觉效果的条件下,对图片的大小和分辨率进行压缩,使得Bitmap的内存大小减小。典型的代码如下。
BitmapFactory.Options options = new Options();
options.inSampleSize = 2;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), resId, options);
- 矩阵压缩
采样压缩使得Bitmap所占内存变小,同时图的尺寸也变小了,为了能使得改变尺寸可以采用矩阵压缩,具体代码如下。
/**
* 矩阵缩放图片
* @param sourceBitmap
* @param width 要缩放到的宽度
* @param height 要缩放到的长度
* @return
*/
private Bitmap getScaleBitmap(Bitmap sourceBitmap,float width,float height) {Bitmap scaleBitmap;//定义矩阵对象Matrix matrix = new Matrix();float scale_x = width/sourceBitmap.getWidth();float scale_y = height/sourceBitmap.getHeight();matrix.postScale(scale_x,scale_y);try {scaleBitmap = Bitmap.createBitmap(sourceBitmap,0,0,sourceBitmap.getWidth(),sourceBitmap.getHeight(),matrix,true);} catch (OutOfMemoryError e) {scaleBitmap = null;System.gc();}return scaleBitmap;
}
四、Bitmap的高效加载
由于Bitmap的特殊性以及Android对单个应用所施加的内存限制(比如16MB),这导致加载Bitmap的时候容易出现OOM。为了避免OOM,有效地加载图片,可以采用采样压缩的方法。前面笔者也提到了采样压缩可以降低内存占用,这就是能够避免OOM、提高Bitmap加载性能的原因。
通过采样率高效加载Bitmap的流程分为4步:
1) 将BitmapFactory.Options的inJustDecodeBounds参数设为true并加载图片(实际加载图片尺寸)。
2) 从BitmapFactory.Options中获取图片的原始宽高信息,对应于outWidth和outHeight参数。
3) 根据采样率的规则结合目标View的所需大小计算出采样率inSampleSize。
4) 将BitmapFatory.Options的inJustDecodeBounds参数设为false,然后重新加载图片。
前面也提到了inJustDecodeBounds设为true只会返回Bitmap的尺寸而不加载Bitmap,因此该操作是轻量级的。根据上述流程可以得到如下高效加载Bitmap的代码。
public static Bitmap decodeSampleBitmapFromResource(Resources res,int resId, int reqWidth, int reqHeight) {final BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true;BitmapFactory.decodeResource(res, resId, options);options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);options.inJustDecodeBounds = false;return BitmapFactory.decodeResource(res, resId, options);
}private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {final int width= options.outWidth;final int height = options.outHeight;int inSampleSize = 1;if (height > reqHeight || width > reqHeight){final int halfHeight = height / 2;final int halfWidth = width/ 2;while ((halfHeight / inSampleSize) >= reqHeight && (halWidth / inSampleSize) >= reqWidth) {inSampleSize *= 2; }return inSampleSize;
}
另外,decodeStream方法比较特殊,具体读者可以参看博客:深入理解Android Bitmap的各种操作。
参考资料:《Android开发艺术探索》
深入理解Android Bitmap的各种操作
Android Bitmap最全面详解
Android进阶知识(二十五):Bitmap简介及其高效加载相关推荐
- ReactNative进阶(二十五):ScrollView 滚动视图组件详解
文章目录 一.概述 二.属性 2.1 Style 2.2 方法 2.2.1 scrollTo() 2.2.2 crollToEnd() 三.拓展阅读 一.概述 ScrollView在Android和i ...
- 【Android游戏开发二十五】在Android上的使用《贝赛尔曲线》!
首先对于<赛贝尔曲线>不是很了解的童鞋,请自觉白度百科.google等等... 为了方便偷懒的童鞋,这里给个<贝赛尔曲线>百科地址,以及一段话简述<贝赛尔曲线>: ...
- Android笔记(二十五) ListView的缓存机制与BaseAdapter
之前接触了ListView和Adapter,Adapter将数据源和View连接起来,实际应用中,我们要显示的数据往往有很多,而屏幕只有那么大,系统只能屏幕所能显示的内容,当我们滑动屏幕,会将旧的内容 ...
- 打怪升级之小白的大数据之旅(二十五)<Java面向对象进阶之IO流三 其他常见流>
打怪升级之小白的大数据之旅(二十五) Java面向对象进阶之IO流三 其他常见流 上次回顾 上一章,我们学习了常用的字节流与字符流,本章,我会将其他的一些常见的流进行分享,IO流很多,我介绍不完,就挑 ...
- 2021年大数据Hadoop(二十五):YARN通俗介绍和基本架构
全网最详细的Hadoop文章系列,强烈建议收藏加关注! 后面更新文章都会列出历史文章目录,帮助大家回顾知识重点. 目录 本系列历史文章 前言 YARN通俗介绍和基本架构 Yarn通俗介绍 Yarn基本 ...
- 二十五岁零基础转行做软件测试怎么样?
俗话说得好:男怕入错行,女怕嫁错郎,那么你的入行方向决定着你的整个职业发展!! 所以在考虑要进入什么行业之前,必须要了解清楚这个行业的发展前景怎么样? 我们都知道,随着社会的发展,互联网行业涉及也越来 ...
- 二十五个软件测试经典面试题,你确定不收藏一波?
二十五个软件测试经典面试题全在这里了,有兴趣的朋友建议收藏一波,或者留言交流! 1.在搜索引擎中输入汉字就可以解析到对应的域名,请问如何用LoadRunner进行测试? 建立测试计划,确定测试标准和测 ...
- ReactNative进阶(三十五):应用脚手架 Yo 构建 RN 页面
文章目录 一.前言 二.Bloc 数据流讲解 三.利用代码自动生成功能创建新页面 四.Bloc数据流使用说明 五.拓展阅读 一.前言 前期将脚手架yo安装成功,本篇博文主要讲解如何利用yo提供的代码自 ...
- 二十五个深度学习相关公开数据集
转 [干货]二十五个深度学习相关公开数据集 2018年04月18日 13:42:53 阅读数:758 (选自Analytics Vidhya:作者:Pranav Dar:磐石编译) 目录 介绍 图像处 ...
最新文章
- docker nodejs 基本应用
- 软件测试学习:软件测试的背景
- oracle:SAVEPOINT(保存点)
- “鹅厂养鹅”是假的,但腾讯这个“山洞”是真的
- DocumentNavigator是什么东东?
- JS-[IIFE闭包]
- 647.回文字符串 (力扣leetcode) 博主可答疑该问题
- HUSTOJ教程(1)——安装部署
- 含泪推荐四款超级好用的电脑软件,值得收藏
- js根据年份计算总周数并获取每周的日期范围
- 香港服务器的数据泄露是什么?怎样预防?
- 6sigma 基本概念
- 林轩田《机器学习基石》第一篇(观后感)
- RK3566和S905X3/S905X4对比哪个好?
- GDAL 地图切片层级计算公式
- CSS 帧动画 播放动画
- SpringCloudAlibaba【四】Nacos Config 多环境切换与公共配置
- DC-DC与LDO的区别
- 关于majaro安装后的配置,简单记录 机型华硕FZ53v
- Franka Emika Panda机械臂规划路径时,rviz中手爪显示碰撞