Glide解析一:Glide整體流程

注意:如果發(fā)現(xiàn)Glide解析系列的連接訪(fǎng)問(wèn)不了性置,表示此文章是規(guī)劃尚未完成的文章
背景
Glide的使用不在本系列的說(shuō)明范圍,如果要想知道其使用方式請(qǐng)到Glide官網(wǎng)查看混埠。
Gilde是一款快速高效的圖片加載庫(kù)怠缸。Glide老早就出來(lái)了,且其應(yīng)用范圍非常廣钳宪,為什么它深受各位開(kāi)發(fā)者的青睞揭北?它有什么過(guò)人之處?它的框架設(shè)計(jì)的好還是壞吏颖?作為一名渴望進(jìn)步的開(kāi)發(fā)者來(lái)說(shuō)是有必要進(jìn)行深入了解的搔体。如果只關(guān)注它的使用方式,不了解它的思想原理半醉、精髓疚俱,我們只能停留在搬運(yùn)工的階層,很難達(dá)到牛人的階層缩多。
我接下來(lái)將對(duì)Glide進(jìn)行各個(gè)維度的原理解析呆奕。本篇文章先從Glide的整體流程說(shuō)起。

1衬吆、入口

一般情況下我們都是通過(guò)如下的方式使用Glide進(jìn)行加載圖片的:

GlideApp.with(activity)
   .load(myUrl)
   .placeholder(placeholder)
   .fitCenter()
   .into(imageView);

GlideApp是通過(guò)APT根據(jù)我們自定義的GlideModule動(dòng)態(tài)生成的梁钾,其with的實(shí)現(xiàn)如下:

  @NonNull
  public static GlideRequests with(@NonNull Activity activity) {
    return (GlideRequests) Glide.with(activity);
  }

GlideApp內(nèi)部調(diào)用Glide的with方法,大致看一下GlideApp的所有代碼逊抡,其內(nèi)部都是調(diào)用Glide的方法實(shí)現(xiàn)的姆泻,為什么不直接使用Glide而要通過(guò)GlideApp調(diào)用呢?其實(shí)GlideApp可以理解為是Glide的一個(gè)包裹類(lèi)秦忿,這樣的目的是為了封裝麦射、安全性蛾娶。

2灯谣、Glide的with實(shí)現(xiàn)
  public static RequestManager with(@NonNull Activity activity) {
    return getRetriever(activity).get(activity);
  }

Glidewith方法,返回的是RequestManager蛔琅,而RequestManager是通過(guò)getRetriever().get獲取的胎许,我們看下getRetriever:

2.1、Glide的getRetriever:
private static RequestManagerRetriever getRetriever(@Nullable Context context) {
    //省略...
    return Glide.get(context).getRequestManagerRetriever();
  }

getRetriever返回的是RequestManagerRetriever罗售,它先獲取Glide的單例(Glide的初始化通過(guò)Glide.get實(shí)現(xiàn)辜窑,Glide的初始化解析請(qǐng)看這里Glide解析二:Glide的初始化),再使用Glide單例中的getRequestManagerRetriever返回RequestManagerRetriever寨躁,getRequestManagerRetriever用來(lái)干嘛的穆碎?請(qǐng)查看Glide解析三:Glide是如何感知組件生命周期的

2.1.1、RequestManagerRetriever的get返回RequestManager
public RequestManager get(@NonNull Activity activity) {
    if (Util.isOnBackgroundThread()) {
      //如果是在子線(xiàn)程职恳,則綁定Application的生命周期
      return get(activity.getApplicationContext());
    } else {
      //如果是在UI線(xiàn)程所禀,則綁定Activity的生命周期
      assertNotDestroyed(activity);
      android.app.FragmentManager fm = activity.getFragmentManager();
      return fragmentGet(
          activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
    }
  }

RequestManagerRetriever有幾個(gè)get方面,參數(shù)分別有context、fragment色徘、activity恭金、view,其思想主要是根據(jù)不同的生命周期對(duì)象創(chuàng)建一個(gè)RequestManagerFragment褂策,根據(jù)這個(gè)RequestManagerFragment實(shí)現(xiàn)自動(dòng)感知各大組件的生命周期實(shí)現(xiàn)横腿。接著創(chuàng)建RequestManager并綁定RequestManagerFragment,RequestManager主要用來(lái)管理和發(fā)起一個(gè)Glide加載圖片的請(qǐng)求斤寂,因?yàn)榻壎薘equestManagerFragment所以可以感知各大組件的生命周期耿焊,并根據(jù)生命周期對(duì)Glide加載圖片進(jìn)行restart、或者stop等操作扬蕊。
具體的請(qǐng)查看Glide解析三:Glide是如何感知組件生命周期的

3搀别、RequestManager的load實(shí)現(xiàn)

步驟2中解析到Glide的get返回RequestManager,然后使用ReuqestManager進(jìn)行l(wèi)oad操作尾抑,我們看下load的源碼:

  public RequestBuilder<Drawable> load(@Nullable String string) {
    return asDrawable().load(string);
  }

  public RequestBuilder<Drawable> asDrawable() {
    return as(Drawable.class);
  }

  public <ResourceType> RequestBuilder<ResourceType> as(
      @NonNull Class<ResourceType> resourceClass) {
    return new RequestBuilder<>(glide, this, resourceClass, context);
  }

load方法先通過(guò)asDrawable方法創(chuàng)建一個(gè)RequestBuilder歇父,在調(diào)用RequestBuilder的load。as系列操作主要是用于標(biāo)識(shí)將圖片解析成Bitmap還是Drawable再愈。

4榜苫、RequestBuilder

步驟3中解析了RequestManager的load方法先創(chuàng)建一個(gè)RequestBuilder,再調(diào)用RequestBuilder的系列方法設(shè)置一些配置信息想transform翎冲、apply垂睬、error、into等抗悍,接著RequestBuilder再構(gòu)建Glide圖片加載對(duì)象Request驹饺,通過(guò)Request對(duì)象實(shí)現(xiàn)圖片加載的請(qǐng)求。

4.1缴渊、RequestBuilder的load方法
  public RequestBuilder<TranscodeType> load(@Nullable String string) {
    return loadGeneric(string);
  }

  private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
    this.model = model;
    isModelSet = true;
  }

load方法有多重赏壹,可以傳入String、Bitmap衔沼、Uri蝌借、Drawable、file等指蚁,作為model菩佑,代表圖片加載來(lái)源的數(shù)據(jù)模型。String凝化、uri一般是代表從圖片來(lái)源于網(wǎng)絡(luò)稍坯;Bitmap代表圖片來(lái)源當(dāng)前傳進(jìn)來(lái)的Bitmap;Drawable代表圖片來(lái)源于當(dāng)前傳進(jìn)來(lái)的Drawable搓劫;file代表圖片來(lái)源文件瞧哟。

4.2袜蚕、RequestBuilder的transition方法
public RequestBuilder<TranscodeType> transition(
      @NonNull TransitionOptions<?, ? super TranscodeType> transitionOptions) {
    this.transitionOptions = Preconditions.checkNotNull(transitionOptions);
    isDefaultTransitionOptionsSet = false;
    return this;
  }

transition方法只是設(shè)置配置glide圖片加載之后圖片轉(zhuǎn)換器,并標(biāo)識(shí)為不適用默認(rèn)的轉(zhuǎn)換器

4.3绢涡、RequestBuilder的apply方法
public RequestBuilder<TranscodeType> apply(@NonNull RequestOptions requestOptions) {
    Preconditions.checkNotNull(requestOptions);
    this.requestOptions = getMutableOptions().apply(requestOptions);
    return this;
  }

apply方法配置Glide請(qǐng)求的一些配置選項(xiàng)信息

4.4牲剃、RequestBuilder的error方法
public RequestBuilder<TranscodeType> error(@Nullable RequestBuilder<TranscodeType> errorBuilder) {
    this.errorBuilder = errorBuilder;
    return this;
  }

error方法配置圖片加載失敗時(shí)的處理器

4.5、RequestBuilder的listener方法
public RequestBuilder<TranscodeType> listener(
      @Nullable RequestListener<TranscodeType> requestListener) {
    this.requestListeners = null;
    return addListener(requestListener);
  }

