Glide使用详解

英文原文: https://futurestud.io/tutorials/glide-getting-started
中文译文:https://mrfu.me/2016/02/27/Glide_Getting_Started/
郭霖原创: http://mp.weixin.qq.com/s/ccr1wqRYvZkVpeUT_V6fXQ

概述

Glide是一款由 Bump Technologies 开发的图片加载框架,使得我们可以在Android平台上以极度简单的方式加载和展示图片。

要想使用Glide,首先需要将这个库引入到我们的项目当中。在你的 build.gradle 中添加下面这行代码:

compile 'com.github.bumptech.glide:glide:3.7.0'

我当前用的用3.7.0这个版本,这个版本的Glide相当成熟和稳定。

注意:Glide中需要用到网络功能,因此你还得在AndroidManifest.xml中声明一下网络权限才行:

<uses-permission android:name="android.permission.INTERNET"/>

如果权限没加,虽然不会报错,但是图片加载没有反应。

加载图片

如果你想从网络上加载一张图片到 ImageView 上,只需要这样写:

String url = "http://www.jianbihua.cc/uploads/allimg/140215/2-140215123319130.jpg";

Glide.with(this).load(url).into(iv_image);

运行效果如下:

一个完整的功能请求,要求至少有三个参数,先 with() ,再 load() ,最后 into()

  • with() ,用于创建一个加载图片的实例。 with() 方法可以接收 Context、Activity或者 Fragment 类型的参数。不管是在 Activity 还是 Fragment中 调用with()方法,都可以直接传 this 。那如果调用的地方既不在 Activity 中也不在 Fragment 中呢?也没关系,我们可以获取当前应用程序的ApplicationContext,传入到with()方法当中。注意 with() 方法中传入的实例会决定 Glide 加载图片的生命周期,如果传入的是Activity或者 Fragment 的实例,那么当这个 ActivityFragment 被销毁的时候,图片加载也会停止。如果传入的是 ApplicationContext ,那么只有当应用程序被杀掉的时候,图片加载才会停止。

  • load() ,这个方法用于指定待加载的图片资源,Glide支持加载各种各样的图片资源,包括网络图片、本地图片、应用资源、二进制流、Uri对象等等。

  • into() ,需要加载显示图片的 ImageView ,也有一些重载的方法,属于高级用法,后面再讲。

加载进阶

三步走之中,估计大家最关心的就是 load() 方法,那么我们再进阶一下,看看有哪些重载方法可以用。

从应用资源中加载

int resourceId = R.mipmap.ic_launcher;

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

从文件中加载

//这个文件可能不存在于你的设备中。然而你可以指定一个图片路径。
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "Running.jpg");

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

从 Uri 中加载

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

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

占位图

由于我们大多数是去网络请求图片,请求时间视网络而定,在这过程中我们可以先显示请求过程的占位图,也可以在请求失败后显示错误的占位图。如果只设置请求过程的占位图,则错误后也显示该图片。

String url = "http://www.jianbihua.cc/uploads/allimg/140215/2-140215123319130";
Glide.with(this)
.load(url)
.placeholder(R.mipmap.icon_home_placeholder)
.error(R.mipmap.icon_loading_error)
.into(iv_image);

上述代码中, placeholder() 是设置请求过程显示的图片资源, error() 则是设置请求错误的图片资源。 如果你测试的时候感觉占位图还没来得及显示,图片已经加载出来了。那是因为Glide内部做了缓存,你可以清除缓存或删除程序重新加载。

加载动画

我们在加载图片的时候,肯定不希望图片突然显示出来,平滑地显示是非常有必要的,Glide 默认开启淡入淡出动画,我们在代码中不设置,默认开启,设置了也可以,代码如下:

String url = "http://www.jianbihua.cc/uploads/allimg/140215/2-140215123319130";
Glide.with(this)
.load(url)
.placeholder(R.mipmap.icon_home_placeholder)
.error(R.mipmap.icon_loading_error)
.crossFade()
.into(iv_image);

crossFade() 方法还有一个重载方法 crossFade(int duration) ,duration 用来设置动画持续时间,默认时间是300毫秒。具体的效果,大家可以自行测试观察。

可能这时候就有小伙伴要搞事情了,我就不想要加载动画,直接显示图片,那也是可以的。 .dontAnimate() 方法即可禁掉加载动画,代码如下:

String url = "http://www.jianbihua.cc/uploads/allimg/140215/2-140215123319130.jpg";
Glide.with(this).load(url).dontAnimate().into(iv_image);

可能这时候又有小伙伴要搞事情了,我要动画,但是我想自定义加载动画,不要默认的,那也是可以的。在讲自定义动画前,我要说明一下,动画仅仅用于不从缓存中加载的情况。如果图片被缓存过了,它的显示是非常快的,因此动画是没有必要的,并且不显示的。

动画设置通过方法 animate(),该方法有两个重载方法,也就代码着两种不同的加载动画方式。

从资源中加载动画

我们可以传入一个动画id来加载图片动画,比如我们设置从左滑入的动画, android.R.anim.slide_in_left 。下面这段代码是这个动画的XML描述:

<?xml version="1.0" encoding="utf-8"?>  
<set xmlns:android="http://schemas.android.com/apk/res/android">  
    <translate android:fromXDelta="-50%p" android:toXDelta="0"
        android:duration="@android:integer/config_mediumAnimTime"/>
    <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
        android:duration="@android:integer/config_mediumAnimTime" />
</set> 

从左滑入的代码设置如下:

String url = "http://www.jianbihua.cc/uploads/allimg/140215/2-140215123319130.jpg";

Glide.with(this).load(url).animat(android.R.anim.slide_in_left).into(iv_image);

上述的代码是Android自带的动画,我们可以创建自己的XML动画,比如一个先放大后缩小到原来大小的动画。

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" android:fillAfter="true" android:duration="1000">

<scale
    android:fromXScale="1.2"
    android:fromYScale="1.2"
    android:toXScale="1.0"
    android:toYScale="1.0"
    android:pivotX="50%"
    android:pivotY="50%" />

</set>

缩放动画代码设置如下:

Glide.with(this).load(url).animat(R.anim.zoom_in).into(iv_image);

通过自定义类实现动画

我们通过这种方式来实现一下上述的缩放动画:

ViewPropertyAnimation.Animator animatorObject = new ViewPropertyAnimation.Animator() {
        @Override
        public void animate(View view) {

            //  如果 view 是一个自定义的View ,可以在这先找到要实现动画的子View,再做动画处理。

            ObjectAnimator animatorX = ObjectAnimator.ofFloat(view, "scaleX", 1.2f, 1.0f);
            ObjectAnimator animatorY = ObjectAnimator.ofFloat(view, "scaleY", 1.2f, 1.0f);
            AnimatorSet animatorSet = new AnimatorSet();
            animatorSet.play(animatorX).with(animatorY);
            animatorSet.setDuration(2000);
            animatorSet.start();
        }
    };

Glide.with(this).load(url).animat(animatorObject).into(iv_image);

最后实现的效果和上述的一样。这种方式可以实现一些比较复杂的动画,也可以解决 into() 方法中没有加载常规ImageView,而是其他对象时的动画处理。我们在后面再讲 into() 加载的其他对象。

指定图片大小

实际上,使用Glide在绝大多数情况下我们都是不需要指定图片大小的。

在学习本节内容之前,你可能还需要先了解一个概念,就是我们平时在加载图片的时候很容易会造成内存浪费。什么叫内存浪费呢?比如说一张图片的尺寸是10001000像素,但是我们界面上的ImageView可能只有200200像素,这个时候如果你不对图片进行任何压缩就直接读取到内存中,这就属于内存浪费了,因为程序中根本就用不到这么高像素的图片。

而使用Glide,我们就完全不用担心图片内存浪费,甚至是内存溢出的问题。因为Glide从来都不会直接将图片的完整尺寸全部加载到内存中,而是用多少加载多少。Glide会自动判断ImageView的大小,然后只将这么大的图片像素加载到内存当中,帮助我们节省内存开支。

也正是因为Glide是如此的智能,所以刚才在开始的时候我就说了,在绝大多数情况下我们都是不需要指定图片大小的,因为Glide会自动根据ImageView的大小来决定图片的大小。

当你还没有目标 View 去知道尺寸的时候,比如,如果 APP 想要在闪屏界面预热缓存,它还不能测量 ImageView 的尺寸。然而,你如果知道这个图片要多小,用 override(width,height) 提供明确的尺寸。

Glide.with(this).load(url).override(200,200).into(iv_image);

现在 Glide只会将图片加载成200*200像素的尺寸,而不会管你的 ImageView的大小是多少了。

缩放图像

