谈到Glide,从英文字面意思有滑行、滑动的意思;而Android从开发的角度我们知道它是一款图片加载框架,这里引用官方文档的一句话“Glide是一个快速高效的Android图片加载库,注重于平滑的滚动”,从官方文档介绍我们了解到用Glide框架来加载图片是快速并且高效的,接下来就来通过简单使用Glide和源码理解两个方面看看Glide是否是快速和高效(文中代码基于Glide 4.8版本)。
Glide简单使用
-
1.使用前需要添加依赖
implementation 'com.github.bumptech.glide:glide:4.8.0' //使用Generated API需要引入 annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0'
-
2.简单加载网络图片到ImageView,可以看到简单一句代码就能将网络图片加载到ImageView,也可以使用Generated API方式
//直接使用 Glide.with(Context).load(IMAGE_URL).into(mImageView) //使用Generated API, 作用范围Application 模块内使用 //创建MyAppGlideModule类加上@GlideModule注解,make project 就能使用 GlideApp @GlideModule public final class MyAppGlideModule extends AppGlideModule {} //Generated API加载图片 GlideApp.with(Context).load(IMAGE_URL).into(mImageView);
-
3.当加载网络图片的时候,网络请求是耗时操作,所以图片不可能马上就加载出来,网络请求这段时间ImageView是空白的,所以我们可以使用一个占位符显示图片来优化用户体验,占位符有三种
- 加载占位符(placeholder)
- 错误占位符(error)
- 后备回调符(Fallback)
//添加占位图 RequestOptions requestOptions = new RequestOptions() .placeholder(R.drawable.ic_cloud_download_black_24dp) .error(R.drawable.ic_error_black_24dp) .diskCacheStrategy(DiskCacheStrategy.NONE);//不使用缓存 Glide.with(Context).load(IMAGE_URL).apply(requestOptions).into(mImageView); //Generated API 方式(和Glide3 一样) GlideApp.with(Context).load(IMAGE_URL) .placeholder(R.drawable.ic_cloud_download_black_24dp) .error(R.drawable.ic_error_black_24dp) .diskCacheStrategy(DiskCacheStrategy.NONE) .into(mImageView); // 后备回调符(Fallback) Generated API 方式才有,在应用设置用户头像场景中,如果用户不设置,也就是为null的情况,可以使用后备回调符显示默认头像 private static final String NULL_URL=null; GlideApp.with(Context).load(NULL_URL) .fallback(R.drawable.ic_account_circle_black_24dp) .into(mImageView);
-
4.指定加载图片的大小(override)
RequestOptions requestOptions = new RequestOptions().override(200,100); Glide.with(Context).load(IMAGE_URL).apply(requestOptions).into(mImageView); //Generated API 方式 GlideApp.with(Context).load(IMAGE_URL) .override(200,100) .into(mImageView);
-
5.缩略图 (Thumbnail)
- 这个其实和占位符(placeholder)有些相似,但是占位符只能加载本地资源,而缩略图可以加载网络资源,thumbnail方法与我们的主动加载并行运行,如果主动加载已经完成,则缩略图不会显示
//缩略图Options RequestOptions requestOptions = new RequestOptions() .override(200,100) .diskCacheStrategy(DiskCacheStrategy.NONE); Glide.with(Context) .load(IMAGE_URL) .thumbnail( Glide.with(this) .load(IMAGE_URL) .apply(requestOptions)) .into(mImageView); //Generated API 方式 GlideApp.with(Context). load(IMAGE_URL). thumbnail( GlideApp.with(this) .load(IMAGE_URL).override(200,100) .diskCacheStrategy(DiskCacheStrategy.NONE)).into(mImageView);
-
6.图像变化
-
Glide中内置了三种图片的变化操作,分别是CenterCrop(图片原图的中心区域进行裁剪显示),FitCenter(图片原始长宽铺满)和CircleCrop(圆形裁剪)
//显示圆形裁剪到ImageView RequestOptions requestOptions = new RequestOptions() .circleCrop() .diskCacheStrategy(DiskCacheStrategy.NONE); Glide.with(Context) .load(IMAGE_URL) .apply(requestOptions) .into(mImageView); //RequestOptions都内置了使用者三种变化的静态方法 Glide.with(Context) .load(IMAGE_URL) .apply(RequestOptions.circleCropTransform()) .into(mImageView); //Generated API 方式 GlideApp.with(Context).load(IMAGE_URL) .circleCrop() .diskCacheStrategy(DiskCacheStrategy.NONE) .into(mImageView);
-
如果想要更酷炫的变化,可以使用第三方框架glide-transformations来帮助我们实现,并且变化是可以组合的
//第三方框架glide-transformations引入 implementation 'jp.wasabeef:glide-transformations:4.0.0' //使用glide-transformations框架 变换图片颜色和加入模糊效果 RequestOptions requestOptions=new RequestOptions() .placeholder(R.drawable.ic_cloud_download_black_24dp) .transforms(new ColorFilterTransformation(Color.argb(80, 255, 0, 0)),new BlurTransformation(30)) .diskCacheStrategy(DiskCacheStrategy.NONE); Glide.with(Context).load(IMAGE_URL). apply(requestOptions). into(mImageView); //Generated API 方式 GlideApp.with(Context).load(IMAGE_URL) .transforms(new ColorFilterTransformation(Color.argb(80, 255, 0, 0)),new BlurTransformation(30)) .placeholder(R.drawable.ic_cloud_download_black_24dp) .diskCacheStrategy(DiskCacheStrategy.NONE) .into(mImageView);
-
更多效果可以查看官方例子
-
7.加载目标(Target)
-
Target是介于请求和请求者之间的中介者的角色,into方法的返回值就是target对象,之前我们一直使用的 into(ImageView) ,它其实是一个辅助方法,它接受一个 ImageView 参数并为其请求的资源类型包装了一个合适的 ImageViewTarget
//加载 Target
target = Glide.with(Context) .load(url) .into(new Target () { ... }); //清除加载 Glide.with(Context).clear(target); 当我们使用Notification显示应用通知,如果想要自定义通知的界面,我们需要用到RemoteView,如果要给RemoteView设置ImageView,根据提供的setImageViewBitmap方法,如果通知界面需要加载网络图片,则需要将网络图片转换成bitmap,一般我们可以根据获取图片链接的流来转换成bitmap,或者使用本文的主题使用Glide框架,这些都是耗时操作,感觉操作起来很麻烦,而Glide框架很贴心的给我提供了NotificationTarget(继承SimpleTarget),相对于我们加载目标变成Notification
/** * 新建 NotificationTarget 对象参数说明,与Glide3不同,Glide4的asBitmap()方法必须在load方法前面 * @param context 上下文对象 * @param viewId 需要加载ImageView的view的 id * @param remoteViews RemoteView对象 * @param notification Notification对象 * @param notificationId Notification Id */ String iamgeUrl = "http://p1.music.126.net/fX0HfPMAHJ2L_UeJWsL7ig==/18853325881511874.jpg?param=130y130"; NotificationTarget notificationTarget = new NotificationTarget(mContext,R.id.notification_Image_play,mRemoteViews,mNotification,notifyId); Glide.with(mContext.getApplicationContext()) .asBitmap() .load(iamgeUrl) .into( notificationTarget ); //Generated API 方式 GlideApp.with(mContext.getApplicationContext()) .asBitmap() .load(iamgeUrl) .into( notificationTarget );
-
-
8.回调监听
- 使用Glide加载图片,虽然在加载中或者加失败都有占位符方法处理,但是我们还是希望可以知道图片到底是加载成功还是失败,Glide也给我们提供了监听方法来知道图片到底是加载成功还是失败,结合listener和into方法来使用回调
Glide.with(this).load(IMAGE_URL). listener(new RequestListener
() { @Override public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { Toast.makeText(getApplicationContext(),"图片加载失败",Toast.LENGTH_SHORT).show(); return false; } @Override public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { Toast.makeText(getApplicationContext(),"图片加载成功",Toast.LENGTH_SHORT).show(); return false; } }).into(mImageView);*/ //Generated API 方式 GlideApp.with(this).load(IMAGE_URL) .listener(new RequestListener () { @Override public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { Toast.makeText(getApplicationContext(),"图片加载失败",Toast.LENGTH_SHORT).show(); return false; } @Override public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { Toast.makeText(getApplicationContext(),"图片加载成功",Toast.LENGTH_SHORT).show(); return false; } }).into(mImageView); - 可以看到监听实现的方法都有布尔类型的返回值,返回true,则代表处理了该回调事件,false则不进行处理,如果onResourceReady方法返回true,则into方法就不会执行,也就是图片不会加载到ImageView,同理onLoadFailed方法返回true,则error方法不会执行。
Glide还有其他的一些使用方法,这里就不继续展开了,有兴趣的可以自行继续研究。
Glide源码解析
Glide加载图片到ImageView基本流程图
Glide加载图片到ImageView源码分析
- 在上一节简单的列出了一些Glide的使用方法,能用不代表你已经懂了,接下来就通过理解源码的方式来对Glide是如何工作的做深一层次理解,首先从最简单使用开始
Glide.with(Context).load(IMAGE_URL).into(mImageView);
with方法
- 来吧,开始是Glide的with()方法,直接上源码
/** Glide类的with()方法*/
@NonNull
public static RequestManager with(@NonNull Context context) {
return getRetriever(context).get(context);
}
@NonNull
public static RequestManager with(@NonNull Activity activity) {
return getRetriever(activity).get(activity);
}
@NonNull
public static RequestManager with(@NonNull FragmentActivity activity) {
return getRetriever(activity).get(activity);
}
@NonNull
public static RequestManager with(@NonNull Fragment fragment) {
return getRetriever(fragment.getActivity()).get(fragment);
}
@SuppressWarnings("deprecation")
@Deprecated
@NonNull
public static RequestManager with(@NonNull android.app.Fragment fragment) {
return getRetriever(fragment.getActivity()).get(fragment);
}
@NonNull
public static RequestManager with(@NonNull View view) {
return getRetriever(view.getContext()).get(view);
}
- 通过源码,可以看到with有不同参数类型的重载方法,每个方法首先都是调用 getRetriever()方法
/** Glide类的getRetriever()方法*/
private static RequestManagerRetriever getRetriever(@Nullable Context context) {
// Context could be null for other reasons (ie the user passes in null), but in practice it will
// only occur due to errors with the Fragment lifecycle.
Preconditions.checkNotNull(
context,
"You cannot start a load on a not yet attached View or a Fragment where getActivity() "
+ "returns null (which usually occurs when getActivity() is called before the Fragment "
+ "is attached or after the Fragment is destroyed).");
return Glide.get(context).getRequestManagerRetriever();
}
- Glide的get方法中通过new GlideBuilder()获取了Glide对象,并通过Glide的getRequestManagerRetriever()的方法最终得到RequestManagerRetriever对象,接下来我们看看RequestManagerRetriever对象的get方法
/** RequestManagerRetriever类的get()方法*/
@NonNull
public RequestManager get(@NonNull Context context) {
if (context == null) {
throw new IllegalArgumentException("You cannot start a load on a null Context");
} else if (Util.isOnMainThread() && !(context instanceof Application)) {
if (context instanceof FragmentActivity) {
return get((FragmentActivity) context);
} else if (context instanceof Activity) {
return get((Activity) context);
} else if (context instanceof ContextWrapper) {
return get(((ContextWrapper) context).getBaseContext());
}
}
return getApplicationManager(context);
}
@NonNull
public RequestManager get(@NonNull FragmentActivity activity) {
if (Util.isOnBackgroundThread()) {
return get(activity.getApplicationContext());
} else {
assertNotDestroyed(activity);
FragmentManager fm = activity.getSupportFragmentManager();
return supportFragmentGet(
activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
}
}
@NonNull
public RequestManager get(@NonNull Fragment fragment) {
Preconditions.checkNotNull(fragment.getActivity(),
"You cannot start a load on a fragment before it is attached or after it is destroyed");
if (Util.isOnBackgroundThread()) {
return get(fragment.getActivity().getApplicationContext());
} else {
FragmentManager fm = fragment.getChildFragmentManager();
return supportFragmentGet(fragment.getActivity(), fm, fragment, fragment.isVisible());
}
}
@SuppressWarnings("deprecation")
@NonNull
public RequestManager get(@NonNull Activity activity) {
if (Util.isOnBackgroundThread()) {
return get(activity.getApplicationContext());
} else {
assertNotDestroyed(activity);
android.app.FragmentManager fm = activity.getFragmentManager();
return fragmentGet(
activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
}
}
@SuppressWarnings("deprecation")
@NonNull
public RequestManager get(@NonNull View view) {
if (Util.isOnBackgroundThread()) {
return get(view.getContext().getApplicationContext());
}
Preconditions.checkNotNull(view);
Preconditions.checkNotNull(view.getContext(),
"Unable to obtain a request manager for a view without a Context");
Activity activity = findActivity(view.getContext());
// The view might be somewhere else, like a service.
if (activity == null) {
return get(view.getContext().getApplicationContext());
}
// Support Fragments.
// Although the user might have non-support Fragments attached to FragmentActivity, searching
// for non-support Fragments is so expensive pre O and that should be rare enough that we
// prefer to just fall back to the Activity directly.
if (activity instanceof FragmentActivity) {
Fragment fragment = findSupportFragment(view, (FragmentActivity) activity);
return fragment != null ? get(fragment) : get(activity);
}
// Standard Fragments.
android.app.Fragment fragment = findFragment(view, activity);
if (fragment == null) {
return get(activity);
}
return get(fragment);
}
- 同样,RequestManagerRetriever对象的get方法也有不同类型参数的重载,分别针对Application、Activity、Fragmenet、view做了不同的处理,先看Context参数的get方法,在该方法中它把Context的参数分成了两个类型,一个Application类型的Context,另一个是非Application类型的Context。如果是Application类型的Context,则创建的Glide的生命周期则跟随ApplicationContext的生命周期,也就是下面的getApplicationManager所做的事情。
/** RequestManagerRetriever类的getApplicationManager()方法*/
@NonNull
private RequestManager getApplicationManager(@NonNull Context context) {
// Either an application context or we're on a background thread.
if (applicationManager == null) {
synchronized (this) {
if (applicationManager == null) {
// Normally pause/resume is taken care of by the fragment we add to the fragment or
// activity. However, in this case since the manager attached to the application will not
// receive lifecycle events, we must force the manager to start resumed using
// ApplicationLifecycle.
// TODO(b/27524013): Factor out this Glide.get() call.
Glide glide = Glide.get(context.getApplicationContext());
applicationManager =
factory.build(
glide,
new ApplicationLifecycle(),
new EmptyRequestManagerTreeNode(),
context.getApplicationContext());
}
}
}
return applicationManager;
}
- 接着,如果是非Application类型的,Activity、Fragmenet属于非Application;如果是Activity类型的Context,当前不再主线程,则继续跟随Application生命周期,否则给当前Activity添加一个隐藏的Fragment,然后Glide生命周期跟随这个隐藏的Fragment,分析到这里,我们再看Fragmenet类型的Context,或者是View类型,也是添加了一个隐藏的Fragment。这是为什么呢?首先Fragment的生命周期是和Activity同步的,Activity销毁Fragment也会销毁,其次,这也方便Glide知道自己什么时候需要停止加载,如果我们打开一个Activity并关闭它,如果Glide生命周期跟随Application,则Activity虽然已经销毁,但是应用还没退出,则Glide还在继续加载图片,这显然是不合理的,而Glide很巧妙的用一个隐藏Fragment来解决生命周期的监听。
/** RequestManagerRetriever类的fragmentGet()方法*/
@SuppressWarnings({"deprecation", "DeprecatedIsStillUsed"})
@Deprecated
@NonNull
private RequestManager fragmentGet(@NonNull Context context,
@NonNull android.app.FragmentManager fm,
@Nullable android.app.Fragment parentHint,
boolean isParentVisible) {
RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
// TODO(b/27524013): Factor out this Glide.get() call.
Glide glide = Glide.get(context);
requestManager =
factory.build(
glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
current.setRequestManager(requestManager);
}
return requestManager;
}
/** RequestManagerRetriever类的getRequestManagerFragment()方法*/
@SuppressWarnings("deprecation")
@NonNull
private RequestManagerFragment getRequestManagerFragment(
@NonNull final android.app.FragmentManager fm,
@Nullable android.app.Fragment parentHint,
boolean isParentVisible) {
RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
if (current == null) {
current = pendingRequestManagerFragments.get(fm);
if (current == null) {
current = new RequestManagerFragment();
current.setParentFragmentHint(parentHint);
if (isParentVisible) {
current.getGlideLifecycle().onStart();
}
pendingRequestManagerFragments.put(fm, current);
fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();
}
}
return current;
}
- 经过对into方法的分析,最终获取的是跟随对应Context对象生命周期的RequestManager对象。
load方法
- 经过上一小节的分析,Glide.with方法最终获取的是RequestManager对象,所以继续看RequestManager对象里面load方法,
/** RequestManager 类的as()方法*/
@NonNull
@CheckResult
public RequestBuilder as(
@NonNull Class resourceClass) {
return new RequestBuilder<>(glide, this, resourceClass, context);
}
/** RequestManager 类的as()方法*/
@NonNull
@CheckResult
public RequestBuilder asDrawable() {
return as(Drawable.class);
}
/** RequestManager 类的部分load()方法*/
@NonNull
@CheckResult
@Override
public RequestBuilder load(@Nullable Bitmap bitmap) {
return asDrawable().load(bitmap);
}
@NonNull
@CheckResult
@Override
public RequestBuilder load(@Nullable Drawable drawable) {
return asDrawable().load(drawable);
}
@NonNull
@CheckResult
@Override
public RequestBuilder load(@Nullable String string) {
return asDrawable().load(string);
}
//省略其他参数类型 load() 方法
.......
- 通过以上load方法,可以发现虽然RequestManager对象的load方法有多个类型参数的重载,但是不管load方法传递什么类型参数,该方法都是调用RequestBuilder对象的load方法
/** RequestBuilder 类的load()方法*/
@NonNull
@CheckResult
@SuppressWarnings("unchecked")
@Override
public RequestBuilder load(@Nullable Object model) {
return loadGeneric(model);
}
/** RequestBuilder对象 类的loadGeneric()方法*/
@NonNull
private RequestBuilder loadGeneric(@Nullable Object model) {
this.model = model;
isModelSet = true;
return this;
}
- 通过以上RequestBuilder对象的load()方法,我们可以明白不管RequestManager对象的load方法方法传递什么类型的加载资源参数,RequestBuilder对象都把它看成时Object对象,并在loadGeneric方法中赋值给RequestBuilder对象的model对象。
- 通过查看RequestBuilder对象,我们还注意到apply(RequestOptions)这个方法,前面我们的例子中使用缓存,加载图像大小,设置加载占位符和错误占位符都需要新建RequestOptions对象,并设置我们的配置,现在我们分析的加载并没有apply一个RequestOptions对象,则Glide会使用requestOptions.clone()去加载默认配置,这里就先不进行展开了,先继续关注接下来的into方法。
/** RequestBuilder 类的apply方法*/
@NonNull
@CheckResult
public RequestBuilder apply(@NonNull RequestOptions requestOptions) {
Preconditions.checkNotNull(requestOptions);
this.requestOptions = getMutableOptions().apply(requestOptions);
return this;
}
@SuppressWarnings("ReferenceEquality")
@NonNull
protected RequestOptions getMutableOptions() {
return defaultRequestOptions == this.requestOptions
? this.requestOptions.clone() : this.requestOptions;
}
- 经过以上对with()方法和load方法的分析,经过这两步之后得到了RequestBuilder对象,也就说明真正的图片加载操作是在into方法来完成,也就是RequestBuilder对象的into方法。
- 由于字数限制,接下来的into方法分析留到下一篇文章,对本文感兴趣的朋友请继续阅读从源码角度深入理解Glide(中)
- 参考链接
- Gldie文档