public RequestBuilder<TranscodeType> addListener(
      @Nullable RequestListener<TranscodeType> requestListener) {
    if (requestListener != null) {
      if (this.requestListeners == null) {
        this.requestListeners = new ArrayList<>();
      }
      this.requestListeners.add(requestListener);
    }
    return this;
  }

listener內(nèi)部調(diào)用addListener方法雄可,添加圖片加載請(qǐng)求狀態(tài)和結(jié)果監(jiān)聽(tīng)器

4.6凿傅、RequestBuilder的into方法
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
    //檢測(cè)是否在UI線(xiàn)程,如果不是在UI線(xiàn)程則拋出異常
    Util.assertMainThread();
    //如果view為空則拋出隱藏
    Preconditions.checkNotNull(view);
    
    //根據(jù)ImageView的縮放類(lèi)型設(shè)置對(duì)應(yīng)的glide圖片縮放處理器
    RequestOptions requestOptions = this.requestOptions;
    if (!requestOptions.isTransformationSet()
        && requestOptions.isTransformationAllowed()
        && view.getScaleType() != null) {
      // Clone in this method so that if we use this RequestBuilder to load into a View and then
      // into a different target, we don't retain the transformation applied based on the previous
      // View's scale type.
      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:
          // Do nothing.
      }
    }
    //先構(gòu)建ViewTarget数苫,調(diào)用into的重載方法
    return into(
        glideContext.buildImageViewTarget(view, transcodeClass),
        /*targetListener=*/ null,
        requestOptions);
  }

into傳遞的是ImageView時(shí)聪舒,先構(gòu)建為ViewTarget,再調(diào)用into的另一個(gè)重載方法虐急,我們看下ViewTarget的構(gòu)建實(shí)現(xiàn):

//Glide.java
public <X> ViewTarget<ImageView, X> buildImageViewTarget(
      @NonNull ImageView imageView, @NonNull Class<X> transcodeClass) {
    return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
  }
 
//ImageViewTargetFactory.java
public <Z> ViewTarget<ImageView, Z> buildTarget(@NonNull ImageView view,
      @NonNull Class<Z> clazz) {
    if (Bitmap.class.equals(clazz)) {
      return (ViewTarget<ImageView, Z>) new BitmapImageViewTarget(view);
    } else if (Drawable.class.isAssignableFrom(clazz)) {
      return (ViewTarget<ImageView, Z>) new DrawableImageViewTarget(view);
    } else {
      throw new IllegalArgumentException(
          "Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)");
    }
  }

這里ImageViewTargetFactory根據(jù)加載圖片加載回來(lái)的類(lèi)型箱残,構(gòu)建BitmapImageViewTarget或者DrawableImageViewTarget,默認(rèn)是DrawableImageViewTarget止吁。兩者均繼承成ImageViewTarget被辑,ImageViewTarget繼承自ViewTarget,ViewTarget實(shí)現(xiàn)Target接口敬惦。ViewTarget主要用來(lái)根據(jù)圖片加載的狀態(tài)和結(jié)果顯示對(duì)于的圖片盼理、動(dòng)畫(huà),根據(jù)對(duì)應(yīng)的生命周期對(duì)圖片加載請(qǐng)求進(jìn)行暫停俄删、停止宏怔、重新加載等邏輯。Glide解析四:為什么用Target而不直接用ImageView
我們接著看into的重載方法:

private <Y extends Target<TranscodeType>> Y into(
      @NonNull Y target,
      @Nullable RequestListener<TranscodeType> targetListener,
      @NonNull RequestOptions options) {
    //校驗(yàn)是否在Ui線(xiàn)程畴椰,如果不是則拋出異常
    Util.assertMainThread();
    //校驗(yàn)target是否為空臊诊,如果未空則拋出異常
    Preconditions.checkNotNull(target);
    //如果沒(méi)有設(shè)置圖片加載來(lái)源,即從哪里加載圖片斜脂,則拋出異常
    if (!isModelSet) {
      throw new IllegalArgumentException("You must call #load() before calling #into()");
    }
    //克隆圖片加載配置選項(xiàng)
    options = options.autoClone();
    //構(gòu)建圖片加載請(qǐng)求對(duì)象
    Request request = buildRequest(target, targetListener, options);
   
    Request previous = target.getRequest();
    if (request.isEquivalentTo(previous)
        && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
      //如果之前已經(jīng)存在圖片加載請(qǐng)求對(duì)象抓艳,并且不是請(qǐng)求狀態(tài),則發(fā)起請(qǐng)求
      request.recycle();
      // If the request is completed, beginning again will ensure the result is re-delivered,
      // triggering RequestListeners and Targets. If the request is failed, beginning again will
      // restart the request, giving it another chance to complete. If the request is already
      // running, we can let it continue running without interruption.
      if (!Preconditions.checkNotNull(previous).isRunning()) {
        // Use the previous request rather than the new one to allow for optimizations like skipping
        // setting placeholders, tracking and un-tracking Targets, and obtaining View dimensions
        // that are done in the individual Request.
        previous.begin();
      }
      return target;
    }
    //取消正在等待加載或者回收已經(jīng)加載的資源
    requestManager.clear(target);
    //target設(shè)置圖片加載請(qǐng)求對(duì)象
    target.setRequest(request);
    //發(fā)起請(qǐng)求
    requestManager.track(target, request);

    return target;
  }

這里構(gòu)建的是request對(duì)象秽褒,如果我們使用時(shí)不指明Thumbnail壶硅,那么構(gòu)建的默認(rèn)是SingleRequest威兜。
我們看下requestManager.track是如何發(fā)起請(qǐng)求的:

void track(@NonNull Target<?> target, @NonNull Request request) {
    //添加target到追蹤列表中
    targetTracker.track(target);
    //通過(guò)runRequest發(fā)起請(qǐng)求
    requestTracker.runRequest(request);
  }


public void runRequest(@NonNull Request request) {
    
    requests.add(request);
     
    if (!isPaused) {
      //當(dāng)前狀態(tài)不是暫停销斟,則開(kāi)始發(fā)起圖片加載請(qǐng)求
      request.begin();
    } else {
      //如果處于暫停狀態(tài),則取消當(dāng)前圖片加載請(qǐng)求
      request.clear();
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Paused, delaying request");
      }
      //加入等待加載列表
      pendingRequests.add(request);
    }
  }
5椒舵、SingleRequest

SingleRequest是Reuqest的實(shí)現(xiàn)類(lèi)蚂踊,其主要用實(shí)現(xiàn)圖片加載的邏輯。
步驟4中分析到圖片加載的開(kāi)始是在request的begin方法笔宿,我們看下

5.1犁钟、SingleRequest的begin實(shí)現(xiàn):
public void begin() {
    //如果正在調(diào)用回調(diào)方法棱诱,則調(diào)用此方法就會(huì)拋出異常
    assertNotCallingCallbacks();
   //如果已經(jīng)被回收則拋出異常
    stateVerifier.throwIfRecycled();
    startTime = LogTime.getLogTime();
    if (model == null) {
      //如果圖片加載來(lái)源,即從哪里加載涝动,則回調(diào)失敗
      if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
        width = overrideWidth;
        height = overrideHeight;
      }
      // Only log at more verbose log levels if the user has set a fallback drawable, because
      // fallback Drawables indicate the user expects null models occasionally.
      int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
      onLoadFailed(new GlideException("Received null model"), logLevel);
      return;
    }
    //如果當(dāng)前狀態(tài)正處于加載圖片的狀態(tài)迈勋,則拋出異常
    if (status == Status.RUNNING) {
      throw new IllegalArgumentException("Cannot restart a running request");
    }
    
    // If we're restarted after we're complete (usually via something like a notifyDataSetChanged
    // that starts an identical request into the same Target or View), we can simply use the
    // resource and size we retrieved the last time around and skip obtaining a new size, starting a
    // new load etc. This does mean that users who want to restart a load because they expect that
    // the view size has changed will need to explicitly clear the View or Target before starting
    // the new load.
    if (status == Status.COMPLETE) {
      //如果當(dāng)前狀態(tài)處于已經(jīng)加載完成,則回調(diào)資源已經(jīng)加載完畢
      onResourceReady(resource, DataSource.MEMORY_CACHE);
      return;
    }

    // Restarts for requests that are neither complete nor running can be treated as new requests
    // and can run again from the beginning.
    //標(biāo)識(shí)當(dāng)前狀態(tài)為等待獲取View的大小信息
    status = Status.WAITING_FOR_SIZE;
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
      //如果View大小已經(jīng)就緒醋粟,則調(diào)用onSizeReady方法進(jìn)行圖片加載請(qǐng)求
      onSizeReady(overrideWidth, overrideHeight);
    } else {
      //如果view大小未就緒靡菇,則先去獲取view的大小
      target.getSize(this);
    }

    if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
        && canNotifyStatusChanged()) {
      //通知圖片開(kāi)始加載
      target.onLoadStarted(getPlaceholderDrawable());
    }
    if (IS_VERBOSE_LOGGABLE) {
      logV("finished run method in " + LogTime.getElapsedMillis(startTime));
    }
  }

