【Glide】- 源码分析

简介

Glide由Google推出的图片加载框架,支持多种图片格式,同类的还有picassofresco。picasso功能没有Glide强大,也不支持gif加载,fresco是Facebook公司推出的框架。

添加glide

// glide
implementation 'com.github.bumptech.glide:glide:4.10.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.10.0'

实例

Glide.with(targetImageView.getContext())
      .load(headerUrl)
      .into(targetImageView);

Glide初始化

在调用with方法的时候,如果Glide没有初始化,那么会先进行初始化,创建Glide实例。

Glide编译

在编译过程中,Glide注解程序会生成GeneratedAppGlideModuleImpl和GeneratedRequestManagerFactory两个类来帮助Glide的初始化。

创建GeneratedAppGlideModuleImpl对象

通过创建GeneratedAppGlideModuleImpl对象来获取初始化和配置信息实例,并组装到glide实例中。

创建Glide初始化配置实例

自定义ConfigGlideModule,并继承于AppGlideModule抽象类,通过“GlideModule”注解或者在AndroidManifest配置来指定。

  1. 注解方式
    @com.bumptech.glide.annotation.GlideModule
     public class ConfigGlideModule extends AppGlideModule {
         @Override
         public boolean isManifestParsingEnabled() {return false;}
    
         @Override
         public void applyOptions(Context context, GlideBuilder builder) {super.applyOptions(context, builder);}
     }
    
  2. 清单文件配置
    <application>
        <meta-data
            android:name="xx.xx.xx.ConfigGlideModule"
            android:value="GlideModule" />
    </application>
    
with过程

创建初始化Glide实例,调用with方法对glide进行初始化,with()方法可以接收Context、Activity,Fragment类型参数。如果调用的地方不在Activity,Fragment中,可以获取当前应用程序的ApplicationContext,传入到with()方法中。如果传入的是Activity或者Fragment的实例,那么当这个Activity或Fragment被销毁的时候,图片加载也会停止。如果传入的是ApplicationContext,那么只有当应用程序被杀掉的时候,图片加载才会停止。

Glide类中存在下面两个变量。

  private static volatile Glide glide;
  private static volatile boolean isInitializing;

如果glide是null,才会去创建glide对象,如果isInitializing是true,代表glide正在初始化,这时候如果再次初始化,是直接会抛异常的,所以在多线程使用环境中一定要注意。

  • 全局初始化配置
    1. 调用自定义的初始化配置类的applyOptions和registerComponents方法
       // 通过manifest配置
      for (com.bumptech.glide.module.GlideModule module : manifestModules) {
         module.applyOptions(applicationContext, builder);
         module.registerComponents(applicationContext, glide, glide.registry);
       }
       // 通过“GlideModule”注解配置
       if (annotationGeneratedModule != null) {
         annotationGeneratedModule.applyOptions(applicationContext, builder);
         annotationGeneratedModule.registerComponents(applicationContext, glide, glide.registry);
       }
      
  • GlideBuilder(组装Glide实例)
    1. 调用GeneratedAppGlideModuleImpl的getRequestManagerFactory获取GeneratedRequestManagerFactory实例并设置到glide中。
     builder.setRequestManagerFactory(factory);
    
  • 注册ComponentCallbacks
    applicationContext.registerComponentCallbacks(glide);
    

load过程

  • 创建RequestBuilder
    public <ResourceType> RequestBuilder<ResourceType> as(
       @NonNull Class<ResourceType> resourceClass) {
     return new RequestBuilder<>(glide, this, resourceClass, context);
     }
    
    resourceClass默认是Drawable.class,可以通过调用 asGif,.asBitmap等方法来改变
    Glide.with(targetImageView.getContext())
           .asBitmap()
           .load(internetUrl)                
           .apply(getRoundedCornersOptions(dip2px(targetImageView.getContext(), cornerRadios)))
           .into(targetImageView);
    
    保存load传入的参数
    model的值就是load传入的参数
    private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
      this.model = model;
      isModelSet = true;
      return this;
    }
    

into过程

