前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ImageLoader 相关知识点

ImageLoader 相关知识点

作者头像
yuxiaofei93
发布2018-09-11 16:33:28
5330
发布2018-09-11 16:33:28
举报

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条。
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016.09.14 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • BitmapFactory
  • BitmapFactory.Options
  • Bitmap
  • Bitmap.Config
  • Bitmap.CompressFormat
  • LruCache
  • DiskLruCache
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档