Android 开发之Glide《二》

前言

前一章介绍了Glide 的创建与Request 的创建,本篇我们探究Request 的执行过程。

一、下载图片

  private <Y extends Target<TranscodeType>> Y into(
      @NonNull Y target,
      @Nullable RequestListener<TranscodeType> targetListener,
      @NonNull RequestOptions options) {
    options = options.autoClone();
    Request request = buildRequest(target, targetListener, options);
    Request previous = target.getRequest();
    if (request.isEquivalentTo(previous)
        && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
      request.recycle();
        previous.begin();
      }
      return target;
    }

    requestManager.clear(target);
    target.setRequest(request);
    //
    requestManager.track(target, request);

    return target;
  }

request的请求是由requestManager.track(target, request); 方法发起

在这里插入图片描述

targetTracker.track(target); 试讲target 加入到一个列表里面。targetTracker 用于保存当前活跃的所有Target。

  public void runRequest(Request request) {
  //将请求加入的队列当中
    requests.add(request);
    if (!isPaused) {
    //开始执行请求
      request.begin();
    } else {
      pendingRequests.add(request);
    }
  }

runRequest 首先将请求加入到队列里面,然后调用请求的begin 方法,这里以SImpleRequest为例分析。

public void begin() {
	//代码有删减
	
    startTime = LogTime.getLogTime();
    //model 可以看成是图片额url
    if (model == null) {
      if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
        width = overrideWidth;
        height = overrideHeight;
      }
      int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
      //抛出异常
      onLoadFailed(new GlideException("Received null model"), logLevel);
      return;
    }
    //如果当前状态正处于加载图片的状态,则抛出异常
    if (status == Status.RUNNING) {
      throw new IllegalArgumentException("Cannot restart a running request");
    }
    if (status == Status.COMPLETE) {
      //如果当前状态处于已经加载完成,则回调资源已经加载完毕
      onResourceReady(resource, DataSource.MEMORY_CACHE);
      return;
    }
    //标识当前状态为等待获取View的大小信息,
    status = Status.WAITING_FOR_SIZE;
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
      //如果View大小已经测量完成,则调用onSizeReady方法进行图片加载请求
      onSizeReady(overrideWidth, overrideHeight);
    } else {
      //如果view大小未就绪,则先去获取view的大小。
     //这个很重要,实际是注册一个回调接口,在View测量完大小之后会
   //调用onSizeReady。
      target.getSize(this);
    }

    if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
        && canNotifyStatusChanged()) {
      //通知图片开始加载
      target.onLoadStarted(getPlaceholderDrawable());
    }
  }

这个方法中,先是对model进行判断,这个model此时就是我们传的那个url,如果为空,则直接load失败,然后是一些状态的检查和一些回调方法等,接下来判断size,如果是有效的,则触发去真正的请求图片,否则设置一个回调,等待view布局有size之后,再来触发请求,真正的请求其实就在onSizeReady中被得到执行。

begin的步骤可以归纳如下:
a、如果当前状态是正在加载图片,则抛出异常,结束
b、如果当前状态是已经加载完成,则回调加载图片已就绪方法
c、如果当前View大小已经就绪,则调用onSizeReady方法实现图片加载
d、如果当前View大小为就绪,则等待View大小就绪再回调onSizeReady实现图片加载
e、调用onLoadStarted通知图片开始加载

onSizeReady 内部是通过线程池现在图片,在进入onSizeReady 之前我们先看target.onLoadStarted(getPlaceholderDrawable());

ImageViewTarget

  public void onLoadStarted(@Nullable Drawable placeholder) {
    super.onLoadStarted(placeholder);
    setResourceInternal(null);
    setDrawable(placeholder);
  }
  public void setDrawable(Drawable drawable) {
    view.setImageDrawable(drawable);
  }

onLoadStarted 是在开启线程下载图片之前先显示占位图片。