glide需要工作在主线程,否则直接抛异常。

  • 请求配置(BaseRequestOptions)

        if (!requestOptions.isTransformationSet()
         && requestOptions.isTransformationAllowed()
         && view.getScaleType() != null) {
       switch (view.getScaleType()) {
         case CENTER_CROP:
           requestOptions = requestOptions.clone().optionalCenterCrop();
           break;
         case CENTER_INSIDE:
           requestOptions = requestOptions.clone().optionalCenterInside();
           break;
         case FIT_CENTER:
         case FIT_START:
         case FIT_END:
           requestOptions = requestOptions.clone().optionalFitCenter();
           break;
         case FIT_XY:
           requestOptions = requestOptions.clone().optionalCenterInside();
           break;
         case CENTER:
         case MATRIX:
         default:
       }
     }
    
    • 构建请求
      Request request = buildRequest(target, targetListener, options, callbackExecutor);
      
      1. 构建主请求实例mainRequest
        里面重点就是创建请求实例,创建请求实例分两种情况。
        • 需求请求缩略图
          如果有缩略图将创建全图fullRequest(SingleRequest类型)和缩略图thumbnailRequest(SingleRequest类型)请求实例,并且会创建一个持有这两个请求实例的协同请求实例coordinator(ThumbnailRequestCoordinator类型),
        • 不需要缩略图
          直接创建全图fullRequest(SingleRequest类型)请求实例。
      2. 创建错误请求实例
        如果错误请求协同实例errorRequestCoordinator是为null,那么会创建主请求实例和错误请求实例,并且设置到errorRequestCoordinator(ErrorRequestCoordinator类型)中。
  • 获取缓存Request

    Request previous = target.getRequest();
    

    如果首次加载图片,previous是null,在正式加载前,调用目标View的setTag方法将请求实例request保存到View中,如果获得到的实例不是Request类型,那么将直接抛异常

     "You must not call setTag() on a view Glide is targeting"
    

    所以用glide是不能在给View设置tag的,否则会报异常。

    判断是否是同一个任务逻辑如下:

    return localOverrideWidth == otherLocalOverrideWidth
          && localOverrideHeight == otherLocalOverrideHeight
          // 需要加载的图片路径。比如url
          && Util.bothModelsNullEquivalentOrEquals(localModel, otherLocalModel)
          // 转换类型
          && localTransocdeClass.equals(otherLocalTransocdeClass)
          && localRequestOptions.equals(otherLocalRequestOptions)
          && localPriority == otherLocalPriority  
          && localListenerCount == otherLocalListenerCount;
    

    大概意思就是,请求的图片路径,应用到目标View的基本参数,任务优先级等相同就任务是相同任务请求。

    如果是同一任务,有下面几种情况

    1. 开启缓存
      • 如果请求已完成,则重新开始重新传递请求结果,从而触发RequestListeners和Targets
      • 如果请求失败,则重新启动请求,完成请求。
      • 如果请求已经正在运行,我们可以让它继续运行而不会中断
    2. 没有开启缓存
      • 如果请求失败,则重新启动请求,完成请求。
      • 如果请求已经正在运行,我们可以让它继续运行而不会中断
  • 缓存request

    target.setRequest(request);
    
  • 运行任务

    public void runRequest(@NonNull Request request) {
     requests.add(request);
     if (!isPaused) {
       request.begin();
     } else {
       request.clear();
       if (Log.isLoggable(TAG, Log.VERBOSE)) {
         Log.v(TAG, "Paused, delaying request");
       }
       pendingRequests.add(request);
     }
    }
    

    如果请求已经暂停,者清理请求,释放此请求所拥有的任何资源,显示当前占位图(如果已提供),并将该请求标记为已取消,然后将该请求添加到等待队列里。否则,加载占位图,开始运行任务。

    1. 请求已经完成
      获取结果,改变请求状态,通知。

      try {
          boolean anyListenerHandledUpdatingTarget = false;
          if (requestListeners != null) {
             for (RequestListener<R> listener : requestListeners) {
                 anyListenerHandledUpdatingTarget |=
                    listener.onResourceReady(result, model, target, dataSource, isFirstResource);
            }
          }
          anyListenerHandledUpdatingTarget |=
          targetListener != null
              && targetListener.onResourceReady(result, model, target, dataSource, isFirstResource);
      
          if (!anyListenerHandledUpdatingTarget) {
              Transition<? super R> animation = animationFactory.build(dataSource, isFirstResource);
        target.onResourceReady(result, animation);
      }
      } finally {
        isCallingCallbacks = false;
      }
      notifyLoadSuccess();
      

      后面继续分析,这些回调里面都做了什么操作。

    2. 计算目标View尺寸
      一种是你使用了override() API为图片指定了固定的宽高,一种是没有指定。如果指定了就调用onSizeReady()方法。如果没指定就调用target.getSize()方法,并根据ImageView的layout_width和layout_height值来算出图片应该的宽高

      if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
        onSizeReady(overrideWidth, overrideHeight);
      } else {
        // 通过目标view计算
        target.getSize(this);
      }
      
    3. 通知请求开始导入

      if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
          && canNotifyStatusChanged()) {
        target.onLoadStarted(getPlaceholderDrawable());
      }
      

请求执行引擎

这部分讲解真正的从网络端获取数据,并处理的逻辑

获取缓存数据

如果缓存存在,者直接返回缓存数据,不在执行请求,否则运行请求。

  • 构建缓存key
    缓存key和下面的参数有关
     EngineKey key =
         keyFactory.buildKey(
             model,
             signature,
             width,
             height,
             transformations,
             resourceClass,
             transcodeClass,
             options);
    
