[关闭]
@ZeroGeek 2016-12-11T03:18:54.000000Z 字数 12867 阅读 2140

图片加载框架Glide

lib


1.初识Glide

为什么叫Glide?平滑、滑翔的意思。
Sam sjudd,google的员工开发,目前star 12000+
2014年被google,IO大会推荐

1.1概述

1.1.1简介

Glide是Android平台上一个高效、开源的(媒体管理)图像加载框架,包含媒体解码,内存和磁盘缓存以及资源池(复用),并简单易用。

Glide支持抓取,解码和展示(本地)视频,图片和GIF。 Glide默认使用基于HttpUrlConnection的网络协议栈,支持Volley和OkHttp网络请求库。

目的:一个是实现平滑的图片列表滚动效果,另一个是支持远程图片的获取、大小调整和展示。

1.1.2特点

  1. 使用简单
  2. 可配置度高,自适应程度高
  3. 支持常见图片格式,jpg、png、gif、webp
  4. 支持多种数据源,网络、资源、assets 、File、Uri等
  5. 高效缓存策略支持内存和硬盘缓存
  6. 生命周期集成根据Activity/Fragment生命周期自动管理请求
  7. 高效处理Bitmap

1.2 Hello Glide

1.2.1 配置和基本使用

Glide需要依赖Support Library v4
添加依赖

  1. dependencies {
  2. compile 'com.github.bumptech.glide:glide:3.7.0'
  3. }

Glide加载图片的方法和Picasso很相似,流式接口
基本使用,只能在主线程启动。

  1. java.lang.IllegalArgumentException: You must call this method on the main thread

记得加上网络权限

  1. Glide.with(context)
  2. .load("http://7xjung.com1.z0.glb.clouddn.com/zeromp3.jpg")
  3. .into(imageView);
  1. with(Context context)
  2. with(Activity activity)
  3. with(FragmentActivity activity)

将Activity/Fragment作为with()参数的好处是,图片加载会和Activity/Fragment的生命周期保持一致,比如 onPaused状态在暂停加载,在onResumed的时候又自动重新加载。
(有兴趣的可以去看下怎么实现这种生命周期绑定的)

1.2.2 加载图片方式,多种数据源

1.String参数
load(String string)
2.资源定位符
load(Uri uri)
3.本地文件
load(File file)
4.资源文件
load(Integer resourceId)
5.URL,不建议使用,在glide 4.0移除了
load(URL url) @Deprecated
6 字节数组
load(byte[] model)

1.2.3 设置占位符

placeholder()

  1. Glide.with(this)
  2. .load("http://7xjung.com1.z0.glb.clouddn.com/zeromp3.jpg")
  3. .placeholder(R.drawable.placeholder)
  4. .into(imageView);

1.2.4 加载失败

error()

  1. Glide.with(context)
  2. .load("http://7xjung.com1.z0.glb.clouddn.com/zeromp3.jpg")
  3. .placeholder(R.drawable.placeholder)
  4. .error(R.drawable.error)
  5. .into(imageView);

加载失败了,想知道原因,提供了失败监听的方法

  1. private RequestListener<String, GlideDrawable> requestListener = new RequestListener<String, GlideDrawable>() {
  2. @Override
  3. public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {
  4. // do something
  5. return false;
  6. }
  7. @Override
  8. public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
  9. return false;
  10. }
  11. };
  1. Glide.with(context)
  2. .load("http://7xjung.com1.z0.glb.clouddn.com/zeromp3.jpg")
  3. .listener(requestListener)
  4. .into(imageView);

1.2.5 默认自带渐入动画

crossFade()
crossFade(int duration),可用它来指定动画执行的时间,默认300ms

animate() 自定义动画
animate(ViewPropertyAnimation.Animator animator)
animate(int animationId)

不想任何动画,使用dontAnimate()

  1. Glide.with(context)
  2. .load("http://7xjung.com1.z0.glb.clouddn.com/zeromp3.jpg")
  3. .placeholder(R.drawable.placeholder)
  4. .error(R.drawable.error)
  5. .listener(requestListener)
  6. .dontAnimate()
  7. .into(imageView);

1.2.6 支持缩略图

  1. Glide.with(context)
  2. .load(R.drawable.placeholder)
  3. .thumbnail(0.1f)
  4. .into(imageView);

注:先加载缩略图,再加载原图

1.2.7 调整图片大小

override(int width, int height) ,像素。

  1. Glide.with(context)
  2. .load(R.drawable.placeholder)
  3. .override(600, 300)
  4. .into(imageView);

