27
2017
09

Day7 Glide的三级数据结构缓存设计

效果图:

点此进入目录:[干货] 十天 教你从创意到上线APP

在图片加载库繁荣昌盛的今天,选择一个适合自己使用的图片加载库已经成为了每一个Android开发者的必经之路。现在市面上知名的图片加载库有UILPicassoVolley ImageLoaderFresco以及我们今天的主角Glide,它们各有千秋。但是Glide在众多图片加载库中独受青睐,我们来看下它的各种用法。

一、Glide都有哪些优点?

有Android开发经验的程序员可以跳过这一节,但是对于新手应当弄明白为什么你要使用Glide来代替你自己的实现,说的简单点,还不是因为它有众多优点么,接下来带大家一起去看看。

在Android中使用图片的时候是相当麻烦的,因为需要一个像素一个像素地加载这些图片到内存。一个中端手机(5百万像素)所拍摄的一张照片有2592×1936这么大!这会占用大概19M内存!如果你再加上各种好坏不一的网络下的图片请求,同时要处理缓存、图片加载等问题,焦头烂额。如果你这时候使用了一个像Glide一样经过不断优化和严格测试的图片处理库,你会庆幸你节省了大量的时间,同时也避免了很多头疼的问题。

所以,我们为什么要用Glide呢?就是为了避免我们处理纷繁复杂的图片缓存、加载等问题,从而剩下更多精力专注于业务逻辑的实现。

二、Glide的简单使用

1、添加Glide到依赖库

(1)Gradle中添加

和大多数依赖库一样,在Gradle项目中只需要在build.gradle中添加一行:

compile 'com.github.bumptech.glide:glide:3.7.0'
(2)Maven

如果你用的是Maven构建项目,可以这么添加:

<dependency>
<groupId>com.github.bumptech.glide</groupId>
<artifactId>glide</artifactId>
<version>3.7.0</version>
<type>aar</type>
</dependency>

2、从 URL加载图片

和Picasso类似的是,Glide同样使用了一个流接口。用Glide完成一个完整的图片加载功能请求,需要向其构造器中至少传入3个参数,分别是:
- with(Context context)
Context是许多Android API需要调用的, Glide也不例外。这里Glide非常方便,你可以任意传递一个Activity或者Fragment对象,它都可以自动提取出上下文。
- load(String imageUrl)
这里传入的是你要加载的图片的URL,大多数情况下这个String类型的变量会链接到一个网络图片。
- into(ImageView targetImageView)
将你所希望解析的图片传递给所要显示的ImageView。

下面我举个栗子:
ImageView targetImageView = (ImageView) findViewById(R.id.imageView);
String internetUrl = "http://image85.360doc.com/DownloadImg/2015/05/1722/53667314_4.jpg";

Glide
    .with(context)
    .load(internetUrl)
    .into(targetImageView);
然后你就会发现在你想要的地方加载出如下图片:

没错!加载图片只需上面这几行!如果这个URL链接的图片的确存在,并且你的ImageView可见,你将会在1到2秒见到这张图片被加载。假如这张图片不存在,Glide会回调相应的出错接口。

3、从Res资源中加载图片

然后我们介绍从Android资源中加载。不同于上面的String类型的网络URL,这里是一个Int型的资源id。加载方法如下:

int resourceId = R.mipmap.ic_launcher;

Glide
    .with(context)
    .load(resourceId)
    .into(imageViewResource);

你知道R.mipmap…这样的资源定位方式吗?这是Android的一个处理图标的新方法。虽然,你可以直接在ImageView的属性里添加这一资源。但是,如果你使用Glide这种更高级的方式进行动态转换,你的应用可以做得非常有趣。

4、从文件中加载图片

从资源文件加载通常是固定的,当你让用户任意选择一张图片来显示的时候,这个文件的路径并非是开发人员预先设定的,从图片文件中加载对于实际应用将会非常有用。需要传递的参数也仅仅是一个文件对象,举个栗子:

File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "WillFlow.jpg");

Glide
    .with(context)
    .load(file)
    .into(imageViewFile);