begin的步驟可以歸納如下:
a、如果當(dāng)前狀態(tài)是正在加載圖片米愿,則拋出異常厦凤,結(jié)束
b、如果當(dāng)前狀態(tài)是已經(jīng)加載完成育苟,則回調(diào)加載圖片已就緒方法
c较鼓、如果當(dāng)前View大小已經(jīng)就緒,則調(diào)用onSizeReady方法實(shí)現(xiàn)圖片加載
d违柏、如果當(dāng)前View大小為就緒博烂,則等待View大小就緒再回調(diào)onSizeReady實(shí)現(xiàn)圖片加載
e、調(diào)用onLoadStarted通知圖片開(kāi)始加載
至于當(dāng)View的大小在未知的情況下漱竖,怎么獲取view的大小并回調(diào)onSizeReady的脖母,請(qǐng)查看Glide解析四:為什么用Target而不直接用ImageView

5.2、SingleRequest的onSizeReady實(shí)現(xiàn):
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) {
      //如果當(dāng)前狀態(tài)不處于等待獲取View大小闲孤,則返回結(jié)束
      return;
    }
    //更改當(dāng)前狀態(tài)為正在運(yùn)行圖片加載中
    status = Status.RUNNING;
    //獲取圖片大小的乘積系數(shù)
    float sizeMultiplier = requestOptions.getSizeMultiplier();
    //如果圖片大小為-1谆级,則返回圖片大小為負(fù)數(shù)芬探,否則=大小*sizeMultiplier
    this.width = maybeApplySizeMultiplier(width, sizeMultiplier);
    this.height = maybeApplySizeMultiplier(height, sizeMultiplier);

    if (IS_VERBOSE_LOGGABLE) {
      logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));
    }
    //通過(guò)圖片加載引擎加載圖片
    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);

    // This is a hack that's only useful for testing right now where loads complete synchronously
    // even though under any executor running on any thread but the main thread, the load would
    // have completed asynchronously.
    if (status != Status.RUNNING) {
      loadStatus = null;
    }
    if (IS_VERBOSE_LOGGABLE) {
      logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
    }
  }

onSizeReady的核心是調(diào)用圖片加載引擎engine的load方法加載圖片

6雁芙、Engine
6.1、Engine.load的實(shí)現(xiàn):
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 = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
    //構(gòu)建key
    EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);
    //根據(jù)key從活躍的資源列表中獲取圖片資源對(duì)象
    EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
    if (active != null) {
      //如果活躍的圖片資源對(duì)象存在歹河,則回調(diào)資源加載已就緒方法
      cb.onResourceReady(active, DataSource.MEMORY_CACHE);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }
      return null;
    }
   //根據(jù)key從緩存中加載圖片資源對(duì)象
    EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
      //如果緩存圖片資源對(duì)象存在勤众,則回調(diào)資源加載已就緒方法
      cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }
      return null;
    }
    //根據(jù)key從圖片加載任務(wù)中獲取任務(wù)對(duì)象
    EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
    if (current != null) {
      //如果已經(jīng)有任務(wù)正在加載圖片資源舆绎,則添加回調(diào),等待任務(wù)完成之后調(diào)度          
      current.addCallback(cb);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Added to existing load", startTime, key);
      }
      return new LoadStatus(cb, current);
    }
    //構(gòu)建一個(gè)新的圖片加載任務(wù)對(duì)象
    EngineJob<R> engineJob =
        engineJobFactory.build(
            key,
            isMemoryCacheable,
            useUnlimitedSourceExecutorPool,
            useAnimationPool,
            onlyRetrieveFromCache);
    //構(gòu)建一個(gè)新的圖片解析任務(wù)對(duì)象
    DecodeJob<R> decodeJob =
        decodeJobFactory.build(
            glideContext,
            model,
            key,
            signature,
            width,
            height,
            resourceClass,
            transcodeClass,
            priority,
            diskCacheStrategy,
            transformations,
            isTransformationRequired,
            isScaleOnlyOrNoTransform,
            onlyRetrieveFromCache,
            options,
            engineJob);
    //將新構(gòu)建的圖片加載任務(wù)加入任務(wù)列表中
    jobs.put(key, engineJob);
    //添加回調(diào)
    engineJob.addCallback(cb);
    //調(diào)用圖片加載任務(wù)的start方法開(kāi)始加載圖片
    engineJob.start(decodeJob);

    if (VERBOSE_IS_LOGGABLE) {
      logWithTimeAndKey("Started new load", startTime, key);
    }
    return new LoadStatus(cb, engineJob);
  }

對(duì)于圖片加載引擎Engine的load實(shí)現(xiàn)可以歸納如下:
a们颜、構(gòu)建圖片加載對(duì)應(yīng)的key
b吕朵、如果活躍資源列表中存在要加載的圖片資源,則回調(diào)資源準(zhǔn)備就緒
c窥突、如果緩存列表中存在要加載的圖片資源努溃,則回調(diào)資源準(zhǔn)備就緒
d、如果存在要加載圖片對(duì)于的圖片加載任務(wù)阻问,則添加回調(diào)給圖片加載任務(wù)梧税,等待任務(wù)加載圖片完成調(diào)度
e、構(gòu)建圖片加載任務(wù)和圖片解析任務(wù)對(duì)象
f、通過(guò)圖片加載任務(wù)加載任務(wù)
不知道對(duì)于上面的的這幾個(gè)步驟你是否有疑問(wèn)第队,反正我是有的哮塞,不是說(shuō)三層緩存策略嗎?一是緩存凳谦,二是磁盤(pán)忆畅,三是網(wǎng)絡(luò),這里看上去像只有一和三尸执,二的磁盤(pán)緩存呢邻眷?難道是在圖片加載任務(wù)EngineJob或者DecodeJob中?是的剔交,待下文詳解肆饶。
這里為什么還要有個(gè)活躍緩存?請(qǐng)查看Glide解析五:活躍緩存策略
對(duì)于緩存策略請(qǐng)看這里Glide解析六:緩存策略

7岖常、EngineJob
7.1驯镊、EngineJob的start
public void start(DecodeJob<R> decodeJob) {
    this.decodeJob = decodeJob;
    
    GlideExecutor executor = decodeJob.willDecodeFromCache()
        ? diskCacheExecutor
        : getActiveSourceExecutor();
    executor.execute(decodeJob);
  }

boolean willDecodeFromCache() {
    //獲取下一個(gè)狀態(tài)
    Stage firstStage = getNextStage(Stage.INITIALIZE);
    //如果當(dāng)前狀態(tài)為RESOURCE_CACHE 或者DATA_CACHE代表從磁盤(pán)緩存中讀取
    return firstStage == Stage.RESOURCE_CACHE || firstStage == Stage.DATA_CACHE;
  }