Glide加载图片大小是自动调整的,他根据ImageView的尺寸自动调整加载的图片大小,并且缓存的时候也是按图片大小缓存,每种尺寸都会保留一份缓存,如果图片不会自动适配到 ImageView,可调用这个方法。

1.2.8 缩放

CenterCrop():缩放图像让它填充到 ImageView 界限内并且裁剪额外的部分。ImageView 可能会完全填充,但图像可能不会完整显示

fitCenter() 缩放图像让图像都测量出来等于或小于 ImageView 的边界范围。该图像将会完全显示,但可能不会填满整个 ImageView。

  1. Glide
  2. .with(context)
  3. .load(UsageExampleListViewAdapter.eatFoodyImages[0])
  4. .override(600, 200)
  5. .centerCrop()
  6. .into(imageView);

1.2.9 transform()转换

旋转,圆角等
继承BitmapTransformation

  1. private class CornersTransform extends BitmapTransformation {
  2. public CornersTransform(Context context) {
  3. super(context);
  4. }
  5. @Override
  6. protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
  7. if (toTransform == null) return null;
  8. Bitmap result = pool.get(toTransform.getWidth(), toTransform.getHeight(), Bitmap.Config.ARGB_8888);
  9. if (result == null) {
  10. result = Bitmap.createBitmap(toTransform.getWidth(), toTransform.getHeight(), Bitmap.Config.ARGB_8888);
  11. }
  12. Canvas canvas = new Canvas(result);
  13. Paint paint = new Paint();
  14. paint.setShader(new BitmapShader(toTransform, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP));
  15. paint.setAntiAlias(true);
  16. RectF rectF = new RectF(0f, 0f, toTransform.getWidth(), toTransform.getHeight());
  17. canvas.drawRoundRect(rectF, 5f, 5f, paint);
  18. return result;
  19. }
  20. @Override
  21. public String getId() {
  22. return getClass().getName();
  23. }
  24. }

Bitmap 复用

为了减少垃圾回收,你可以说使用BitmapPool接口来释放不想要的Bitmap或者复用存在的Bitmap。一个在Transformation中复用Bitmap典型的例子:从pool中获取Bitmap,使用该Bitmap创建一个Canvas,然后使用Matrix/Paint/Shader来变换图像并绘制到Canvas上。为了正确有效地在Transformation中复用Bitmap中,遵守下面的规则:
在transform()不要回收资源或者把Bitmap放入Bitmap池中,这些步骤会自动完成。
如果你从Bitmap池中获取了多个Bitmap,或者没有使用从pool中获取到的Bitmap,确保把多余的Bitmap放回pool中。
如果你的Trasformation并没有替换调原始图片(比如,图片已经满足你要求的大小,直接返回了),请在transform()方法中返回原始的资源或者Bitmap。

  1. Glide.with(context)
  2. .load(pngUrl)
  3. .transform(new CornersTransform(context))
  4. .error(R.drawable.error)
  5. .into(imageView);

1.2.10 显示gif和本地视频

  1. Glide.with(context)
  2. .load("http://7xjung.com1.z0.glb.clouddn.com/loadgif.gif")
  3. .into(imageView);
  1. Glide.with(context)
  2. .load(pngUrl)
  3. .asGif()
  4. .error(R.drawable.error)
  5. .into(imageView);

asGif(),检查是否gif
asBitmap(),即使是gif,也当作bitmap处理,(用第一帧)

加载本地视频,略过

  1. Glide
  2. .with( context )
  3. .load( Uri.fromFile( new File( filePath ) ) )
  4. .into( imageViewGifAsBitmap );

2.Glide原理剖析

总体设计:
整个库分为 RequestManager(请求管理器),Engine(数据获取引擎)、 Fetcher(数据获取器)、MemoryCache(内存缓存)、DiskLRUCache、Transformation(图片处理)、Encoder(本地缓存存储)、Registry(图片类型及解析器配置)、Target(目标) 等模块。

简单的讲就是 Glide 收到加载及显示资源的任务,创建 Request 并将它交给RequestManager,Request 启动 Engine 去数据源获取资源(通过 Fetcher ),获取到后 Transformation 处理后交给 Target。

2.1 图片质量

  1. public enum Config {
  2. // No color information is stored.With this configuration, each pixel requires 1 byte of memory.8位Alpha位图 每个像素占用1byte内存
  3. ALPHA_8 (1),
  4. // Each pixel is stored on 2 bytes,代表8位RGB位图 每个像素占用2byte内存
  5. RGB_565 (3),
  6. /**
  7. * Each pixel is stored on 2 bytes. The three RGB color,代表16位ARGB位图 每个像素占用2byte内存
  8. */
  9. @Deprecated
  10. ARGB_4444 (4),
  11. /**
  12. * Each pixel is stored on 4 bytes. 代表32位ARGB位图 每个像素占用4byte内存.文档里推荐的,尽可能使用这种
  13. */
  14. ARGB_8888 (5);
  15. }
