美文网首页Android知识Android开发经验谈
从源码角度深入理解Glide(上)

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

作者: maoqitian | 来源:发表于2019-02-20 23:18 被阅读15次
    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
      @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);
      
    显示占位图.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)

      • 这个其实和占位符(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);
                  
        
    glide_transformation.gif
    • 更多效果可以查看官方例子

    • 7.加载目标(Target)

      • 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

      /**
       * 新建 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 );
      
    glide_notification.gif
    • 8.回调监听

      • 使用Glide加载图片,虽然在加载中或者加失败都有占位符方法处理,但是我们还是希望可以知道图片到底是加载成功还是失败,Glide也给我们提供了监听方法来知道图片到底是加载成功还是失败,结合listener和into方法来使用回调
      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源码解析

    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;
      }
    
    • 经过以上对with()方法和load方法的分析,经过这两步之后得到了RequestBuilder对象,也就说明真正的图片加载操作是在into方法来完成,也就是RequestBuilder对象的into方法。
    • 由于简书字数限制,接下来的into方法分析留到下一篇文章,对本文感兴趣的朋友请继续阅读从源码角度深入理解Glide(中)
    • 参考链接

    相关文章

      网友评论

        本文标题:从源码角度深入理解Glide(上)

        本文链接:https://www.haomeiwen.com/subject/gnfhsqtx.html