public void onSizeReady(int width, int height) {
    stateVerifier.throwIfRecycled();
    if (IS_VERBOSE_LOGGABLE) {
      logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
    }
    if (status != Status.WAITING_FOR_SIZE) {
      //如果当前状态不处于等待获取View大小,则返回结束
      return;
    }
    //更改当前状态为正在运行图片加载中
    status = Status.RUNNING;
    //获取图片大小的乘积系数,也就是放大或是缩小的倍数。
    float sizeMultiplier = requestOptions.getSizeMultiplier();
    //如果图片大小为-1,则返回图片大小为负数,否则=大小*sizeMultiplier
    this.width = maybeApplySizeMultiplier(width, sizeMultiplier);
    this.height = maybeApplySizeMultiplier(height, sizeMultiplier);

    //通过图片加载引擎加载图片
    loadStatus = engine.load(
        glideContext,
        model,
        requestOptions.getSignature(),
        this.width,
        this.height,
        requestOptions.getResourceClass(),
        transcodeClass,
        priority,
        requestOptions.getDiskCacheStrategy(),
        requestOptions.getTransformations(),
        requestOptions.isTransformationRequired(),
        requestOptions.isScaleOnlyOrNoTransform(),
        requestOptions.getOptions(),
        requestOptions.isMemoryCacheable(),
        requestOptions.getUseUnlimitedSourceGeneratorsPool(),
        requestOptions.getUseAnimationPool(),
        requestOptions.getOnlyRetrieveFromCache(),
        this);

    if (status != Status.RUNNING) {
      loadStatus = null;
    }

  }

这个方法中,首先检查状态是不是在等待size,如果不是,则表明size已经有了,下面则是更新状态到Status.RUNNING,进而去调用Engine的load 方法。

Engine 是Glide很重要的一个类,其作用是执行请求以及管理请求下来的资源
在这里插入图片描述

public <R> LoadStatus load(
    GlideContext glideContext,
    Object model,
    Key signature,
    int width,
    int height,
    Class<?> resourceClass,
    Class<R> transcodeClass,
    Priority priority,
    DiskCacheStrategy diskCacheStrategy,
    Map<Class<?>, Transformation<?>> transformations,
    boolean isTransformationRequired,
    boolean isScaleOnlyOrNoTransform,
    Options options,
    boolean isMemoryCacheable,
    boolean useUnlimitedSourceExecutorPool,
    boolean useAnimationPool,
    boolean onlyRetrieveFromCache,
    ResourceCallback cb) {
  Util.assertMainThread();
  long startTime = LogTime.getLogTime();
//构建唯一的key,
  EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
      resourceClass, transcodeClass, options);
 //根据key从活跃的资源列表中获取图片资源对象
  EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
  if (active != null) {
 //如果活跃的图片资源对象存在,则回调资源加载已就绪方法。
    cb.onResourceReady(active, DataSource.MEMORY_CACHE);
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      logWithTimeAndKey("Loaded resource from active resources", startTime, key);
    }
    return null;
  }
//根据key从缓存中加载图片资源对象
  EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
  if (cached != null) {
     //如果缓存图片资源对象存在,则回调资源加载已就绪方法
    cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      logWithTimeAndKey("Loaded resource from cache", startTime, key);
    }
    return null;
  }
//根据key从图片加载任务中获取任务对象
  EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);	
  if (current != null) {
  //如果已经有任务正在加载图片资源,则添加回调,等待任务完成之后调度          
    current.addCallback(cb);
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      logWithTimeAndKey("Added to existing load", startTime, key);
    }
    return new LoadStatus(cb, current);
  }
 
 //构建一个新的图片加载任务对象
  EngineJob<R> engineJob =
      engineJobFactory.build(
          key,
          isMemoryCacheable,
          useUnlimitedSourceExecutorPool,
          useAnimationPool,
          onlyRetrieveFromCache);
 //构建一个新的图片解析任务对象
  DecodeJob<R> decodeJob =
      decodeJobFactory.build(
          glideContext,
          model,
          key,
          signature,
          width,
          height,
          resourceClass,
          transcodeClass,
          priority,
          diskCacheStrategy,
          transformations,
          isTransformationRequired,
          isScaleOnlyOrNoTransform,
          onlyRetrieveFromCache,
          options,
          engineJob);
 //将新构建的图片加载任务加入任务列表中
  jobs.put(key, engineJob);