创建请求任务

请求任务也进行了缓存,如果缓存里面存在该请求任务,这不会创建新的请求任务,只添加执行回调。具体,请自己看源码。

    EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
    if (current != null) {
      current.addCallback(cb, callbackExecutor);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Added to existing load", startTime, key);
      }
      return new LoadStatus(cb, current);
    }

如果缓存没有该任务,者会创建EngineJob和DecodeJob两个任务实例,然后缓存,执行任务

jobs.put(key, engineJob);
engineJob.addCallback(cb, callbackExecutor);
engineJob.start(decodeJob);
执行请求任务
engineJob.start(decodeJob);

调用这个方法后会执行DecodeJob中的run方法,然后执行 runWrapped()

  private void runWrapped() {
    switch (runReason) {
      case INITIALIZE:
        stage = getNextStage(Stage.INITIALIZE);
        currentGenerator = getNextGenerator();
        runGenerators();
        break;
      case SWITCH_TO_SOURCE_SERVICE:
        runGenerators();
        break;
      case DECODE_DATA:
        decodeFromRetrievedData();
        break;
      default:
        throw new IllegalStateException("Unrecognized run reason: " + runReason);
    }
  }

根据不同的运行类型,执行不同的流程

  • 数据提取器生成器

    private DataFetcherGenerator getNextGenerator() {
     switch (stage) {
       case RESOURCE_CACHE:
         return new ResourceCacheGenerator(decodeHelper, this);
       case DATA_CACHE:
         return new DataCacheGenerator(decodeHelper, this);
       case SOURCE:
         return new SourceGenerator(decodeHelper, this);
       case FINISHED:
         return null;
       default:
         throw new IllegalStateException("Unrecognized stage: " + stage);
     }
    }
    
  • 执行数据提取器生成器

     while (!isCancelled
         && currentGenerator != null
         && !(isStarted = currentGenerator.startNext())) {
       stage = getNextStage(stage);
       currentGenerator = getNextGenerator();
    
       if (stage == Stage.SOURCE) {
         reschedule();
         return;
       }
     }
    
  • 获取数据

    loadData.fetcher.loadData(helper.getPriority(), this);
    
    截屏2020-03-18下午4.51.07.png
    public void loadData(
        @NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
      long startTime = LogTime.getLogTime();
      try {
        InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
        callback.onDataReady(result);
      } catch (IOException e) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
          Log.d(TAG, "Failed to load data for url", e);
        }
        callback.onLoadFailed(e);
      } finally {
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
          Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime));
        }
      }
    }
    
  • 读数据

    public void onDataReady(Object data) {
      DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
      if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
        dataToCache = data;
        // We might be being called back on someone else's thread. Before doing anything, we should
        // reschedule to get back onto Glide's thread.
        cb.reschedule();
      } else {
        cb.onDataFetcherReady(
            loadData.sourceKey,
            data,
            loadData.fetcher,
            loadData.fetcher.getDataSource(),
            originalKey);
      }
    }
    
  • 解析数据
    解析数据的逻辑可以自己去分析,比如需要的是bitmap,拿到数据流后,就会解析成bitmap,再进行一些转换,然后通知数据获取完成

    public Resource<Transcode> decode(
        DataRewinder<DataType> rewinder,
        int width,
        int height,
        @NonNull Options options,
        DecodeCallback<ResourceType> callback)
        throws GlideException {
      Resource<ResourceType> decoded = decodeResource(rewinder, width, height, options);
      Resource<ResourceType> transformed = callback.onResourceDecoded(decoded);
      return transcoder.transcode(transformed, options);
    }
    
    private void notifyComplete(Resource<R> resource, DataSource dataSource) {
      setNotifiedOrThrow();
      callback.onResourceReady(resource, dataSource);
    }
    
  • 设置数据
    在设置数据之前,会根据参数配置处理好数据,然后设置给View

    1. 比如BitmapContainerTransitionFactory转换工厂
      @Override
      public boolean transition(R current, ViewAdapter adapter) {
          Resources resources = adapter.getView().getResources();
          Drawable currentBitmap = new BitmapDrawable(resources, getBitmap(current));
          return transition.transition(currentBitmap, adapter);
      }
      
    2. ImageViewTarget
      public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
         if (transition == null || !transition.transition(resource, this)) {
         setResourceInternal(resource);
       } else {
         maybeUpdateAnimatable(resource);
       }
      }
      

总结

glide加载大体流程就这样,最后面数据解析和回调写得有点模糊,自己看了一下代码,有点多,自己可以去跟踪一下,看一下大体处理逻辑。下一篇将写一篇用法总结,如果对里面的原理不关心的,可以阅读我下一篇文章。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容