5、从Uri加载图片

最后介绍从Uri中加载图片,这里的请求跟上面的方法并无太大差异:

Uri uri = resourceIdToUri(context, R.mipmap.future_studio_launcher);

Glide
    .with(context)
    .load(uri)
    .into(imageViewUri);

这个可以是任何Uri. 这里为了演示,我们只创建了一个指向桌面图标的Uri。

如果你想要将资源id转换为一个Uri,可以这样:

public static final String ANDROID_RESOURCE = "android.resource://";
public static final String FOREWARD_SLASH = "/";

private static Uri resourceIdToUri(Context context, int resourceId) {
    return Uri.parse(ANDROID_RESOURCE + context.getPackageName() + FOREWARD_SLASH + resourceId);
}

当然,Uri并不一定是从资源id中创建,它可以是任意Uri。

三、Glide添加占位图

1、默认占位图

空白的ImageView在任何UI中看起来都是丑陋的,Android中更是如此。所以如果你在使用Glide从网络上加载图片,假如你网络的环境不好,加载过程可能需要花费大量的时间。那么这时候就需要一个占位图先显示出图片,以此避免空白的显示,直到正确的图片加载完成并处理完毕后加载出来。

Glide的流接口让这个工作变得很简单!我们只要调用.placeHolder()并传递进去一个图片资源,Glide会显示那个占位图,直到正确的图片准备完毕。

Glide
    .with(context)
    .load("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1504579473379&di=647a164ef14bf1cbf12917d3f53151ba&imgtype=0&src=http%3A%2F%2Fwww.taopic.com%2Fuploads%2Fallimg%2F140701%2F240398-140F10J52436.jpg")
    .placeholder(R.mipmap.ic_launcher)
    .into(imageViewPlaceholder);

毫无疑问,你不能设置一个网络的Url当作占位图。因为这样的话占位图也需要时间去加载,这样就使得占位图失去了意义,所以App内的资源和图片是推荐使用的。同时,由于Glide的load()可以接受各式的参数,这些参数可能是不能加载的(无网络连接、服务器挂掉、被删除等)或者其他无法访问的,那么我们就需要用到出错占位图了。

2、出错占位图

我们假设我们的App尝试从网页加载一张图片但网页不可访问,Glide会去给我们进行出错的回调,这时我们可以采取合适的行动(这个后面会说到)。这时一个出错的占位图可以用来表明图片无法加载的情况,使得APP看起来更加友好。

跟预加载的占位图一样,调用Glide的流接口即可:
Glide
    .with(context)
    .load("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1504579905069&di=5238a961892eef232fa51f717ded3946&imgtype=0&src=http%3A%2F%2Fimg1.xiazaizhijia.com%2Fwalls%2F20150120%2F1024x768_ad05a08bbd0b650.jpg")
    .placeholder(R.mipmap.ic_launcher)
    .error(R.mipmap.future_studio_launcher)
    .into(imageViewError);

上面的代码中,如果从load()里传入的图片无法被加载,Glide会显示R.mipmap.future_studio_launcher来代替。再次强调一下,error()可以接受的只能是已经被初始化的图片资源或者指向图片资源的id。

四、Glide中的过渡动画

1、使用动画

无论你是否使用占位图,图片的改变对于UI来说是相当大的一个动作。一个简单的方法可以让这个变化更平滑从而更让人眼接受,这就是使用crossfade动画。Glide支持标准的crossfade动画,对于目前版本3.7.0是默认可用的。如果你想要使用crossfade动画,你只要在在构造器里添加这样一个调用:

Glide.with(context)
    .load("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1504580292512&di=067e20c865d5f97cf2a4558f318d1f8a&imgtype=0&src=http%3A%2F%2Fimg.taopic.com%2Fuploads%2Fallimg%2F130131%2F240473-13013109324311.jpg")
    .placeholder(R.mipmap.ic_launcher)
    .error(R.mipmap.future_studio_launcher)
    .crossFade()
    .into(imageViewFade);

