ImageLoader 笔记

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条。
posted @ 2016-08-20 20:42  Nick2019  阅读(882)  评论(0编辑  收藏  举报