位图位数越高代表其可以存储的颜色信息越多,图像越逼真,占用内存越大。Android 在处理图片工作,它会以像素形式加载图片到内存中去.
一张图片(Bitmap)占用的内存=图片长度*图片宽度*单位像素占用的字节数。

GlideBuilder 类也允许你配置一个App全局使用的Bitmap的Config属性。 Glide默认使用RGB_565,因为它每个像素只需要2bytes(16bit)的空间,它仅需要高质量图片(既系统默认的ARGB_8888)一半的内存空间。但是,这对于某些图片这可能会出现『条带』的问题,而且它也不支持透明度。 如果在你的应用中『条带』是一个重要的问题,或者你需要尽可能好的图片质量,你可以使用GlideBuilder的setDecodeFormat方法设置DecodeFormat.ALWAYS_ARGB_8888作为首选配置。

2.2 缓存机制浅析

  1. Glide内存为三块:
    ActiveResourceCache:缓存当前正在使用的资源(注意是弱引用)
    LruResourceCache: 缓存最近使用过但是当前未使用的资源,LRU算法
    LruBitmapPool:缓存所有被释放的图片,内存复用,LRU算法

强引用 + LRU 算法

Glide 的 BitmapPool
Glide 构建了一个 BitmapPool , Bitmap 申请和回收都是透过 BitmapPool 来处理的。新加载图片时,会先从 BitmapPool 里面找有没有相应大小的 Bitmap ,有则直接使用,没有才会申请新的 Bitmap ;回收时,则会提交给 BitmapPool , 供下次使用。 这种方式极大的减少了 Bitmap 的申请和回收操作,使得 GC 频度降低了很多。

Glide 内存缓存的特点

① Glide 的内存缓存有个 active 的设计 从内存缓存中取数据时,不像一般的实现用 get,而是用 remove ,再将这个缓存数据放到一个 value 为软引用的 activeResources map 中,并计数引用数,在图片加载完成后进行判断,如果引用计数为空则回收掉。

② 内存缓存更小图片 Glide 以 url、viewwidth、viewheight、屏幕的分辨率等做为联合 key,将处理后的图片缓存在内存缓存中,而不是原始图片以节省大小

内存缓存的是Drawable 而不是Bitmap 理由是Drawable相对Bitmap来说有很大的内存优势

2.磁盘缓存

DiskCacheStrategy.NONE 什么都不缓存
DiskCacheStrategy.SOURCE 仅仅只缓存原来的全分辨率的图像。
DiskCacheStrategy.RESULT 仅仅缓存最终的图像,转换后的
DiskCacheStrategy.ALL 缓存所有版本的图像(默认行为)

磁盘缓存策略

Glide 依赖于 DiskLRUCache 开源库去完成本地缓存工作(即重写其 put,get,delet,clear 方法),并使用 SHA-256 编码生成关键 key 。
磁盘缓存会缓存几乎同一图像的所有尺寸,包括原始图像,全分辨率图像和另外小版本的图像。比如,如果你请求的一个图像是 1000×1000 像素的,但你的 ImageView 是 500×500 像素的,Glide 将会把这两个尺寸都进行缓存。

3.进阶用法

  1. 自定义Target

SimpleTarget

如果你只是想加载一个Bitmap,并不想直接展示给用户而是有一些特殊用途,比如在通知栏中显示或者作为头像上传。 Glide也可以做到。 SimpleTarget为Target接口提供了大部分的默认实现。你可以专注于处理加载的结果。 为了使用SimpleTarget,你需要在它的构造函数中提供你要加载的资源的宽和高(单位像素),你还需要实现onResourceReady(T resource, GlideAnimation animation)方法。 一个典型的使用SimpleTarget的例子如下:

一些警告

正常情况下,你加载一个资源会把他们放到view中。当你的Activity或者Fragment被pause或者destroy时,Glide会暂停或取消加载,确保你不会加载那些根本不会显示的资源。 可是当我们使用SimpleTarget的时候,这可能并不是我们希望的行为。所以,当你调用Glide.with(context)的时候,你可以传入Application的context,而不是传入Activity或者Fragment。 此外,考虑到长时间的加载操作可能导致内存泄漏,请考虑使用静态内部类,而不是匿名内部类。

ViewTarget