//添加回调
  engineJob.addCallback(cb);
 //调用图片加载任务的start方法开始加载图片
  engineJob.start(decodeJob);

  if (Log.isLoggable(TAG, Log.VERBOSE)) {
    logWithTimeAndKey("Started new load", startTime, key);
  }
  return new LoadStatus(cb, engineJob);
}

对于图片加载引擎Engine的load实现可以归纳如下:
a、构建图片加载对应的key
b、如果活跃资源列表中存在要加载的图片资源,则回调资源准备就绪
c、如果缓存列表中存在要加载的图片资源,则回调资源准备就绪
d、如果存在要加载图片对于的图片加载任务,则添加回调给图片加载任务,等待任务加载图片完成调度
e、构建图片加载任务和图片解析任务对象
f、通过图片加载任务加载任务

这里涉及到了Glide 的缓存策略,本处值简单介绍,具体的实现有机会在单开一篇文章。
Glide有三级缓存策略,一个是活跃内存缓存,一个是内存缓存,一个是磁盘缓存。这边文章将对活跃内存缓存策略进行剖析
活跃缓存使用的是弱引用缓存,内存缓存是强引用缓存,磁盘缓存就是磁盘缓存。
如果没有在缓存中找到想要下载的图片,此时会新建一个engineJob,然后通过engineJob的start 方法下载图片。

  public void start(DecodeJob<R> decodeJob) {
    this.decodeJob = decodeJob;
    //具体使用哪一个线程池由DecodeJob自己决定。
    GlideExecutor executor = decodeJob.willDecodeFromCache()
        ? diskCacheExecutor
        : getActiveSourceExecutor();
    executor.execute(decodeJob);
  }

DecodeJob对象被放到线程池Executor执行。
在这里插入图片描述

我们看看DecodeJob的run方法

@Override
  public void run() {
    //...
  DataFetcher<?> localFetcher = currentFetcher;
    try {
      if (isCancelled) {
        //已取消,则通知加载失败
        notifyFailed();
        return;
      }
      //run的包裹实现
      runWrapped();
    } catch (Throwable t) {
      //代码有删减
    } finally {
      if (localFetcher != null) {
        //清除数据、释放相关资源
        localFetcher.cleanup();
      }
      GlideTrace.endSection();
    }
  }

private void runWrapped() {
    switch (runReason) {
//主要分析这个
      case INITIALIZE:
        //当前状态是初始化状态
        //此处返回的是RESOURCE_CACHE
        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);
    }
  }

通常缓存文件包含内存缓存与磁盘缓存,内存缓存在上面介绍了主要是软引用缓存与强引用缓存,而Glide 的磁盘缓存有分为两种缓存一种是原图片的缓存,一种是变换之后的缓存文件,例如缓存裁剪,缩放之后的图片。因此加载磁盘文件也分为两种,先加载变换之后的文件,如果有那么直接返回该文件,如果不存在那么就加载原图片,缩放,裁剪之后返回,如果也没有缓存原图片,那么就只能加载网络图片,RESOURCE_CACHE,DATA_CACHE,SOURCE 对应的就是这三种情形。

第一次进入这个方法的时候runReason 为INITIALIZE,此时会读取缓存的图片,如果没有找到缓存的图片,runGenerators 里面的reschedule 会再次进入到runWrapped 方法(后面有详细介绍),再次进入会执行SWITCH_TO_SOURCE_SERVICE 分支,此时会从网络挤在图片。当从网络下载图片之后可能再次进入到runWrapped 方法runReason就是DECODE_DATA,此时主要是缓存图片以及执行回调显示图片。

