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. 网络基础Cisco路由交换四

    NAT及静态转换 概述(NAT:网络地址转化) 作用: 通过将内部网络的私有ip地址翻译成全球唯一的公网ip地址, 使内部网络可以连接到互联网等外部网络上. NATA的特性 优点: 节省公有合法ip地 ...

  2. $_FILES数组为空的原因

    今天做上传的文件时候,打印$_files总是为空,查阅了下资料. 发现是 max_file_uploads=0 知道了原因 file_uploads = On upload_max_filesize ...

  3. hi3531的i2c部分

    一.关于编译Hi3531 SDK: 之前编译SDK时编译到make uImage总出错,一是找不到.config文件,这个问题是必须先make menuconfig 然后保存.config文件. 二是 ...

  4. CSS精心整理的面试题

    CSS精心整理的面试题 1.设置边框的样式用border-style实现,设置边框的颜色用border-color实现 2.CSS的语法由选择器.属性.值三部分组成 3.设置一个div的最小宽度为50 ...

  5. Linux显示文件和目录的详细资料

    Linux显示文件和目录的详细资料 youhaidong@youhaidong-ThinkPad-Edge-E545:~$ ls -l 总用量 56 -rw-r--r-- 1 youhaidong y ...

  6. hihocoder1388 Periodic Signal

    FFT 就可以了 比赛时候没时间做了 #include<bits/stdc++.h> using namespace std; typedef long long ll; const in ...

  7. PyCharm运行报编码错误

    运行报如下错误: SyntaxError: Non-ASCII character '\xe8' in file /home/ubuntu/code/201803091253-text.py on l ...

  8. 谈谈MySQL的事务隔离级别

    这篇文章能够阐述清楚跟数据库相关的四个概念:事务.数据库读现象.隔离级别.锁机制 一.事务 先来看下百度百科对数据库事务的定义: 作为单个逻辑单元执行一系列操作,要么完全执行,要么完全不执行.事务处理 ...

  9. 【CJOJ P1333】【HNOI2012】矿场搭建

    [HNOI2012]矿场搭建 Description 煤矿工地可以看成是由隧道连接挖煤点组成的无向图.为安全起见,希望在工地发生事故时所有挖煤点的工人都能有一条出路逃到救援出口处.于是矿主决定在某些挖 ...

  10. 【THUWC 2017】在美妙的数学王国中畅游

    数学王国里有n座城市,每座城市有三个参数\(f\),\(a\),\(b\),一个智商为\(x\)的人经过一座城市的获益\(f(x)\)是 若\(f=1\),则\(f(x)=\sin(ax+b)\): ...