为了账号安全,请及时绑定邮箱和手机立即绑定

从源码角度深入理解Glide(上)

标签:
Android

webp

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)

webp

显示占位图.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);

webp

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

webp

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基本流程图

webp

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


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消