Android进阶知识(二十五):Bitmap简介及其高效加载

一、Bitmap

  Bitmap代表一个位图,在Android中指的是一张图片,可以是png、jpg等格式的图片。BitmapDrawable里封装的图片就是Bitmap对象。

  1. 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);
  1. 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的缩放有三种方式,分别为:质量压缩、采样压缩、矩阵压缩。

  1. 质量压缩

  质量压缩不会改变图片的像素点,这就意外着使用质量压缩之后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;
}
  1. 采样压缩

  采样压缩主要用于在图片资源本身较大,或者适当地采样不会影响视觉效果的条件下,对图片的大小和分辨率进行压缩,使得Bitmap的内存大小减小。典型的代码如下。

BitmapFactory.Options options = new Options();
options.inSampleSize = 2;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), resId, options);
  1. 矩阵压缩

  采样压缩使得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简介及其高效加载相关推荐

  1. ReactNative进阶(二十五):ScrollView 滚动视图组件详解

    文章目录 一.概述 二.属性 2.1 Style 2.2 方法 2.2.1 scrollTo() 2.2.2 crollToEnd() 三.拓展阅读 一.概述 ScrollView在Android和i ...

  2. 【Android游戏开发二十五】在Android上的使用《贝赛尔曲线》!

    首先对于<赛贝尔曲线>不是很了解的童鞋,请自觉白度百科.google等等... 为了方便偷懒的童鞋,这里给个<贝赛尔曲线>百科地址,以及一段话简述<贝赛尔曲线>: ...

  3. Android笔记(二十五) ListView的缓存机制与BaseAdapter

    之前接触了ListView和Adapter,Adapter将数据源和View连接起来,实际应用中,我们要显示的数据往往有很多,而屏幕只有那么大,系统只能屏幕所能显示的内容,当我们滑动屏幕,会将旧的内容 ...

  4. 打怪升级之小白的大数据之旅(二十五)<Java面向对象进阶之IO流三 其他常见流>

    打怪升级之小白的大数据之旅(二十五) Java面向对象进阶之IO流三 其他常见流 上次回顾 上一章,我们学习了常用的字节流与字符流,本章,我会将其他的一些常见的流进行分享,IO流很多,我介绍不完,就挑 ...

  5. 2021年大数据Hadoop(二十五):YARN通俗介绍和基本架构

    全网最详细的Hadoop文章系列,强烈建议收藏加关注! 后面更新文章都会列出历史文章目录,帮助大家回顾知识重点. 目录 本系列历史文章 前言 YARN通俗介绍和基本架构 Yarn通俗介绍 Yarn基本 ...

  6. 二十五岁零基础转行做软件测试怎么样?

    俗话说得好:男怕入错行,女怕嫁错郎,那么你的入行方向决定着你的整个职业发展!! 所以在考虑要进入什么行业之前,必须要了解清楚这个行业的发展前景怎么样? 我们都知道,随着社会的发展,互联网行业涉及也越来 ...

  7. 二十五个软件测试经典面试题,你确定不收藏一波?

    二十五个软件测试经典面试题全在这里了,有兴趣的朋友建议收藏一波,或者留言交流! 1.在搜索引擎中输入汉字就可以解析到对应的域名,请问如何用LoadRunner进行测试? 建立测试计划,确定测试标准和测 ...

  8. ReactNative进阶(三十五):应用脚手架 Yo 构建 RN 页面

    文章目录 一.前言 二.Bloc 数据流讲解 三.利用代码自动生成功能创建新页面 四.Bloc数据流使用说明 五.拓展阅读 一.前言 前期将脚手架yo安装成功,本篇博文主要讲解如何利用yo提供的代码自 ...

  9. 二十五个深度学习相关公开数据集

    转 [干货]二十五个深度学习相关公开数据集 2018年04月18日 13:42:53 阅读数:758 (选自Analytics Vidhya:作者:Pranav Dar:磐石编译) 目录 介绍 图像处 ...

最新文章

  1. docker nodejs 基本应用
  2. 软件测试学习:软件测试的背景
  3. oracle:SAVEPOINT(保存点)
  4. “鹅厂养鹅”是假的,但腾讯这个“山洞”是真的
  5. DocumentNavigator是什么东东?
  6. JS-[IIFE闭包]
  7. 647.回文字符串 (力扣leetcode) 博主可答疑该问题
  8. HUSTOJ教程(1)——安装部署
  9. 含泪推荐四款超级好用的电脑软件,值得收藏
  10. js根据年份计算总周数并获取每周的日期范围
  11. 香港服务器的数据泄露是什么?怎样预防?
  12. 6sigma 基本概念
  13. 林轩田《机器学习基石》第一篇(观后感)
  14. RK3566和S905X3/S905X4对比哪个好?
  15. GDAL 地图切片层级计算公式
  16. CSS 帧动画 播放动画
  17. SpringCloudAlibaba【四】Nacos Config 多环境切换与公共配置
  18. DC-DC与LDO的区别
  19. 关于majaro安装后的配置,简单记录 机型华硕FZ53v
  20. Franka Emika Panda机械臂规划路径时,rviz中手爪显示碰撞

热门文章

  1. 解决Python官网打不开
  2. 我们已在路上,希望就在前方
  3. 什么软件能测试苹果手机的配件,爱思助手“正品配件检测”功能使用方法
  4. 并发--生产者消费者模式
  5. arXiv每日推荐-5.9:语音/音频每日论文速递
  6. 手把手教你,抖音去水印-有手就能学会
  7. 使用云效构建部署项目
  8. pboot 将编码转换为实体html_PbootCms在模板中对定制标签中的内容进行二次处理
  9. dio java_Flutter -------- dio网络请求
  10. 计算机云的使用方法,云电脑教程:云电脑怎么用?