//根據(jù)當(dāng)前狀態(tài)獲取下一個(gè)狀態(tài)
private Stage getNextStage(Stage current) {
    switch (current) {
      case INITIALIZE:
        //當(dāng)前狀態(tài)為初始狀態(tài)——》如果允許從已緩存的資源中讀取,返回轉(zhuǎn)換后磁盤(pán)緩存狀態(tài)竭鞍,否則返回下一個(gè)狀態(tài)
        return diskCacheStrategy.decodeCachedResource()
            ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
      case RESOURCE_CACHE:
      //當(dāng)前狀態(tài)從轉(zhuǎn)換后磁盤(pán)緩存讀取——》如果允許從已緩存原始圖片磁盤(pán)緩存中讀取板惑,則返回原始磁盤(pán)緩存狀態(tài),否則返回下一個(gè)狀態(tài)
        return diskCacheStrategy.decodeCachedData()
            ? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
      case DATA_CACHE:
        //當(dāng)前已緩存原始圖片磁盤(pán)緩存中讀取偎快,如果只從緩存中讀取冯乘,則返回完成狀態(tài),否則返回從網(wǎng)絡(luò)讀取狀態(tài)
        // Skip loading from source if the user opted to only retrieve the resource from cache.
        return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
      case SOURCE:
      case FINISHED:
        //當(dāng)前狀態(tài)從網(wǎng)絡(luò)讀取或者已完成狀態(tài)晒夹,則返回已完成狀態(tài)
        return Stage.FINISHED;
      default:
        throw new IllegalArgumentException("Unrecognized stage: " + current);
    }
  }

這段代碼可以看到如果允許從磁盤(pán)緩存中讀取裆馒,則先從磁盤(pán)緩存中讀取,接著是從網(wǎng)絡(luò)讀取丐怯。
上面的代碼使用GlideExecutor 線(xiàn)程池調(diào)度decodeJob線(xiàn)程喷好,使用decodeJob實(shí)現(xiàn)從磁盤(pán)、數(shù)據(jù)源读跷、網(wǎng)絡(luò)中加載圖片梗搅。這里說(shuō)明下RESOURCE_CACHE、DATA_CACHE效览、SOURCE這三種狀態(tài)的明確意思:

  • RESOURCE_CACHE: 從磁盤(pán)緩存中讀取轉(zhuǎn)換之后的圖片无切,而這里的圖片指的是原始圖片轉(zhuǎn)換之后的圖片,比如從網(wǎng)絡(luò)下載下來(lái)之后丐枉,先緩存到磁盤(pán)的就是原始的圖片哆键,經(jīng)過(guò)對(duì)原始圖片進(jìn)行大小、方向等轉(zhuǎn)換之后的圖片就是轉(zhuǎn)換之后的圖片矛洞。
  • DATA_CACHE: 從磁盤(pán)緩存中讀取原始圖片洼哎,而這個(gè)磁盤(pán)緩存存的就是原始圖片
  • SOURCE: 代表從網(wǎng)絡(luò)上加載的狀態(tài)
7、DecodeJob

DecodeJob是一個(gè)線(xiàn)程對(duì)象沼本,其實(shí)現(xiàn)了Runnable接口:

class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback,
    Runnable,
    Comparable<DecodeJob<?>>,
    Poolable {
    //...
}
7.1噩峦、DecodeJob的run
@Override
  public void run() {
    //...
  DataFetcher<?> localFetcher = currentFetcher;
    try {
      if (isCancelled) {
        //已取消,則通知加載失敗
        notifyFailed();
        return;
      }
      //run的包裹實(shí)現(xiàn)
      runWrapped();
    } catch (Throwable t) {
      //...
      if (stage != Stage.ENCODE) {
        //當(dāng)前狀態(tài)不等于解析圖片狀態(tài)抽兆,則代表加載失敗
        throwables.add(t);
        //通知加載失敗
        notifyFailed();
      }
      if (!isCancelled) {
        throw t;
      }
    } finally {
      if (localFetcher != null) {
        //清除數(shù)據(jù)识补、釋放相關(guān)資源
        localFetcher.cleanup();
      }
      GlideTrace.endSection();
    }
  }

run接著調(diào)用runWrapped方法

7.2、DecodeJob的runWrapped
private void runWrapped() {
    switch (runReason) {
      case INITIALIZE:
        //當(dāng)前狀態(tài)是初始化狀態(tài)
        //根據(jù)當(dāng)前狀態(tài)獲取新的狀態(tài)
        stage = getNextStage(Stage.INITIALIZE);
        //獲取當(dāng)前狀態(tài)的執(zhí)行器
        currentGenerator = getNextGenerator();
        //運(yùn)行當(dāng)前圖片加載執(zhí)行器
        runGenerators();
        break;
      case SWITCH_TO_SOURCE_SERVICE:
        //當(dāng)前狀態(tài)轉(zhuǎn)為從網(wǎng)絡(luò)加載
        //運(yùn)行當(dāng)前圖片加載執(zhí)行器
        runGenerators();
        break;
      case DECODE_DATA:
        //當(dāng)前狀態(tài)為解析圖片數(shù)據(jù)
        //從獲取的數(shù)據(jù)源中解析圖片
        decodeFromRetrievedData();
        break;
      default:
        //未知的狀態(tài)辫红,拋出異常
        throw new IllegalStateException("Unrecognized run reason: " + runReason);
    }
  }

步驟6.1中知道凭涂,INITIALIZE之后的狀態(tài)就是從磁盤(pán)緩存中讀取轉(zhuǎn)換后圖片的狀態(tài),我們看下getNextGenerator的實(shí)現(xiàn):

private DataFetcherGenerator getNextGenerator() {
    switch (stage) {
      case RESOURCE_CACHE:
        //當(dāng)前狀態(tài)為從磁盤(pán)緩存中讀取轉(zhuǎn)換后圖片狀態(tài)贴妻,創(chuàng)建一個(gè)從磁盤(pán)緩存讀取轉(zhuǎn)換后圖片的執(zhí)行器
        return new ResourceCacheGenerator(decodeHelper, this);
      case DATA_CACHE:
        //當(dāng)前狀態(tài)為從磁盤(pán)緩存中讀取原始圖片狀態(tài)切油,創(chuàng)建一個(gè)從磁盤(pán)緩存讀取原始圖片的執(zhí)行器
        return new DataCacheGenerator(decodeHelper, this);
      case SOURCE:
        //當(dāng)前狀態(tài)為從網(wǎng)絡(luò)讀取狀態(tài),創(chuàng)建一個(gè)從網(wǎng)絡(luò)讀取的執(zhí)行器
        return new SourceGenerator(decodeHelper, this);
      case FINISHED:
        //當(dāng)前狀態(tài)為完成狀態(tài)名惩,返回空
        return null;
      default:
        //未知狀態(tài)澎胡,拋出異常
        throw new IllegalStateException("Unrecognized stage: " + stage);
    }
  }

創(chuàng)建一個(gè)圖片加載的執(zhí)行器之后,接著就是運(yùn)行執(zhí)行器娩鹉,我們看下runGenerators的源碼:

private void runGenerators() {
    //獲取當(dāng)前線(xiàn)程
    currentThread = Thread.currentThread();
    //記錄當(dāng)前獲取的時(shí)間
    startFetchTime = LogTime.getLogTime();
    //開(kāi)始獲取狀態(tài)
    boolean isStarted = false;
    while (!isCancelled && currentGenerator != null
        && !(isStarted = currentGenerator.startNext())) {
      //如果當(dāng)前未取消攻谁、執(zhí)行不為空、并且執(zhí)行器執(zhí)行失敗弯予,則
      //獲取新的狀態(tài)
      stage = getNextStage(stage);
      //獲取新的狀態(tài)對(duì)應(yīng)的圖片加載執(zhí)行器
      currentGenerator = getNextGenerator();
      
      if (stage == Stage.SOURCE) {
        //如果當(dāng)前新的狀態(tài)從網(wǎng)絡(luò)讀取戚宦,則重新規(guī)劃圖片加載的請(qǐng)求
        reschedule();
        return;
      }
    }
    // We've run out of stages and generators, give up.
    if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
      //狀態(tài)已完成、已取消圖片加載锈嫩、執(zhí)行器執(zhí)行失敗受楼,通知失敗
      notifyFailed();
    }

    // Otherwise a generator started a new load and we expect to be called back in
    // onDataFetcherReady.
  }

根據(jù)上面的代碼分析,我們可以知道優(yōu)先從磁盤(pán)緩存中讀取轉(zhuǎn)換后圖片呼寸,我們看下磁盤(pán)緩存中讀取轉(zhuǎn)換后圖片執(zhí)行器ResourceCacheGenerator

