BitmapFactory

我们不能够通过构造函数创建Bitmap对象。如果需要将图片转成Bitmap对象加载到内存中,就需要使用BitmapFactory类。BitmapFactory跟据图片数据源的不同,提供了几类获取Bitmap的方法。如下:

数据源类型 方法
byte[] decodeByteArray(byte[] data, int offset, int length,BitmapFactory.Options opts)
byte[] decodeByteArray(byte[] data, int offset, int length)
File decodeFile(String pathName, BitmapFactory.Options opts)
File decodeFile(String pathName)
FileDescriptor decodeFileDescriptor(FileDescriptor fd)
FileDescriptor decodeFileDescriptor(FileDescriptor fd, BitmapFactory.Options opts)
Resource decodeResource(Resource res, int id)
Resource decodeResource(Resource res, int id, BitmapFactory.Options opts)
ResourceStream decodeResourceStream(Resource res, TypedValue value,InputStream is,Rect pad,BitmapFactory.Options opts)
Stream decodeStream(InputStream is)
Stream decodeStream(InputStream is, Rect outPadding, BitmapFactory.Options opts)

BitmapFactory.Options

从上面的表格可以看出,每一类数据源的解码方法都有两个。其中一个都有一个BitmapFactory.Options参数。这个参数对解码进行了配置。

它的可选参数如下:

参数 作用
inBitmap : Bitmap 重用一个Bitmap对象
inDensity : int 这张图片解码使用的屏幕密度
inDither : boolean deprecated in api-24, 如果设置改选项,那么解码的时候会尝试防抖动处理
inInputShareable : boolean deprecated in api-21
inJustDecodeBounds : boolean 如果设置该选项,返回值为null。但是可以从Options对象中获取Bitmap的宽高
inMutable : boolean 如果设置,将会解码出一个可更改的Bitmap对象,而不是不可更改的
inPreferQualityOverSpeed : boolean deprecated in api-24, 设置它会牺牲时间效率,提升图片的质量
inPreferredConfig : Bitmap.Config 这里设置Bitmap的像素存储格式,也就是Bitmap的config对象
inPremutiplied : boolean 默认为true,与dither类似是一种图像处理的方式
inPurgeable : boolean deprecated in api-21
inSampleSize : int 如果值大于1,那么生成一个缩略版的Bitmap
inScaled : boolean 如果设置为true,并且inDensity和inTargetDensity不一致的时候,那么生成的bitmap会按照inTargetDensity的密度缩放,而不是系统提供的密度
inScreenDensity : int 屏幕的真实密度
inTargetDensity : int 这张bitmap绘制的屏幕密度
inTempStorage : byte[] 解码用的临时内存区域
mCancel : boolean deprecated in api 24
outHeight : int Bitmap的高度
outWidth : int Bitmap的宽度
outMimeType: int 解码图片的MimeType

减少内存占用:

原理:

解码图片设置缩放比例可以减少Bitmap对象的内存占用。关键的参数是inSampleSize参数。举个例子:

一张2018 x 1536 的图片如果完全解码为(ARGB_8888)的Bitmap,那么他的内存占用为 2048 * 1536 * 4=12M;

如果设置inSampleSize为4,那么最终Bitmap对象的尺寸为512 x 384。内存占用为512 * 384 * 4 = 0.75M;

也就是inSampleSize = n的时候,内存占用为1/(n * n) n=1, 2, 4, 8, 16 .....

使用方法:

  1. 计算图片的尺寸
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
  1. 根据期望的imageview尺寸计算缩放的倍数
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2;
final int halfWidth = width / 2; // Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
} return inSampleSize;
}