缩放图像是为了避免长宽比失真并且无以伦比图片显示。 Glide 提供了两个标准选项: centerCrop()fitCenter() 。其实效果跟设置 ImageView 的 ScaleType 是一样的。

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

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

代码和效果如下:

Glide.with(this).load(url).centerCrop().into(iv_image);

指定图片格式

Glide是支持加载 GIF 图片的,而使用Glide加载GIF图并不需要编写什么额外的代码,Glide内部会自动判断图片格式。比如这是一张GIF图片的URL地址:

http://img.zcool.cn/community/01822155c1a44f6ac7253f360673f6.gif

代码如下:

Glide.with(this).load(gifUrl)
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
.into(iv_image);

我们只需要像之前那样把地址放在 load() 方法里面即可,如果是 gif ,就会播放。如果加载速度过慢或加载不出来,可以加上 .diskCacheStrategy(DiskCacheStrategy.SOURCE) ,这是 Glide 的缓存策略,后面再讲。

效果如下:

如果你只想显示 GIF 图片的第一帧,可以调用 asBitmap()方法。

Glide.with(this).load(gifUrl)
.asBitmap()
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
.into(iv_image);

效果如下:

如果你期望这个 URL 是一个 Gif,Glide 不会自动检查是否是 Gif。但是我们可以设置只显示 GIF,如果不是则加载错误,方法为 asGif()

代码如下:

Glide.with(this).load(jpgUrl)
.asGif()
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
.into(iv_image);

效果如下:

没错,跟之前的占位图效果一样,即使是正常的图片链接,但是我们显式设置了要显示 GIF ,那只能是加载失败了。

缓存基础

Glide 通过使用默认的内存和硬盘缓存去避免不必要的网络请求。这两个缓存模块的作用各不相同,内存缓存的主要作用是防止应用重复将图片数据读取到内存当中,而硬盘缓存的主要作用是防止应用重复从网络或其他地方重复下载和读取数据。

你也可以改变 Glide 的默认缓存行为,我们可以调用 .skipMemoryCache(true) 跳过内存缓存。这样 Glide将不会把这张图片缓存到内存中去了。

注意:对于相同的URL,如果一开始没有跳过内存缓存,后面再调用的话,也还是会从内存中获取缓存的。

对于硬盘缓存,可以用 .diskCacheStrategy() 方法改变默认行为,该方法需要传入一个枚举而不是一个简单的布尔值 。如果你想禁用硬盘缓存,则使用 DiskCacheStrategy.NONE 作为参数。

Glide.with(this).load(url)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(iv_image);

在了解其他枚举值的时候,我们先了解一下 Glide 的缓存策略。 Glide 默认会缓存原始图片和小版本的图片。比如,你请求的图片是 10001000 像素的,但是你的 ImageView 是 500500 像素的,Glide将会把这两个尺寸都进行缓存。下面对枚举参数进行说明:

  • DiskCacheStrategy.NONE 什么都不缓存,就像刚讨论的那样
  • DiskCacheStrategy.SOURCE 仅仅只缓存原来的全分辨率的图像。在我们上面的例子中,将会只有一个 1000x1000 像素的图片
  • DiskCacheStrategy.RESULT 仅仅缓存最终的图像,即,降低分辨率后的(或者是转换后的)
  • DiskCacheStrategy.ALL 缓存所有版本的图像(默认行为)

请求优化级

假设一下有这样一个场景,你需要显示一组图片,中间有一张非常大的,底部有两张小的。怎样才体验友好呢?最好是优化加载中间大图,再显示底部小图了,这就涉及优先级的问题了。 Glide 可以用 priority 枚举来设置优先级,调用 .priority() 方法。

priority 枚举的值如下:

  • Priority.LOW
  • Priority.NORMAL
  • Priority.HIGH
  • Priority.IMMEDIATE

注意:虽然设置了优先级,但是不一定是按优先级的顺序显示图片,但是会尽可能地做优先级的处理显示。

Glide.with(this).load(url).priority(Priority.HIGH).into(iv_image);

缩略图

缩略图是动态占位符。它也可以从网络中加载。如果缩略图比全尺寸图先加载完,就显示缩略图,否则就不显示。方法为 .thumbnail(float sizeMultiplier) ,sizeMultiplier 范围从 0~1f ,如果你传了 0.1f 作为参数, Glide将会显示原始图像的10%的大小。