7.2.1那槽、ResourceCacheGenerator的startNext
public boolean startNext() {
    //獲取磁盤(pán)緩存按鍵列表
    List<Key> sourceIds = helper.getCacheKeys();
    if (sourceIds.isEmpty()) {
      //如果按鍵列表為空,代表沒(méi)有磁盤(pán)緩存等舔,返回執(zhí)行失敗
      return false;
    }
    //返回磁盤(pán)資源類(lèi)型列表
    List<Class<?>> resourceClasses = helper.getRegisteredResourceClasses();
    if (resourceClasses.isEmpty()) {
      
      if (File.class.equals(helper.getTranscodeClass())) {
        return false;
      }
      //如果磁盤(pán)資源類(lèi)型列表為空拋出異常
      // TODO(b/73882030): This case gets triggered when it shouldn't. With this assertion it causes
      // all loads to fail. Without this assertion it causes loads to miss the disk cache
      // unnecessarily
      // throw new IllegalStateException(
      //    "Failed to find any load path from " + helper.getModelClass() + " to "
      //        + helper.getTranscodeClass());
    }
    while (modelLoaders == null || !hasNextModelLoader()) {
      //如果圖片加載器為空或者沒(méi)有下一個(gè)圖片加載器
      resourceClassIndex++;
      if (resourceClassIndex >= resourceClasses.size()) {
        sourceIdIndex++;
        if (sourceIdIndex >= sourceIds.size()) {
          return false;
        }
        resourceClassIndex = 0;
      }

      Key sourceId = sourceIds.get(sourceIdIndex);
      Class<?> resourceClass = resourceClasses.get(resourceClassIndex);
      Transformation<?> transformation = helper.getTransformation(resourceClass);
      // PMD.AvoidInstantiatingObjectsInLoops Each iteration is comparatively expensive anyway,
      // we only run until the first one succeeds, the loop runs for only a limited
      // number of iterations on the order of 10-20 in the worst case.
      //生成新的磁盤(pán)緩存key
      currentKey =
          new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops
              helper.getArrayPool(),
              sourceId,
              helper.getSignature(),
              helper.getWidth(),
              helper.getHeight(),
              transformation,
              resourceClass,
              helper.getOptions());
      //根據(jù)key獲取磁盤(pán)緩存文件
      cacheFile = helper.getDiskCache().get(currentKey);
      if (cacheFile != null) {
        sourceKey = sourceId;
        //根據(jù)文件獲取對(duì)應(yīng)的磁盤(pán)緩存圖片加載器
        modelLoaders = helper.getModelLoaders(cacheFile);
        modelLoaderIndex = 0;
      }
    }

    loadData = null;
    boolean started = false;
    while (!started && hasNextModelLoader()) {
      //遍歷磁盤(pán)緩存圖片加載器
      ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
     //圖片加載器構(gòu)建圖片加載對(duì)象 
     loadData = modelLoader.buildLoadData(cacheFile,
          helper.getWidth(), helper.getHeight(), helper.getOptions());
      if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
        started = true;
        //加載圖片圖片數(shù)據(jù)
        loadData.fetcher.loadData(helper.getPriority(), this);
      }
    }

    return started;
  }

第一次從磁盤(pán)緩存加載的時(shí)候骚灸,磁盤(pán)緩存為空,所以返回false慌植。至于磁盤(pán)緩存怎么加載圖片甚牲,請(qǐng)查看Glide解析七:磁盤(pán)緩存策略
按照上面的分析結(jié)果,從磁盤(pán)緩存中讀取轉(zhuǎn)換后圖片狀態(tài)之后就是從磁盤(pán)緩存中讀取原始圖片狀態(tài)蝶柿,對(duì)應(yīng)的執(zhí)行器是DataCacheGenerator

7.2.2丈钙、DataCacheGenerator的startNext
public boolean startNext() {
    while (modelLoaders == null || !hasNextModelLoader()) {
      //如果圖片加載器或者沒(méi)有下一個(gè)圖片加載器
      sourceIdIndex++;
      if (sourceIdIndex >= cacheKeys.size()) {
        return false;
      }
      //獲取key
      Key sourceId = cacheKeys.get(sourceIdIndex);
      // PMD.AvoidInstantiatingObjectsInLoops The loop iterates a limited number of times
      // and the actions it performs are much more expensive than a single allocation.
      @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
      //創(chuàng)建原始圖片磁盤(pán)緩存key
      Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
      //獲取磁盤(pán)緩存文件
      cacheFile = helper.getDiskCache().get(originalKey);
      if (cacheFile != null) {
        this.sourceKey = sourceId;
        //獲取磁盤(pán)緩存圖片加載器列表
        modelLoaders = helper.getModelLoaders(cacheFile);
        modelLoaderIndex = 0;
      }
    }

    loadData = null;
    boolean started = false;
    while (!started && hasNextModelLoader()) {
      //遍歷圖片加載器
      ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
      //構(gòu)建圖片加載對(duì)象
      loadData =
          modelLoader.buildLoadData(cacheFile, helper.getWidth(), helper.getHeight(),
              helper.getOptions());
      if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
        started = true;
        //加載圖片
        loadData.fetcher.loadData(helper.getPriority(), this);
      }
    }
    return started;
  }

與ResourceCacheGenerator一樣,開(kāi)始并不存在磁盤(pán)緩存交汤,所以會(huì)走到從網(wǎng)絡(luò)加載的步驟雏赦,至于磁盤(pán)緩存怎么加載圖片劫笙,請(qǐng)查看Glide解析七:磁盤(pán)緩存策略

7.2.3、從網(wǎng)絡(luò)中加載圖片

步驟7.2知道從網(wǎng)絡(luò)加載圖片使用的執(zhí)行器是SourceGenerator星岗,我們看下SourceGenerator的startNext:

public boolean startNext() {
    if (dataToCache != null) {
      //dataToCache是網(wǎng)絡(luò)圖片流填大,網(wǎng)絡(luò)圖片流不為空
      Object data = dataToCache;
      dataToCache = null;
      //讀取圖片緩存流,并緩存圖片到磁盤(pán)中
      cacheData(data);
    }

    if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
      //磁盤(pán)緩存執(zhí)行器不為空俏橘,則執(zhí)行磁盤(pán)緩存執(zhí)行器
      return true;
    }
    sourceCacheGenerator = null;

    loadData = null;
    boolean started = false;
    while (!started && hasNextModelLoader()) {
      //遍歷網(wǎng)絡(luò)圖片加載器
      loadData = helper.getLoadData().get(loadDataListIndex++);
      if (loadData != null
          && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
          || helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
        started = true;
        //使用網(wǎng)絡(luò)圖片加載器加載圖片
        loadData.fetcher.loadData(helper.getPriority(), this);
      }
    }
    return started;
  }

這里的網(wǎng)絡(luò)圖片加載器允华,如果不指定默認(rèn)使用的是HttpUrlFetcher,如果配置了其他的網(wǎng)絡(luò)圖片加載器寥掐,比如OkHttpStreamFetcher靴寂,則使用配置的網(wǎng)絡(luò)加載器,我們這里看HttpUrlFetcher的loadData是怎么加載圖片的:

public void loadData(@NonNull Priority priority,
      @NonNull DataCallback<? super InputStream> callback) {
    long startTime = LogTime.getLogTime();
    try {
      //獲取網(wǎng)絡(luò)圖片輸入流
      InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
      //回調(diào)通知網(wǎng)絡(luò)圖片輸入流已經(jīng)就緒
      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));
      }
    }
  }

//發(fā)起http請(qǐng)求獲取圖片輸入流
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.
      }
    }
    //構(gòu)建http請(qǐng)求連接
    urlConnection = connectionFactory.build(url);
    for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
      //添加請(qǐng)求頭
      urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
    }
    //設(shè)置連接召耘、讀超時(shí)時(shí)間
    urlConnection.setConnectTimeout(timeout);
    urlConnection.setReadTimeout(timeout);
    //禁用緩存
    urlConnection.setUseCaches(false);
    //使用post請(qǐng)求方式
    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請(qǐng)求成功
      return getStreamForSuccessfulRequest(urlConnection);
    } else if (isHttpRedirect(statusCode)) {
      //重定向重新發(fā)起請(qǐng)求
      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) {
      //網(wǎng)絡(luò)請(qǐng)求失敗百炬,拋出異常
      throw new HttpException(statusCode);
    } else {
      //網(wǎng)絡(luò)請(qǐng)求失敗,拋出異常
      throw new HttpException(urlConnection.getResponseMessage(), statusCode);
    }
  }