这里的imSampleSize的结果都是2的幂。根据inSampleSize的文档,如果传进去的inSampleSize非2的幂,那么会向下取2的幂为最终缩放比例。 例如:传 15 最终为 8;传 7最终为4;

  1. 根据inSampleSize解码图片
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) { // First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options); // Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}

这里参考一些文章的思路,根据实验发现利用BitmapFactory.Options中的Density相关的设置也可以控制图像的大小:

如果单独设置inDensity变量,那么只会影响到生成的Bitmap的density的值。

如果想要更根据density缩放,需要同时设置三个值:

变量
inDensity 图片数据对应的像素密度
inTargetDensity 生成的bitmap的像素密度
inScale 是否根据像素密度缩放,需要设置true

测试代码:

        // 这里使用了一张大小960000B大小的图片,放在assets目录下
// 这里的测试机默认屏幕像素密度480 AssetManager assetManager = getAssets();
InputStream is = null;
try {
is = assetManager.open("img_fjords.jpg");
BitmapFactory.Options options = new BitmapFactory.Options();
options.inTargetDensity = DisplayMetrics.DENSITY_HIGH; // 240
options.inDensity = DisplayMetrics.DENSITY_XXHIGH; // 480
options.inScaled = true;
Bitmap bitmap = BitmapFactory.decodeStream(is, new Rect(0, 0, 0, 0), options);
Log.d(LOG_TAG, "default density: " + 480);
Log.d(LOG_TAG, "default size: 960000"); Log.d(LOG_TAG, "bitmap density: " + String.valueOf(bitmap.getDensity()));
Log.d(LOG_TAG, "bitmap size: " + bitmap.getByteCount());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
输出结果:
08-20 03:03:06.528 7386-7386/com.example.densitytest D/MainActivity: default density: 480
08-20 03:03:06.528 7386-7386/com.example.densitytest D/MainActivity: default size: 960000
08-20 03:03:06.528 7386-7386/com.example.densitytest D/MainActivity: bitmap density: 240
08-20 03:03:06.528 7386-7386/com.example.densitytest D/MainActivity: bitmap size: 240000
*/

可以看出来,内存大小确实变化了。内存大小关系应该是 finalSize = originSize*(inTargetDensity/inDensity)^2

不过需要注意的是tagetSize如果与屏幕像素密度不一致的时候展示的时候还是会缩放。所以,在使用这个方法控制的内存的时候

通过inDensity来控制,这样就不需要额外修改bitmap的density。

Bitmap

这个类就代表位图,它的一部分接口如下:

方法 解释
compress(Bitmap.CompressFormat format, int quality, OutputStream stream) : boolean 把一个位图写入流中
copy(Bitmap.Config config, boolean isMutable) : Bitmap 使用config配置复制一个Bitmap
copyPixelsFromBuffer(Buffer src) : void 从一个Buffer对象中复制出所有的像素
copyPixelsToBuffer(Buffer dst) : void 将Bitmap的所有像素都复制到Buffer中
static createBitmap(Bitmap source, int x, int y, int width, int height) : Bitmap 从已有的Bitmap对象中取一个子集
static createBitmap(int[] colors, int width, int height, Bitmap.Config config) : Bitmap 根据颜色矩阵生成一幅位图
static createBitmap(DisplayMetrics display, int width, int height, Bitmap.Config config) 返回一个可更改的Bitmap
static createBitmap(int width, int height, Bitmap.Config config):Bitmap 返回一个可更改的Bitmap
static createScaledBitmap(Bitmap src, int dstWidth, int sdtHeight, boolean filter) 创建一个缩放到指定尺寸的Bitmap
describeContents() ...
eraseColor(int c) : void 将bitmap的所有像素都设置成同一颜色
extractAlpha(): Bitmap 生成一幅去掉Alpha值的Bitmap
extractAlpha(Paint paint, int[] offsetXY) .
getAllocationByteCount(): int 获取bitmap的尺寸
getByteCount() : int 获取存储图片最少需要的空间
getConfig(): Bitmap.Config 获取图片配置
getDensity() : int 获取图片密度
getGenerationId() : int 返回generationId
getHeight() 位图高度
getNinePatchChunck() : byte[] 返回一个数组,为.9.png使用
getPixel(int x, int y): int 获取具体位置的颜色值
getRowBytes() 图片中一行像素占多少空间
getScaledHeight(int targetDensity) : int, getScaledWidth(int targetDenisty) : int 特定目标屏幕密度下的高度
hasAlpha() : boolean 如果每个像素都支持透明效果的话就返回true
hasMipMap(): boolean ...
isMutable() : boolean 是否可以更改
isPremultiplied() : boolean 像素点是否是premulitplied格式存储
isRecycled() : boolean 图片是否已经被回收
prepareToDraw() 为绘制做缓存
reconfigure(int width, int height, Bitmap.Config config) : void 更改Bitmap的配置属性,但是不会影响底层的存储
recycle() : void 释放native层的对象,并释放对像素矩阵的引用
sameAs(Bitmap other) 如果另一个Bitmap拥有同样的尺寸,配置,像素值就返回true
setConfig(Bitmap.Config config): void reconfig方法的一种捷径
setDensity(int density) : void .
setHasAlpha(boolean hasAlpha) : void .
setHasMipMap(boolean hasMipMap) : void .
set Height(int height):void .
setPixel(int x, int y):void .
setPremultiplied(boolean premultiplied) : void .
setWidth(int width):void .
writeToParcel(Parcel p, int flags): void .

Bitmap.Config

这个类是用来配置像素格式的。它决定了像素的大小,图像的质量

变量名 大小(B) 补充说明
ALPHA_8 1 只有黑白灰,就像黑白电视,最节省空间
ARGB_4444 2 由于图像质量问题,建议使用ARGB_8888。deprecated since api 14
ARGB_8888 4 最高画质,建议使用,空间使用最多
RGB_565 2 颜色相对丰富,适合不做透明处理的图像

Bitmap.CompressFormat

变量名 说明
JPEG 有损压缩,画质不稳定,存储传输效率高
PNG 无损压缩,画质很好,存储传输效率低
WEBP api 14 以后才提供使用,效果未知

这里对压缩做一下说明。compress方法有三个参数:

第一个是格式,PNG格式是无损的,所以后面的第二个参数对它没有影响。另外两种格式都有影响。

第二个是压缩比,取值在0~100之间。数字越大图片质量越高,体积越大。100 代表不压缩,0代表尽全力压缩。

第三个是输出流。

reconfigure方法,它是不更改底层像素值的。调用这个方法之后只是“看起来”变了,不会影响内存。

getAllocationByteCount()getByteCount()分别需要API-19和API-12,api版本相对较高。事实上源码的计算很简单,如果app使用的时候受到api限制的话,完全可以自己计算:

public final int getBytesCount() {
return getRowBytes() * getHeight();
}

LruCache

基于LinkedHashMap的一种经典的内存缓存模型。它是用强引用控制的缓存。可以设置缓存的大小,个数。可以统计命中率,读写次数。它是线程安全的。从做缓存的角度来说,要比WeakHashMap要好很多。

api 12 以上可以直接使用。api 12 以下可以通过support v4包 使用。

它的接口十分简单明了

具体的接口参见:LruCache

DiskLruCache

DiskLruCache并不是谷歌官方的API。它是推荐给开发者使用的文件缓存的类。从名称上很好理解,文件系统中的Lru缓存。它的源码地址

它的原理 利用LinkedHashMap在内存中记录文件缓存的最近访问顺序。磁盘中利用了journal文件作为日志文件,记录文件读写操作。每次创建DiskLruCache的时候都会通过journal日志重建LinkedHashMap的对象,这样在每次重新创建的时候也可以保持之前LRU的效果。源码不长,有兴趣的同学自行研究。网络上也有比较详细的介绍。

可以控制的变量:

  1. 缓存路径。建议选择App的cache目录下;
  2. cache版本。cache版本升级的时候会把旧的缓存全部清除;
  3. cache大小。cache的大小要小于缓存路径下的可有
  4. 日志条数。默认2000条。

ImageLoader 笔记的更多相关文章

  1. 《Android源码设计模式》学习笔记之ImageLoader

    微信公众号:CodingAndroid cnblog:http://www.cnblogs.com/angel88/ CSDN:http://blog.csdn.net/xinpengfei521 需 ...

  2. 《android开发艺术探索》读书笔记(十二)--Bitmap的加载和Cache

    接上篇<android开发艺术探索>读书笔记(十一)--Android的线程和线程池 No1: 目前比较常用的缓存策略是LruCache和DiskLruCache,LruCache常被用作 ...

  3. ImageLoader初始化以及调用

    1.首先在当前程序的Application中调用ImageLoader的初始化init()方法 [java] view plain copy private void initImageLoader( ...

  4. Android开发笔记——以Volley图片加载、缓存、请求及展示为例理解Volley架构设计

    Volley是由Google开源的.用于Android平台上的网络通信库.Volley通过优化Android的网络请求流程,形成了以Request-RequestQueue-Response为主线的网 ...

  5. qml自学笔记------自己写相似于劲舞团的按键小游戏(中)

    接上篇<qml自学笔记------自己写类似于劲舞团的按键小游戏(上)> 第三部分DisplayPart.qml 代码的其它部分都是渣,就这里花了点时间,整个小游戏就靠这个文件. 首先,屏 ...

  6. git-简单流程(学习笔记)

    这是阅读廖雪峰的官方网站的笔记,用于自己以后回看 1.进入项目文件夹 初始化一个Git仓库,使用git init命令. 添加文件到Git仓库,分两步: 第一步,使用命令git add <file ...

  7. js学习笔记:webpack基础入门(一)

    之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...

  8. SQL Server技术内幕笔记合集

    SQL Server技术内幕笔记合集 发这一篇文章主要是方便大家找到我的笔记入口,方便大家o(∩_∩)o Microsoft SQL Server 6.5 技术内幕 笔记http://www.cnbl ...

  9. PHP-自定义模板-学习笔记

    1.  开始 这几天,看了李炎恢老师的<PHP第二季度视频>中的“章节7:创建TPL自定义模板”,做一个学习笔记,通过绘制架构图.UML类图和思维导图,来对加深理解. 2.  整体架构图 ...

随机推荐

  1. a标签文字选中后的颜色样式更改

    ::selection 选择器,选择被用户选取的元素部分.是css3的用法,讲真,我觉得这个东西没必要特地去写.因为选中样式默认的会根据你的背景颜色还有字体color来设置颜色 这是我默认的样式

  2. LVDS/DVI/HDMI Interface

    数字视频信号 以SXGA为例,其时序如下: 垂直:         水平: 图中DSPTMG为使能信号,VSYNC为场同步信号,HSYNC为行同步信号.在行场的消隐期(T1与T7),DSPTMG为低电 ...

  3. 阿里开源的热补丁框架AndFix使用教程

    阿里巴巴推出的AndFix框架 首次给出大家这个框架的地址:https://github.com/alibaba/AndFix 对源码比较感兴趣的同学们可以自行研究代码 AndFix原理介绍 AndF ...

  4. 阿里巴巴开源前端框架--Weex实践

    Weex是最近很火很NB的一个技术产品,因为本篇介绍的是怎样使用Weex的最佳实践,所以就不罗里吧嗦的夸它怎么怎么好了,感兴趣的可以访问Weex HomePage,或加入旺旺群:1330170019. ...

  5. CSS注释

    CSS注释 1./*注释内容*/ /*-moz-background-origin:border; -webkit-background-origin:border; -moz-background- ...

  6. Java求素数时出现错误

    Java求素数时出现错误 1.具体错误如下 No enclosing instance of type Prime is accessible. Must qualify the allocation ...

  7. Excel 2010高级应用-圆环图(七)

    Excel 2010高级应用-圆环图(七) 基本操作如下: 1.新建空白Excel文档,并命名为圆环图 2.单击"插入",并找到圆环图图样 3.单击圆环图图样,并在空白文档上生成图 ...

  8. Error: expected expression, got '}'

    1.错误描述 Error: expected expression, got '}' .globalEval/<@http://localhost:8080/Sys/resource/globa ...

  9. 石子归并 51Nod - 1021

    N堆石子摆成一条线.现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的代价.计算将N堆石子合并成一堆的最小代价.   例如: 1 2 3 4,有 ...

  10. iOS - Quartz 2D 手势截屏绘制

    1.绘制手势截屏 具体实现代码见 GitHub 源码 QExtension QTouchClipView.h @interface QTouchClipView : UIView /** * 创建手势 ...