在分析runWrapped的时候有山重水复疑无路的感觉,但是实际距离最终目的地尚有西天还有十万八千里呢!

通过变量runWrapped选择性的进入分支
stage记录目前执行阶段,currentGenerator记录目前Generator
runGenerators方法推进stage和currentGenerator的变化,进入下一阶段。

我们看下getNextGenerator的实现:


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);
    }
  }

通常缓存文件包含内存缓存与磁盘缓存,内存缓存在上面介绍了主要是软引用缓存与强引用缓存,而Glide 的磁盘缓存有分为两种缓存一种是原图片的缓存,一种是变换之后的缓存文件,例如缓存裁剪,缩放之后的图片。因此加载磁盘文件也分为两种,先加载变换之后的文件,如果有那么直接返回该文件,如果不存在那么就加载原图片,缩放,裁剪之后返回,如果也没有缓存原图片,那么就只能加载网络图片,RESOURCE_CACHE,DATA_CACHE,SOURCE 对应的就是这三种情形。

我们这里主要分析SourceGenerator,也就是加载网络图片。

private void runGenerators() {
    //获取当前线程
    currentThread = Thread.currentThread();
    //记录当前获取的时间
    startFetchTime = LogTime.getLogTime();
    //开始获取状态
    boolean isStarted = false;
 //开启循环while一直在推进stage和currentGenerator向下一阶段变化
    while (!isCancelled && currentGenerator != null
        && !(isStarted = currentGenerator.startNext())) {
      stage = getNextStage(stage);
      //获取新的状态对应的图片加载执行器
      currentGenerator = getNextGenerator();
      
      if (stage == Stage.SOURCE) {
        //如果当前新的状态为从网络读取,则重新规划图片加载的请求
        reschedule();
        return;
      }
    }
    if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
      //状态已完成、已取消图片加载、执行器执行失败,通知失败
      notifyFailed();
    }

  }

while 循环与getNextGenerator 组合在一起实现的就是先读取转换后的缓存,没有的话就读取缓存的源文件,还是没有就从网络加载。我们进入网络加载也就是reschedule

  @Override
  public void reschedule() {
    runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
    callback.reschedule(this);
  }

callback 这是实际就是EngineJob在这里插入图片描述

EngineJob

  @Override
  public void reschedule(DecodeJob<?> job) {
    // Even if the job is cancelled here, it still needs to be scheduled so that it can clean itself
    // 开启一个新的线程
    getActiveSourceExecutor().execute(job);
  }
  @Override
  public void run() {
    DataFetcher<?> localFetcher = currentFetcher;
    try {
      runWrapped();
    } catch (Throwable t) {
    //xxx
    } finally {
		//xxx
    }
  }

此处再一次执行runWrapped,但是此时的runReason 已经不是INITIALIZE而是SWITCH_TO_SOURCE_SERVICE。
由此也可以知道,整个获取图片的流程中DecodeJob的run会执行多次。根据每次的runReason 不同执行不同的分支。

第二次调用 runWrapper的时候runReason 是SWITCH_TO_SOURCE_SERVICE
在这里插入图片描述
在这里插入图片描述

此时的currentGenerator 是SourceGenerator,此处会调用startNext 方法。

@Override
public boolean startNext() {
  if (dataToCache != null) {
  //dataToCache是网络图片流,网络图片流不为空
    Object data = dataToCache;
    dataToCache = null;
//读取图片缓存流,并缓存图片到磁盘中
    cacheData(data);
  }

  if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
 //磁盘缓存执行器不为空,则执行磁盘缓存执行器
    return true;
  }
  sourceCacheGenerator = null;

  loadData = null;
  boolean started = false;
  while (!started && hasNextModelLoader()) {
//遍历网络图片加载器
    loadData = helper.getLoadData().get(loadDataListIndex++);
    if (loadData != null
        && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
        || helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
      started = true;
 //使用网络图片加载器加载图片
      loadData.fetcher.loadData(helper.getPriority(), this);
    }
  }
  return started;
}