從網(wǎng)絡(luò)加載圖片污它,主要是根據(jù)給定的url收壕,發(fā)起http請(qǐng)求,得到網(wǎng)絡(luò)圖片輸入流轨蛤,再回調(diào)onDataReady通知網(wǎng)絡(luò)圖片輸入流已經(jīng)準(zhǔn)備就緒好蜜宪,在回調(diào)中對(duì)圖片輸入流進(jìn)行處理。這里有個(gè)問(wèn)題祥山,為什么不直接在網(wǎng)絡(luò)讀取圖片輸入流之后就對(duì)圖片進(jìn)行解析圃验、緩存等處理?這里Glide遵循了設(shè)計(jì)模式中的單一職責(zé)缝呕,網(wǎng)絡(luò)圖片加載器只負(fù)責(zé)網(wǎng)絡(luò)請(qǐng)求澳窑,具體網(wǎng)絡(luò)響應(yīng)內(nèi)容要怎么處理,是其他模塊的職責(zé)供常。
接下來(lái)我們看下SourceGenerator的onDataReady是怎么處理網(wǎng)絡(luò)圖片輸入流的:

public void onDataReady(Object data) {
    DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
    if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
      //需要緩存到磁盤(pán)
      //保存網(wǎng)絡(luò)圖片輸入流到dataToCache 
      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.
      //重新規(guī)劃圖片加載
      cb.reschedule();
    } else {
      //不需要緩存到磁盤(pán)摊聋,直接回到圖片已經(jīng)獲取就緒
      cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
          loadData.fetcher.getDataSource(), originalKey);
    }
  }

一般情況下默認(rèn)是緩存到磁盤(pán)的,所有我們看cb.reschedule的源碼栈暇,這個(gè)cb就是DecodeJob:

public void reschedule() {
    //設(shè)置當(dāng)前的狀態(tài)為轉(zhuǎn)為網(wǎng)絡(luò)圖片流就緒狀態(tài)
    runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
    callback.reschedule(this);
  }

DecodeJob的reschedule接著有回到Engine的reschedule麻裁,我們看下Engine的reschedule:

public void reschedule(DecodeJob<?> job) {
    // Even if the job is cancelled here, it still needs to be scheduled so that it can clean itself
    // up.
    //在ActiveSourceExecutor線(xiàn)程池中運(yùn)行DecodeJob線(xiàn)程
    getActiveSourceExecutor().execute(job);
  }

接著又進(jìn)入了DecodeJob的run方法,根據(jù)前面對(duì)DecodeJob run方法的分析源祈,它會(huì)進(jìn)入runWrapped的SWITCH_TO_SOURCE_SERVICE分支煎源,SWITCH_TO_SOURCE_SERVICE分支直接調(diào)用runGenerators,而當(dāng)前的圖片執(zhí)行器還是網(wǎng)絡(luò)執(zhí)行器SourceGenerator香缺,接著又執(zhí)行SourceGenerator的startNext方法:

public boolean startNext() {
    if (dataToCache != null) {
      //dataToCache就是網(wǎng)絡(luò)請(qǐng)求的網(wǎng)絡(luò)圖片輸入流
      Object data = dataToCache;
      dataToCache = null;
      //緩存網(wǎng)絡(luò)圖片輸入流到磁盤(pán)中
      cacheData(data);
    }
    //執(zhí)行原始圖片磁盤(pán)緩存輸入流執(zhí)行器
    if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
      return true;
    }
    //...
}

繞了一大圈又回到SourceGenerator的startNext手销,在startNext中調(diào)用cacheData對(duì)網(wǎng)絡(luò)圖片輸入流進(jìn)行緩存:

private void cacheData(Object dataToCache) {
    long startTime = LogTime.getLogTime();
    try {
      //獲取圖片編碼器
      Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);
      //創(chuàng)建圖片數(shù)據(jù)緩存讀寫(xiě)器
      DataCacheWriter<Object> writer =
          new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
      //創(chuàng)建原始圖片緩存key
      originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
      //緩存到磁盤(pán)中
      helper.getDiskCache().put(originalKey, writer);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Finished encoding source to cache"
            + ", key: " + originalKey
            + ", data: " + dataToCache
            + ", encoder: " + encoder
            + ", duration: " + LogTime.getElapsedMillis(startTime));
      }
    } finally {
      loadData.fetcher.cleanup();
    }
    //創(chuàng)建原始圖片磁盤(pán)緩存執(zhí)行器
    sourceCacheGenerator =
        new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
  }

具體磁盤(pán)是怎么緩存的請(qǐng)查看Glide解析七:磁盤(pán)緩存策略
我們繼續(xù)往下看DataCacheGenerator的startNext,前面我們分析過(guò)DataCacheGenerator的startNext图张,其最終根據(jù)key的磁盤(pán)緩存的文件锋拖,再通過(guò)圖片加載取讀取加載圖片诈悍,使用的圖片加載器就是FileLoader,而FileLoader的fetcher對(duì)應(yīng)的是FileFetcher兽埃,然后使用FileFetcher的loadData加載圖片侥钳,我們看下FileFetcher的loadData方法:

 public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super Data> callback) {
      try {
        data = opener.open(file);
      } catch (FileNotFoundException e) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
          Log.d(TAG, "Failed to open file", e);
        }
        callback.onLoadFailed(e);
        return;
      }
      callback.onDataReady(data);
    }

這段代碼使用了opener的open方法,opener由兩個(gè)工廠類(lèi)創(chuàng)建的讲仰,一個(gè)是FileLoader的靜態(tài)內(nèi)部類(lèi)StreamFactory慕趴,另一個(gè)是FileLoader的靜態(tài)內(nèi)部類(lèi)FileDescriptorFactory痪蝇,我們用StreamFactory來(lái)分析:

public static class StreamFactory extends Factory<InputStream> {
    public StreamFactory() {
      super(new FileOpener<InputStream>() {
        @Override
        public InputStream open(File file) throws FileNotFoundException {
          //open方法直接就是創(chuàng)建一個(gè)圖片文件輸入流鄙陡,并返回
          return new FileInputStream(file);
        }

        @Override
        public void close(InputStream inputStream) throws IOException {
          inputStream.close();
        }

        @Override
        public Class<InputStream> getDataClass() {
          return InputStream.class;
        }
      });
    }
  }

由此可知,F(xiàn)ildeLoader圖片加載器使用FileFetcher的loadData加載圖片主要是獲得圖片的文件輸入流躏啰,并回調(diào)callback的onDataReady方法趁矾,根據(jù)前面的分析流程此callback就是DataCacheGenerator,我們看DataCacheGenerator的onDataReady方法:

public void onDataReady(Object data) {
    cb.onDataFetcherReady(sourceKey, data, loadData.fetcher, DataSource.DATA_DISK_CACHE, sourceKey);
  }

DataCacheGenerator的onDataReady又回調(diào)SourceGenerator的onDataFetcherReady给僵,我們看下SourceGenerator的onDataFetcherReady:

public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
      DataSource dataSource, Key attemptedKey) {
    // This data fetcher will be loading from a File and provide the wrong data source, so override
    // with the data source of the original fetcher
    cb.onDataFetcherReady(sourceKey, data, fetcher, loadData.fetcher.getDataSource(), sourceKey);
  }

SourceGenerator的onDataFetcherReady又回調(diào)DecodeJob的onDataFetcherReady毫捣,我們接著看DecodeJob的onDataFetcherReady:

public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
      DataSource dataSource, Key attemptedKey) {
    //保存key
    this.currentSourceKey = sourceKey;
   //保存當(dāng)前圖片文件輸入流
    this.currentData = data;
   //保存當(dāng)前圖片加載器使用的圖片獲取實(shí)現(xiàn)類(lèi)FileFetcher
    this.currentFetcher = fetcher;
   //圖片來(lái)源,此處值為DataSource.DATA_DISK_CACHE帝际,來(lái)自磁盤(pán)緩存
    this.currentDataSource = dataSource;
    this.currentAttemptingKey = attemptedKey;
    if (Thread.currentThread() != currentThread) {
      //如果不等于當(dāng)前線(xiàn)程
      //設(shè)置當(dāng)前運(yùn)行狀態(tài)為圖片解碼
      runReason = RunReason.DECODE_DATA;
      //重新規(guī)劃圖片加載流程
      callback.reschedule(this);
    } else {
      GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData");
      try {
        //從圖片輸入流中對(duì)圖片進(jìn)行解碼
        decodeFromRetrievedData();
      } finally {
        GlideTrace.endSection();
      }
    }
  }

進(jìn)入DecodeJob的onDataFetcherReady蔓同,如果當(dāng)前運(yùn)行線(xiàn)程不等于當(dāng)前線(xiàn)程,那么重新規(guī)劃圖片加載流程蹲诀,接著進(jìn)入DecodeJob的runWrapper方法的DECODE_DATA分支斑粱,DEOCDE_DATA分支也是調(diào)用decodeFromRetrievedData,所以我們接著看decodeFromRetrievedData是怎么對(duì)圖片文件輸入流進(jìn)行處理的:

private void decodeFromRetrievedData() {
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      logWithTimeAndKey("Retrieved data", startFetchTime,
          "data: " + currentData
              + ", cache key: " + currentSourceKey
              + ", fetcher: " + currentFetcher);
    }
    Resource<R> resource = null;
    try {
      //調(diào)用decodeFromData進(jìn)行圖片解碼脯爪,并返回圖片資源對(duì)象
      resource = decodeFromData(currentFetcher, currentData, currentDataSource);
    } catch (GlideException e) {
      e.setLoggingDetails(currentAttemptingKey, currentDataSource);
      throwables.add(e);
    }
    if (resource != null) {
      //如果圖片資源對(duì)象不為空则北,則根據(jù)解析結(jié)果狀態(tài)進(jìn)行相應(yīng)的操作
      notifyEncodeAndRelease(resource, currentDataSource);
    } else {
      //如果圖片資源對(duì)象為空,代表圖片加載失敗
      runGenerators();
    }
  }

這里通過(guò)decodeFromData方法對(duì)圖片文件輸入流進(jìn)行圖片解析處理痕慢,并返回圖片資源對(duì)象尚揣,通過(guò)notifyEncodeAndRelease通知圖片加載成功或者失敗,并釋放相關(guān)的資源掖举。具體是怎么對(duì)圖片文件輸入流進(jìn)行解析處理的請(qǐng)查看Glide解析八:圖片解析處理
我們接著看notifyEncodeAndRelease方法的實(shí)現(xiàn):

private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {
   //...
    notifyComplete(result, dataSource);
  //...
  }

notifyEncodeAndRelease有調(diào)用notifyComplete方法:

private void notifyComplete(Resource<R> resource, DataSource dataSource) {
    setNotifiedOrThrow();
    callback.onResourceReady(resource, dataSource);
  }

notifyComplete回調(diào)callback的onResourceReady通知圖片資源已經(jīng)加載完成就緒好快骗,這個(gè)callback就是EngineJob,我們繼續(xù)看EngineJob的onResourceReady實(shí)現(xiàn):

public void onResourceReady(Resource<R> resource, DataSource dataSource) {
    //保存圖片資源對(duì)象到resource成員變量中
    this.resource = resource;
    //數(shù)據(jù)來(lái)源保存到dataSource成員變量中
    this.dataSource = dataSource;
    //發(fā)送MSG_COMPLETE切換到主線(xiàn)程處理
    MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
  }

接著我們看MAIN_THREAD_HANDLER的MSG_COMPLETE分支:

case MSG_COMPLETE:
          job.handleResultOnMainThread();
          break;

MSG_COMPLETE分支有調(diào)用EngineJob的handleResultOnMainThread方法:

void handleResultOnMainThread() {
    stateVerifier.throwIfRecycled();
    if (isCancelled) {
      //已取消圖片加載
      //回收?qǐng)D片資源
      resource.recycle();
      //釋放相關(guān)資源
      release(false /*isRemovedFromQueue*/);
      return;
    } else if (cbs.isEmpty()) {
      //回調(diào)列表為空塔次,拋出異常
      throw new IllegalStateException("Received a resource without any callbacks to notify");
    } else if (hasResource) {
      throw new IllegalStateException("Already have resource");
    }
    //將圖片資源構(gòu)成成EngineResource對(duì)象
    engineResource = engineResourceFactory.build(resource, isCacheable);
    hasResource = true;
    
    // Hold on to resource for duration of request so we don't recycle it in the middle of
    // notifying if it synchronously released by one of the callbacks.
    //通知圖片加載任務(wù)執(zhí)行完成
    engineResource.acquire();
    listener.onEngineJobComplete(this, key, engineResource);

    //noinspection ForLoopReplaceableByForEach to improve perf
    for (int i = 0, size = cbs.size(); i < size; i++) {
      ResourceCallback cb = cbs.get(i);
      if (!isInIgnoredCallbacks(cb)) {
        //調(diào)用回調(diào)列表滨巴,通知圖片資源加載完成且已準(zhǔn)備就緒
        engineResource.acquire();
        cb.onResourceReady(engineResource, dataSource);
      }
    }
    // Our request is complete, so we can release the resource.
    //釋放資源
    engineResource.release();

    release(false /*isRemovedFromQueue*/);
  }

我們的重點(diǎn)在cb的onResourceReady回調(diào),而cb是Engine的load方法參數(shù)俺叭,在Engine load方法中創(chuàng)建EngineJob時(shí)將回調(diào)參數(shù)添加到回調(diào)列表中恭取,前面分析的時(shí)候通過(guò)SingleRequest調(diào)用Engine的load方法并將callback傳給Engine的load方法的,所以最終這個(gè)callback是SingleRequest熄守。我們看下SingleRequest的onResourceReady做什么處理的:

public void onResourceReady(Resource<?> resource, DataSource dataSource) {
    stateVerifier.throwIfRecycled();
    loadStatus = null;
    if (resource == null) {
      GlideException exception = new GlideException("Expected to receive a Resource<R> with an "
          + "object of " + transcodeClass + " inside, but instead got null.");
      //如果圖片資源為空蜈垮,通知加載失敗
      onLoadFailed(exception);
      return;
    }

    Object received = resource.get();
    if (received == null || !transcodeClass.isAssignableFrom(received.getClass())) {
      //請(qǐng)求轉(zhuǎn)換的圖片類(lèi)型與實(shí)際解析得到的圖片類(lèi)型不匹配耗跛,則通知圖片加載失敗
      releaseResource(resource);
      GlideException exception = new GlideException("Expected to receive an object of "
          + transcodeClass + " but instead" + " got "
          + (received != null ? received.getClass() : "") + "{" + received + "} inside" + " "
          + "Resource{" + resource + "}."
          + (received != null ? "" : " " + "To indicate failure return a null Resource "
          + "object, rather than a Resource object containing null data."));
      onLoadFailed(exception);
      return;
    }

    if (!canSetResource()) {
      //不允許設(shè)置圖片資源的view,釋放相關(guān)的資源
      releaseResource(resource);
      // We can't put the status to complete before asking canSetResource().
      status = Status.COMPLETE;
      return;
    }
    //調(diào)用onResourceReady的重載方法
    onResourceReady((Resource<R>) resource, (R) received, dataSource);
  }

SingleRequest的onResourceReady中又接著調(diào)用onResourceReady的重載方法:

private void onResourceReady(Resource<R> resource, R result, DataSource dataSource) {
    // We must call isFirstReadyResource before setting status.
    boolean isFirstResource = isFirstReadyResource();
    //設(shè)置狀態(tài)為加載圖片完成
    status = Status.COMPLETE;
    this.resource = resource;

    if (glideContext.getLogLevel() <= Log.DEBUG) {
      Log.d(GLIDE_TAG, "Finished loading " + result.getClass().getSimpleName() + " from "
          + dataSource + " for " + model + " with size [" + width + "x" + height + "] in "
          + LogTime.getElapsedMillis(startTime) + " ms");
    }

    isCallingCallbacks = true;
    try {
      boolean anyListenerHandledUpdatingTarget = false;
      if (requestListeners != null) {
        for (RequestListener<R> listener : requestListeners) {
          //通知圖片加載請(qǐng)求監(jiān)聽(tīng)列表圖片加載完成
          anyListenerHandledUpdatingTarget |=
              listener.onResourceReady(result, model, target, dataSource, isFirstResource);
        }
      }
      //通知targetListener圖片加載完成
      anyListenerHandledUpdatingTarget |=
          targetListener != null
              && targetListener.onResourceReady(result, model, target, dataSource, isFirstResource);

      if (!anyListenerHandledUpdatingTarget) {
        Transition<? super R> animation =
            animationFactory.build(dataSource, isFirstResource);
        //通知target(imageview的包裹類(lèi))圖片加載完成
        target.onResourceReady(result, animation);
      }
    } finally {
      isCallingCallbacks = false;
    }
    //通知圖片加載成功
    notifyLoadSuccess();
  }

當(dāng)圖片加載完成時(shí)攒发,會(huì)先調(diào)用RequestListener列表通知圖片加載完成调塌,接著回調(diào)TargetListener通知圖片加載完成,最后回調(diào)Target通知圖片加載完成惠猿。在前面分析的時(shí)候Target的具體實(shí)現(xiàn)類(lèi)是DrawableImageViewTarget或者BitmapImageViewTarget羔砾,默認(rèn)是DrawableImageViewTarget,這兩兩者繼承自ImageViewTarget偶妖。我們進(jìn)入ImageViewTarget看下其onResourceReady的實(shí)現(xiàn):

public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
    if (transition == null || !transition.transition(resource, this)) {
      //沒(méi)有動(dòng)畫(huà)
      setResourceInternal(resource);
    } else {
      //有動(dòng)畫(huà)
      maybeUpdateAnimatable(resource);
    }
  }

我們看沒(méi)有動(dòng)畫(huà)的實(shí)現(xiàn)是怎么樣的:

private void setResourceInternal(@Nullable Z resource) {
    // Order matters here. Set the resource first to make sure that the Drawable has a valid and
    // non-null Callback before starting it.
    setResource(resource);
    maybeUpdateAnimatable(resource);
  }

調(diào)用的是setResource方法設(shè)置圖片資源姜凄,而setResource是個(gè)抽象方法,具體實(shí)現(xiàn)是在具體實(shí)現(xiàn)類(lèi)DrawableImageViewTarget和BitmapImageViewTarget實(shí)現(xiàn)趾访,我們看下DrawableImageViewTarget的setResource的實(shí)現(xiàn):

protected void setResource(@Nullable Drawable resource) {
    view.setImageDrawable(resource);
  }

最終調(diào)用ImageView來(lái)設(shè)置圖片态秧,到這里Glide圖片加載的整體流程就分析講解完,期間涉及到很多點(diǎn)扼鞋,并沒(méi)有展開(kāi)細(xì)說(shuō)申鱼,而是規(guī)劃拆分為各自的點(diǎn)進(jìn)行講解。
我們來(lái)概括性的梳理下Glide的加載流程:

  • 先通過(guò)APT在編譯器動(dòng)態(tài)生成GlideApp
  • 運(yùn)行期通過(guò)GlideApp的with作為入口返回RequestManager
  • 使用RequestManager創(chuàng)建一個(gè)圖片加載請(qǐng)求構(gòu)造器RequestBuilder
  • 通過(guò)RequestBuilder設(shè)置圖片加載和解析時(shí)的一些配置信息:比如playholder云头、error捐友、apply、transform溃槐、listener等
  • 通過(guò)RequestBuilder的into構(gòu)建具體圖片加載請(qǐng)求對(duì)象默認(rèn)是SingleRequest
  • SingleRequest等待ImageView測(cè)量得到具體大小之后匣砖,在通過(guò)圖片加載引起Engine來(lái)加載圖片
  • Engine圖片加載引擎根據(jù)四級(jí)緩存策略:優(yōu)先從獲取資源緩存列表加載;接著從緩存列表接著竿痰;在從磁盤(pán)緩存加載脆粥;最后是從網(wǎng)絡(luò)加載
  • 最后加載完成先回調(diào)RequestListener通知圖片加載完成;再回調(diào)TargetListener通知圖片加載完成影涉;最后才回調(diào)Target(ImageView的包裹類(lèi))將圖片設(shè)置給ImageView

從Glide的整體流程分析下來(lái)变隔,還是挺復(fù)雜的,為什么Glide要這樣設(shè)計(jì)(或者說(shuō)Glide這樣的框架設(shè)計(jì)有什么巧妙之處)蟹倾?
以我個(gè)人的見(jiàn)解是Glide的這樣的框架設(shè)計(jì)有這樣的巧妙之處:

  • 使用方便:使用了鏈?zhǔn)较辉怠㈨憫?yīng)式的思想,給使用者帶來(lái)了使用上的便捷
  • 封裝安全性:動(dòng)態(tài)生成GlideApp間接訪(fǎng)問(wèn)Glide
  • 模塊鲜棠、職責(zé)劃分清晰:使用了各種設(shè)計(jì)模式(單例肌厨、工廠、觀察者豁陆、構(gòu)建柑爸、適配器、狀態(tài)等)使模塊和相應(yīng)的職責(zé)劃分比較清晰盒音、耦合度也比較低
  • 擴(kuò)展性強(qiáng):Glide的擴(kuò)展性設(shè)計(jì)的還是很強(qiáng)的表鳍,這也是Glide的一個(gè)突出的特點(diǎn)馅而,它提供很多可擴(kuò)展的功能給開(kāi)發(fā)者,比如:自定義圖片加載器譬圣、自定義圖片解析處理瓮恭、設(shè)置緩存大小、設(shè)置緩存路徑等等

Glide有哪些亮點(diǎn)厘熟,為什么很多人喜歡用屯蹦?

  • 使用方便
  • 擴(kuò)展性強(qiáng)
  • 四級(jí)緩存策略,提供效率和內(nèi)存利用率
  • 自動(dòng)感應(yīng)生命周期
  • 高性能:運(yùn)用了各種緩存绳姨、復(fù)用策略(LruCache登澜、Pool等),減少對(duì)象的頻繁回收和創(chuàng)建就缆,避免內(nèi)存抖動(dòng)帖渠、對(duì)象頻繁回收導(dǎo)致頻繁GC引起的卡頓
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末谒亦,一起剝皮案震驚了整個(gè)濱河市竭宰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌份招,老刑警劉巖切揭,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異锁摔,居然都是意外死亡廓旬,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén)谐腰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)孕豹,“玉大人,你說(shuō)我怎么就攤上這事十气±常” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵砸西,是天一觀的道長(zhǎng)叶眉。 經(jīng)常有香客問(wèn)我,道長(zhǎng)芹枷,這世上最難降的妖魔是什么衅疙? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮鸳慈,結(jié)果婚禮上饱溢,老公的妹妹穿的比我還像新娘。我一直安慰自己走芋,他們只是感情好绩郎,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布絮识。 她就那樣靜靜地躺著,像睡著了一般嗽上。 火紅的嫁衣襯著肌膚如雪次舌。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,764評(píng)論 1 290
  • 那天兽愤,我揣著相機(jī)與錄音彼念,去河邊找鬼。 笑死浅萧,一個(gè)胖子當(dāng)著我的面吹牛逐沙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播洼畅,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼吩案,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了帝簇?” 一聲冷哼從身側(cè)響起徘郭,我...
    開(kāi)封第一講書(shū)人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎丧肴,沒(méi)想到半個(gè)月后残揉,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡芋浮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年抱环,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纸巷。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡镇草,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出瘤旨,到底是詐尸還是另有隱情梯啤,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布裆站,位于F島的核電站条辟,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏宏胯。R本人自食惡果不足惜羽嫡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望肩袍。 院中可真熱鬧杭棵,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至滓侍,卻和暖如春蒋川,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背撩笆。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工捺球, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人夕冲。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓氮兵,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親歹鱼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子泣栈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容