Glide.with(this)
      .load(url2)
      .thumbnail(0.1f).skipMemoryCache(true)
      .diskCacheStrategy(DiskCacheStrategy.NONE)
       into(iv_image);

如果你想加载网络图片来做缩略图,可以使用令一个重载方法,代码如下:

DrawableRequestBuilder<String> thumbnailRequest = Glide.with(this).load;

Glide.with(this)
       .load(url2)
       .thumbnail(thumbnailRequest).skipMemoryCache(true)
       .diskCacheStrategy(DiskCacheStrategy.NONE)
        into(iv_image);

Glide 中的回调:Targets

有时候我们加载图片,不一定要显示到 ImageView 中的,我们只想获取 Bitmap 图片。 Targets 是在做完所有加载和处理之后返回的结果。Targets 有很多种类,下面我们将一一介绍一下:

SimpleTarget

private SimpleTarget drawableTarget = new SimpleTarget<GlideBitmapDrawable>() {
    @Override
    public void onResourceReady(GlideBitmapDrawable resource, GlideAnimation<? super GlideBitmapDrawable> glideAnimation) {
        iv_image.setImageDrawable(resource);
    }
};

private SimpleTarget bitmapTarget = new SimpleTarget<Bitmap>() {
    @Override
    public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
        iv_image.setImageBitmap(resource);
    }
};

Glide.with(this)
     .load(url2)
     .asBitmap()
     .into(bitmapTarget);

其实我们拿到了 Drawable 或 Bitmap 后可以做很多处理,这里我直接加载到 ImageView ,效果跟之前一样。

注意:

  1. 如果我们想要 Bitmap 类型,必须加上 .asBitmap() 方法 ,明确告诉 Glide 我们需要 Bitmap 类型,不然会报错。

  2. 确保声明的回调对象是作为一个字段对象的,这样可以避免当图像加载完成后,回调不被调用的问题。如果作为匿名内部类对象,因为 Android 回收机制,可能会因为图片加载完成之前被移除掉。

之前我们说过,Glide 将自动和我们加载 ImageView宽高尺寸的图片,这样更加高效。但是当我们传入 Target 的时候, Glide将无法知道宽高是多少了,只能加载源图,其实我们是可以自动 Target的大小的,下面请看:

private SimpleTarget bitmapTarget = new SimpleTarget<Bitmap>(250,250) {
    @Override
    public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
        iv_image.setImageBitmap(resource);
    }
};

在之前代码的基础上,我们指定了宽高为 250 个像素,这样我们的加载效率会大大提高,如果源图尺寸很大的时候。

ViewTarget

如果你自定义了一个 View ,我们能不能直接通过 into()来展示呢? 答案是肯定的,我们可以通过 ViewTarget 来实现。让我们看一个简单的自定义 View,它继承自 FrameLayout 并内部使用了一个 ImageView 以及覆盖了一个 TextView 。

public class FutureStudioView extends FrameLayout {  
    ImageView iv;
    TextView tv;

    public void initialize(Context context) {
        inflate( context, R.layout.custom_view_futurestudio, this );

        iv = (ImageView) findViewById( R.id.custom_view_image );
        tv = (TextView) findViewById( R.id.custom_view_text );
    }

    public FutureStudioView(Context context, AttributeSet attrs) {
        super( context, attrs );
        initialize( context );
    }

    public FutureStudioView(Context context, AttributeSet attrs, int defStyleAttr) {
        super( context, attrs, defStyleAttr );
        initialize( context );
    }

    public void setImage(Drawable drawable) {
        iv = (ImageView) findViewById( R.id.custom_view_image );

        iv.setImageDrawable( drawable );
    }
}

你不能使用常规的 Glide 的方法 .into(),因为我们的自定义 view 并不继承自 ImageView。因此,我们必须创建一个 ViewTarget,并用 .into() 方法:

private void loadImageViewTarget() {  
    FutureStudioView customView = (FutureStudioView) findViewById( R.id.custom_view );

    viewTarget = new ViewTarget<FutureStudioView, GlideDrawable>( customView ) {
        @Override
        public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) {
            this.view.setImage( resource.getCurrent() );
        }
    };

    Glide
        .with( context.getApplicationContext() ) // safer!
        .load( eatFoodyImages[2] )
        .into( viewTarget );
}

在 target 回调方法中,我们使用我们创建的方法 setImage(Drawable drawable) 在自定义 view 类中去设置图片。另外确保你注意到我们必须在 ViewTarget 的构造函数中传递我们自定义 view 作为参数: new ViewTarget<FutureStudioView, GlideDrawable>(customView)

