glide_logo.png
谈到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@GlideModulepublic final class MyAppGlideModule extends AppGlideModule {}//Generated API加载图片GlideApp.with(Context).load(IMAGE_URL).into(mImageView);
3.当加载网络图片的时候,网络请求是耗时操作,所以图片不可能马上就加载出来,网络请求这段时间ImageView是空白的,所以我们可以使用一个占位符显示图片来优化用户体验,占位符有三种
//添加占位图 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);
加载占位符(placeholder)
错误占位符(error)
后备回调符(Fallback)
显示占位图.gif
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)
//缩略图OptionsRequestOptions 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);
这个其实和占位符(placeholder)有些相似,但是占位符只能加载本地资源,而缩略图可以加载网络资源,thumbnail方法与我们的主动加载并行运行,如果主动加载已经完成,则缩略图不会显示
6.图像变化
Glide中内置了三种图片的变化操作,分别是CenterCrop(图片原图的中心区域进行裁剪显示),FitCenter(图片原始长宽铺满)和CircleCrop(圆形裁剪)
//显示圆形裁剪到ImageViewRequestOptions 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);
glide_transformation.gif
更多效果可以查看官方例子
7.加载目标(Target)
/** * 新建 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 );
Target是介于请求和请求者之间的中介者的角色,into方法的返回值就是target对象,之前我们一直使用的 into(ImageView) ,它其实是一个辅助方法,它接受一个 ImageView 参数并为其请求的资源类型包装了一个合适的 ImageViewTarget
//加载Target<Drawable> target = Glide.with(Context) .load(url) .into(new Target<Drawable>() { ... });//清除加载Glide.with(Context).clear(target);
当我们使用Notification显示应用通知,如果想要自定义通知的界面,我们需要用到RemoteView,如果要给RemoteView设置ImageView,根据提供的setImageViewBitmap方法,如果通知界面需要加载网络图片,则需要将网络图片转换成bitmap,一般我们可以根据获取图片链接的流来转换成bitmap,或者使用本文的主题使用Glide框架,这些都是耗时操作,感觉操作起来很麻烦,而Glide框架很贴心的给我提供了NotificationTarget(继承SimpleTarget),相对于我们加载目标变成Notification
glide_notification.gif
8.回调监听
Glide.with(this).load(IMAGE_URL). listener(new RequestListener<Drawable>() { @Override public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) { Toast.makeText(getApplicationContext(),"图片加载失败",Toast.LENGTH_SHORT).show(); return false; } @Override public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> 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<Drawable>() { @Override public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) { Toast.makeText(getApplicationContext(),"图片加载失败",Toast.LENGTH_SHORT).show(); return false; } @Override public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> 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也给我们提供了监听方法来知道图片到底是加载成功还是失败,结合listener和into方法来使用回调
Glide还有其他的一些使用方法,这里就不继续展开了,有兴趣的可以自行继续研究。
Glide源码解析
Glide加载图片到ImageView基本流程图
Glide基本请求流程图.jpg
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 <ResourceType> RequestBuilder<ResourceType> as( @NonNull Class<ResourceType> resourceClass) { return new RequestBuilder<>(glide, this, resourceClass, context); }/** RequestManager 类的as()方法*/ @NonNull @CheckResult public RequestBuilder<Drawable> asDrawable() { return as(Drawable.class); }/** RequestManager 类的部分load()方法*/ @NonNull @CheckResult @Override public RequestBuilder<Drawable> load(@Nullable Bitmap bitmap) { return asDrawable().load(bitmap); } @NonNull @CheckResult @Override public RequestBuilder<Drawable> load(@Nullable Drawable drawable) { return asDrawable().load(drawable); } @NonNull @CheckResult @Override public RequestBuilder<Drawable> load(@Nullable String string) { return asDrawable().load(string); } //省略其他参数类型 load() 方法 .......
通过以上load方法,可以发现虽然RequestManager对象的load方法有多个类型参数的重载,但是不管load方法传递什么类型参数,该方法都是调用RequestBuilder对象的load方法
/** RequestBuilder 类的load()方法*/ @NonNull @CheckResult @SuppressWarnings("unchecked") @Override public RequestBuilder<TranscodeType> load(@Nullable Object model) { return loadGeneric(model); }/** RequestBuilder对象 类的loadGeneric()方法*/ @NonNull private RequestBuilder<TranscodeType> 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<TranscodeType> 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; }
作者:maoqitian
链接:https://www.jianshu.com/p/ddb8e84b5238