如果你想加载一个图片到View中,但是你想观察或者覆盖Glide的默认行为。你可以覆盖ViewTarget或者它的子类。 当你想让Glide来获取view的的大小,但是由自己来启动动画和设置资源到view中,ViewTarget是个不错的选择。

NotificationTarget
AppWidgetTarget

  1. 自定义GlideModule,自己配置
    Glide module 是一个抽象方法,全局改变 Glide 行为的一个方式。
    要全局的去声明这个类,让 Glide 知道它应该在哪里被加载和使用。
    AndroidManifest.xml 的 标签内去声明这个刚刚创建的 Glide module。
    你可以同时声明多个 Glide module。Glide 将会(没有特定顺序)得到所有的声明 module。因为你当前不能定义顺序,请确保定制不会引起冲突!

.using(),去指定你想要Module

  1. public class AppGlideModule implements GlideModule {
  2. @Override
  3. public void applyOptions(Context context, GlideBuilder builder) {
  4. builder.setBitmapPool(new LruBitmapPool(diskCacheSize));
  5. builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);
  6. builder.setMemoryCache(new LruResourceCache(memoryCacheSize));
  7. builder.setDiskCache(new ExternalCacheDiskCacheFactory(context, filePath, diskCacheSize));
  8. builder.setDiskCacheService(new FifoPriorityThreadPoolExecutor(1));
  9. builder.setResizeService(new FifoPriorityThreadPoolExecutor(1));
  10. }
  11. @Override
  12. public void registerComponents(Context context, Glide glide) {
  13. }
  14. }

它们所有的值都会被收集在Bundle对象中并且使其可以作为组件的 PackageItemInfo.metaData 字段
metadata是一组供父组件使用的名值对(name-value pair)

  1. ApplicationInfo appInfo = this.getPackageManager()
  2. .getApplicationInfo(getPackageName(),PackageManager.GET_META_DATA);
  3. appInfo.metaData.getString("GlideModule");

先来看看默认配置

  1. Glide createGlide() {
  2. if (sourceService == null) {
  3. final int cores = Math.max(1, Runtime.getRuntime().availableProcessors());
  4. sourceService = new FifoPriorityThreadPoolExecutor(cores);
  5. }
  6. if (diskCacheService == null) {
  7. diskCacheService = new FifoPriorityThreadPoolExecutor(1);
  8. }
  9. MemorySizeCalculator calculator = new MemorySizeCalculator(context);
  10. if (bitmapPool == null) {
  11. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
  12. int size = calculator.getBitmapPoolSize();
  13. bitmapPool = new LruBitmapPool(size);
  14. } else {
  15. bitmapPool = new BitmapPoolAdapter();
  16. }
  17. }
  18. if (memoryCache == null) {
  19. memoryCache = new LruResourceCache(calculator.getMemoryCacheSize());
  20. }
  21. if (diskCacheFactory == null) {
  22. diskCacheFactory = new InternalCacheDiskCacheFactory(context);
  23. }
  24. if (engine == null) {
  25. engine = new Engine(memoryCache, diskCacheFactory, diskCacheService, sourceService);
  26. }
  27. if (decodeFormat == null) {
  28. decodeFormat = DecodeFormat.DEFAULT;
  29. }
  30. return new Glide(engine, memoryCache, bitmapPool, context, decodeFormat);
  31. }

内存缓存和缓存池

GlideBuilder类允许你设置内存缓存大小,而且可以实现自定义的MemoryCache和BitmapPool。
大小

默认大小是由MemorySizeCalculator类决定的。MemorySizeCalculator类会考虑该设备的屏幕大小、可用内存来计算出一个合理的默认值。

Bitmap池

Glide的Bitmap池主要作用是,可以让各种尺寸的Bitmap被复用,可以可观地减少由垃圾回收引起的内存抖动。在解码图片时,需要给像素数组分配空间,这会触发垃圾回收。

类似LinkedHashMap,但是加入LRU算法
140多行
private final GroupedLinkedMap groupedMap = new GroupedLinkedMap();

  1. 使用ModelLoader,下载自定义大小图片
    http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0927/3521.html

为何请求特定尺寸图片
我们在做的最新项目用到一个媒体服务器,提供非常高的分辨率图片服务(图片分辨率在6000x4500像素)。我们可以使用直接链接到源文件,在考虑设备带宽、内存和电池的时候是相当低效的。即使在今天的设备上显示高分辨率,对于显示如此极端的分辨率,还是没有任何好处。因此Glide总是测量ImageView的尺寸,然后降低图片的内存消耗以匹配它。然而,无法避免要去下载和计算,完成降低图片大小的任务。

  1. 使用signature()实现自定义cacheKey:

    Glide 以 url、viewwidth、viewheight、屏幕的分辨率等做为联合key,官方api中没有提供删除图片缓存的函数,官方提供了signature()方法,将一个附加的数据加入到缓存key当中,多媒体存储数据,可用MediaStoreSignature类作为标识符,会将文件的修改时间、mimeType等信息作为cacheKey的一部分,通过改变key来实现图片失效相当于软删除。
    1.)使用StringSignature