NotificationTarget

下面我们用 RemoteViews 创建自定义界面的通知栏,布局和代码如下:

<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout  
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@android:color/white"
    android:orientation="vertical">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="2dp">
        <ImageView
            android:id="@+id/remoteview_notification_icon"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_marginRight="2dp"
            android:layout_weight="0"
            android:scaleType="centerCrop"/>
        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:orientation="vertical">
            <TextView
                android:id="@+id/remoteview_notification_headline"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:ellipsize="end"
                android:singleLine="true"
                android:textSize="12sp"/>
            <TextView
                android:id="@+id/remoteview_notification_short_message"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:ellipsize="end"
                android:paddingBottom="2dp"
                android:singleLine="true"
                android:textSize="14sp"
                android:textStyle="bold"/>
        </LinearLayout>
    </LinearLayout>
</LinearLayout>  

final RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.remoteview_notification);

rv.setImageViewResource(R.id.remoteview_notification_icon, R.mipmap.future_studio_launcher);

rv.setTextViewText(R.id.remoteview_notification_headline, "Headline");  
rv.setTextViewText(R.id.remoteview_notification_short_message, "Short Message");

// build notification
NotificationCompat.Builder mBuilder =  
new NotificationCompat.Builder(context)
    .setSmallIcon(R.mipmap.future_studio_launcher)
    .setContentTitle("Content Title")
    .setContentText("Content Text")
    .setContent(rv)
    .setPriority( NotificationCompat.PRIORITY_MIN);

final Notification notification = mBuilder.build();

// set big content view for newer androids
if (android.os.Build.VERSION.SDK_INT >= 16) {  
    notification.bigContentView = rv;
}

NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);  
mNotificationManager.notify(NOTIFICATION_ID, notification); 

这个代码片段为我们创建了三个重要的对象, notification 和 RemoteViews 以及常量 NOTIFICATION_ID。我们会需要这些去创建一个 NotificationTarget。

private NotificationTarget notificationTarget;

...

notificationTarget = new NotificationTarget(  
    context,
    rv,
    R.id.remoteview_notification_icon,
    notification,
    NOTIFICATION_ID); 

最后,我们像之前那样加载图片即可,只不过 into 传入的是 NotificationTarget 。

App Widgets

应用小部件加载网络图片,可以使用 Glide 提供的 AppWidgetTarget 来实现。

public class FSAppWidgetProvider extends AppWidgetProvider {

private AppWidgetTarget appWidgetTarget;

@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
                     int[] appWidgetIds) {

    RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.custom_view_futurestudio);

    appWidgetTarget = new AppWidgetTarget( context, rv, R.id.custom_view_image, appWidgetIds );

    Glide
            .with( context.getApplicationContext() ) // safer!
            .load( GlideExampleActivity.eatFoodyImages[3] )
            .asBitmap()
            .into( appWidgetTarget );

    pushWidgetUpdate(context, rv);
}

public static void pushWidgetUpdate(Context context, RemoteViews rv) {
    ComponentName myWidget = new ComponentName(context, FSAppWidgetProvider.class);
    AppWidgetManager manager = AppWidgetManager.getInstance(context);
    manager.updateAppWidget(myWidget, rv);
}
}

只需要把对应要传的参数都传入 Targets 即可,所有的操作都由 Glide 来完成 。

回调监听

Glide 的回调监听跟大多数的回调一样,创建一个监听并传入 .listener() 方法即可。监听器用字段声明的形式,跟之前一样。

RequestListener<String, Bitmap> requestListener = new RequestListener<String, Bitmap>() {
        @Override
        public boolean onException(Exception e, String model, Target<Bitmap> target, boolean isFirstResource) {
            ToastUtils.showShortToast(MainActivity.this,e.getMessage());
            return false;
        }

        @Override
        public boolean onResourceReady(Bitmap resource, String model, Target<Bitmap> target, boolean isFromMemoryCache, boolean isFirstResource) {
            return false;
        }
    };

    Glide.with(this)
            .load(url2)
            .asBitmap()
            .listener(requestListener)
            .into(drawableTarget);

上述回调中,onException 是加载错误时会回调,默认返回 false ,这样 Glide 会显示错误占位图等,返回 true 则没有后续处理。onResourceReady 是加载成功时会回调,我们可以在这里拿到 Bitmap 做相应处理。