如果你想要减慢(或加快)动画,crossFade()方法提供了另外一个特征:crossFade(int duration),我们可以传入一个毫秒级的时间进去感受一下,默认的动画时间是300毫秒。

2、关闭动画

如果你只是直接显示图片,而不需要crossfade效果,那就在Glide的请求构造里调用.dontAnimate():

Glide
    .with(context)
    .load("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1504580292506&di=25693b1d27ac0c3fc6f36c037920d016&imgtype=0&src=http%3A%2F%2Fimg01.taopic.com%2F161009%2F240386-16100Z9320636.jpg")
    .placeholder(R.mipmap.ic_launcher)
    .error(R.mipmap.future_studio_launcher)
    .dontAnimate()
    .into(imageViewFade);

有个重要的事情要提醒你,这些参数都是独立的并且设置不依赖彼此。例如,你可以只设置.error()而不用调用.placeholder()。你可以设置crossFade()动画而不用设置占位图… 参数的任意结合都是可行的。

五、Glide中图片大小与缩放

1、设置大小

理想情况下服务器能够返回给恰好所需分辨率的图片,这是在网络带宽、内存消耗和图片质量下的完美方案。跟Picasso比起来,Glide在内存上占用更优化。Glide在缓存和内存里自动限制图片的大小去适配ImageView的尺寸。(Picasso也有同样的能力,但需要调用fit()方法。)用Glide时,如果图片不需要自动适配ImageView,调用override(horizontalSize, verticalSize),它会在将图片显示在ImageView之前调整图片的大小。

Glide
    .with(context)
    .load("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1504580292505&di=4d945076bd552fa737195159cacb25cb&imgtype=0&src=http%3A%2F%2Fwww.bz55.com%2Fuploads%2Fallimg%2F150617%2F140-15061G13041.jpg")
    .override(600, 200)
    .into(imageViewResize);

这个设置可以用在没有明确目标但已知尺寸的视图上。例如:如果App想要预先缓存在Splash屏幕上,还没法测量出ImageVIews具体宽高,但已经知道图片应当为多大,这时使用override可以提供一个指定的大小的图片。

2、设置缩放

对于任何图像的任何处理来说,调整图像的大小可能会扭曲长宽比丑化图片的显示。在大多数情况下,我们希望防止这种事情发生。Glide提供了变换去处理图片显示,通过设置centerCrop 和 fitCenter可以得到两个不同的效果(这类似于ImageView里面的用法)。

(1)CenterCrop

CenterCrop()会缩放图片让图片充满整个ImageView的边框,然后裁掉超出的部分,ImageVIew会被完全填充满,但是图片可能不能完全显示出。

Glide
    .with(context)
    .load("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1504580292492&di=8864ca8d00cee6849b628404f89ae7e1&imgtype=0&src=http%3A%2F%2Fbbs.kaitao.cn%2Fshangchuan%2F2013-08-27%2F20130827101902.jpg")
    .override(600, 200)
    .centerCrop()
    .into(imageViewResizeCenterCrop);
(2)FitCenter

FitCenter()会缩放图片让两边都相等或小于ImageView的所需求的边框。图片会被完整显示,可能不能完全填充整个ImageView。

Glide
    .with(context)
    .load("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1504580292492&di=8864ca8d00cee6849b628404f89ae7e1&imgtype=0&src=http%3A%2F%2Fbbs.kaitao.cn%2Fshangchuan%2F2013-08-27%2F20130827101902.jpg")
    .override(600, 200)
    .fitCenter() 
    .into(imageViewResizeFitCenter);

我们会之后的文章中介绍除了centerCrop() 和 fitCenter()以外的自定义变换方法,这里首先给出不同尺寸的图片在运用不同属性时的效果图:

六、Glide中的缓存

当你不断向上向下滑动多次后,你会发现图片会比之前加载地更快。在新手机上可能需要稍微多等一会。你可以很容易想到,这些图片由于被缓存到磁盘上,用的时候不必再从网络获取。Glide的缓存实现是基于Picasso的一个方法,让你可以更简单地使用。具体可以缓存的大小取决于设备磁盘的大小。当加载一张图片时,Glide使用这些资源:内存、磁盘和网络(根据由快到慢)。第二次加载的时候,你啥都不用做,一旦Glide智能地创建了合适大小的图片缓存,将为你分担了所有复杂工作。