Glide.with(this).load(yourFileDataModel).signature(new StringSignature("1.0.0")).into(imageView);

2.)使用MediaStoreSignature

Glide.with(this) .load(mediaStoreUri).signature(new MediaStoreSignature(mimeType, dateModified, orientation)).into(view);

5 后台下载
downloadOnly方法

Glide的downloadOnly(int, int)方法允许你把图片bytes下载到磁盘缓存中,以便以后获取使用。你可以在UI线程异步地调用downloadOnly(),也可以在后台线程同步的使用。但是,注意他们的参数有些不同,异步API的参数是Target,同步api的参数是宽和高的整数值。 为了在后台线程下载图片,你必须使用同步方法

图片缓存

之前我们一直在使用SoftReference软引用,SoftReference是一种现在已经不再推荐使用的方式,因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用变得不再可靠。

Lru缓存机制本质上就是存储在一个LinkedHashMap存储,为了保障插入的数据顺序,方便清理

DiskLruCache
Glide 依赖于 DiskLRUCache、GifDecoder 等开源库去完成本地缓存和 Gif 图片解码工作。

小结

  1. Glide 优点
    (1) 图片缓存->媒体缓存
    Glide 不仅是一个图片缓存,它支持 Gif、WebP、缩略图。甚至是 Video,所以更该当做一个媒体缓存。

(2) 支持优先级处理

(3) 与 Activity/Fragment 生命周期一致,支持 trimMemory
Glide 对每个 context 都保持一个 RequestManager,通过 FragmentTransaction 保持与 Activity/Fragment 生命周期一致,并且有对应的 trimMemory 接口实现可供调用。

(4) 支持 okhttp、Volley
Glide 默认通过 UrlConnection 获取数据,可以配合 okhttp 或是 Volley 使用。实际 ImageLoader、Picasso 也都支持 okhttp、Volley。

(5) 内存友好
① Glide 的内存缓存有个 active 的设计
从内存缓存中取数据时,不像一般的实现用 get,而是用 remove,再将这个缓存数据放到一个 value 为软引用的 activeResources map 中,并计数引用数,在图片加载完成后进行判断,如果引用计数为空则回收掉。

② 内存缓存更小图片
Glide 以 url、view_width、view_height、屏幕的分辨率等做为联合 key,将处理后的图片缓存在内存缓存中,而不是原始图片以节省大小

③ 与 Activity/Fragment 生命周期一致,支持 trimMemory

④ 图片默认使用默认 RGB_565 而不是 ARGB_888
虽然清晰度差些,但图片更小,也可配置到 ARGB_888。

其他:Glide 可以通过 signature 或不使用本地缓存支持 url 过期

Glide 功能强大,但代码量大、流转复杂。

目前哪些App使用了Glide

Yelp

大致150kb左右

链接

https://futurestud.io/tutorials/glide-getting-started
http://mrfu.me/2016/02/27/Glide_Getting_Started/
https://github.com/bumptech/glide
http://frodoking.github.io/2015/10/10/android-glide/
http://angeldevil.me/2016/09/05/glide/
http://blog.qiji.tech/archives/10029
http://www.cnblogs.com/whoislcj/p/5551040.html

private SimpleTarget<GlideDrawable> target = new SimpleTarget<GlideDrawable>() {
    @Override
    public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) {
        // do something
        imageView.setImageDrawable(resource);
    }
};


        Glide.with(context)
            .load(pngUrl)
            .into(target);
  1. public class AppGlideModule implements GlideModule {
  2. @Override
  3. public void applyOptions(Context context, GlideBuilder builder){
  4. builder.setBitmapPool(new LruBitmapPool(diskCacheSize));
  5. builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);
  6. builder.setMemoryCache(newLruResourceCache(memoryCacheSize);
  7. builder.setDiskCache(new ExternalCacheDiskCacheFactory(context, filePath, diskCacheSize));
  8. builder.setDiskCacheService(new FifoPriorityThreadPoolExecutor(1));
  9. builder.setResizeService(new FifoPriorityThreadPoolExecutor(1));
  10. }
  11. @Override
  12. public void registerComponents(Context context, Glide glide) {
  13. }
  14. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注