上面的代码主要做了两个逻辑:
1、先通过getLoadData获取ModelLoder对象的集合,然后获取一个LoadData对象
2、通过loadData对象的fetcher对象的loadData方法来获取图片数据。
在这里插入图片描述

这里的网络图片加载器,如果不指定默认使用的是HttpUrlFetcher,如果配置了其他的网络图片加载器,比如OkHttpStreamFetcher,则使用配置的网络加载器,我们这里看HttpUrlFetcher的loadData是怎么加载图片的:
在这里插入图片描述

LoadData 对象内部有一个DataFetcher,该类就是Glide实际下载图片的类,我们直接进入HttpUrlFetcher对象的loadData方法看看:

@Override
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处理
    callback.onDataReady(result);
  } catch (IOException e) {
    callback.onLoadFailed(e);
  } 
  }
}

loaddata方法是真正获取图片数据的地方:
1、调用loadDataWithRedirects方法,发起网络请求,讲URL指定的图片数据转换成InputStream输入流,该方法内部就是通过URLConnection对象来完成的。

调用loadDataWithRedirects 获取数据

//发起http请求获取图片输入流
private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl,
      Map<String, String> headers) throws IOException {
    if (redirects >= MAXIMUM_REDIRECTS) {
      throw new HttpException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
    } else {
      // Comparing the URLs using .equals performs additional network I/O and is generally broken.
      // See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.
      try {
        if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
          throw new HttpException("In re-direct loop");

        }
      } catch (URISyntaxException e) {
        // Do nothing, this is best effort.
      }
    }
    //构建http请求连接
    urlConnection = connectionFactory.build(url);
    for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
      //添加请求头
      urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
    }
    //设置连接、读超时时间
    urlConnection.setConnectTimeout(timeout);
    urlConnection.setReadTimeout(timeout);
    //禁用缓存
    urlConnection.setUseCaches(false);
    //使用post请求方式
    urlConnection.setDoInput(true);

    // Stop the urlConnection instance of HttpUrlConnection from following redirects so that
    // redirects will be handled by recursive calls to this method, loadDataWithRedirects.
    urlConnection.setInstanceFollowRedirects(false);

    // Connect explicitly to avoid errors in decoders if connection fails.
    //连接http
    urlConnection.connect();
    // Set the stream so that it's closed in cleanup to avoid resource leaks. See #2352.
    //获取图片输入流
    stream = urlConnection.getInputStream();
    if (isCancelled) {
      return null;
    }
    final int statusCode = urlConnection.getResponseCode();
    if (isHttpOk(statusCode)) {
      //http请求成功
      return getStreamForSuccessfulRequest(urlConnection);
    } else if (isHttpRedirect(statusCode)) {
      //重定向重新发起请求
      String redirectUrlString = urlConnection.getHeaderField("Location");
      if (TextUtils.isEmpty(redirectUrlString)) {
        throw new HttpException("Received empty or null redirect url");
      }
      URL redirectUrl = new URL(url, redirectUrlString);
      // Closing the stream specifically is required to avoid leaking ResponseBodys in addition
      // to disconnecting the url connection below. See #2352.
      cleanup();
      return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
    } else if (statusCode == INVALID_STATUS_CODE) {
      //网络请求失败,抛出异常
      throw new HttpException(statusCode);
    } else {
      //网络请求失败,抛出异常
      throw new HttpException(urlConnection.getResponseMessage(), statusCode);
    }
  }

这里通过URLConnection 打开url。然后将对应的数据流返回

总结

至此,Glide 下载图片的过程就梳理完了,但是下载图片之后的路程还没有梳理,这些留到下一篇再做介绍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值