1、内存缓存

如果你在前面用Glide用的很溜,你可能注意到你并不需要额外自己激活缓存。Glide本身自带缓存。然而,如果你的图片变化的非常快,你需要避免一些缓存。

从网络加载一个图片到ImageView:
Glide  .with( context )
    .load("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1504580292492&di=8864ca8d00cee6849b628404f89ae7e1&imgtype=0&src=http%3A%2F%2Fbbs.kaitao.cn%2Fshangchuan%2F2013-08-27%2F20130827101902.jpg")
    .skipMemoryCache( true )
    .into( imageViewInternet );

我们调用了.skipMemoryCache(true)去告诉Glide跳过内存缓存,这意味着Glide不会把这个图片缓存到内存里。重要是,这个只影响内存缓存!Glide为了避免以后的网络请求,仍然会缓存到磁盘。另外,由于Glide默认会将所有的图片资源缓存到内存中,因此没有必要手动调用.skipMemoryCache(false)了。

注意:如果你要对同一个URL做一个初始化的请求,第一次没使用.skipMemoryCache(true),然后第二次使用了,将会获取缓存在内存中的资源。当你调整缓存行为的时候,确保请求的都是指向同一个资源。

2、磁盘缓存

如上面所讲到的,即使你关闭了内存缓存,所请求的图片仍然会被保存在设备的磁盘存储上。如果你有一张不段变化的图片,但是都是用的同一个URL,你可能需要禁止磁盘缓存了。

你可以用.diskCacheStrategy()方法改变Glide的磁盘缓存行为,但不同于.skipMemoryCache()方法,它将需要从枚举型变量中选择一个,而不是一个简单的boolean。如果你想要禁止请求的磁盘缓存,使用枚举型变量DiskCacheStrategy.NONE作为参数。

Glide  .with(context)
    .load("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1504580292492&di=8864ca8d00cee6849b628404f89ae7e1&imgtype=0&src=http%3A%2F%2Fbbs.kaitao.cn%2Fshangchuan%2F2013-08-27%2F20130827101902.jpg")
    .diskCacheStrategy(DiskCacheStrategy.NONE)
    .into(imageViewInternet);

上面代码里的图片根本不会被保存在磁盘上,然而默认情况下它仍然使用内存缓存!为了同时禁止掉两个缓存,结合以下方法:

Glide  
    .with(context)
    .load("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1504580292492&di=8864ca8d00cee6849b628404f89ae7e1&imgtype=0&src=http%3A%2F%2Fbbs.kaitao.cn%2Fshangchuan%2F2013-08-27%2F20130827101902.jpg")
    .diskCacheStrategy(DiskCacheStrategy.NONE)
    .skipMemoryCache(true)
    .into(imageViewInternet);

3、自定义磁盘缓存行为

正如我们之前提到的,Glide有很多磁盘缓存的策略。在我们展示这些选项前,你可能意识到Glide的磁盘缓存是相当复杂的。Picasso只缓存全尺寸图片;Glide会缓存原始尺寸的图片和额外的小版本图片。例如,如果你请求一个1000x1000像素的图片,你的ImageView是500x500像素,Glide会保存两个版本的图片到缓存里。

现在,你应该明白.diskCacheStrategy()中枚举参数的意义了:
  • DiskCacheStrategy.NONE 啥也不缓存
  • DiskCacheStrategy.SOURCE 只缓存全尺寸图
  • DiskCacheStrategy.RESULT 只缓存最终降低分辨后用到的图片
  • DiskCacheStrategy.ALL 缓存所有类型的图片(默认行为)
更多的关于Glide和Picasso的对比,你可以参考这里:

和Picasso的对比

联系方式:

简书:WillFlow
CSDN:WillFlow
微信公众号:WillFlow

微信公众号:WillFlow

上一篇:Day6 快速学习OkHttp3的九大用法 下一篇:Android sdk的目录结构