Glide4.5源碼分析一

這篇摸清一個(gè)問(wèn)題:Glide從網(wǎng)上下載圖片的流程.
Glide4.0加入了Generated API,如此現(xiàn)在有兩種基本加載方式:
Glide.with(context).load(url) .into(imageView);
GlideApp.with(context).load(url) .into(imageView);
而GlideApp.with(context) 其實(shí)也是調(diào)用的Glide.with(context)方法,
Glide.with(context)的目的就是為了獲取一個(gè)RequestManager,下面看看獲取RequestManager的過(guò)程:
Glide.java中

  public static RequestManager with(@NonNull Context context) {
    return getRetriever(context).get(context);
  }
@NonNull
  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();
  }
 /**
   * Get the singleton.
   *
   * @return the singleton
   */
  @NonNull
  public static Glide get(@NonNull Context context) {
    if (glide == null) {
      synchronized (Glide.class) {
        if (glide == null) {
          checkAndInitializeGlide(context);//創(chuàng)建glide實(shí)例開(kāi)始
        }
      }
    }

    return glide;
  }

glide實(shí)例真正創(chuàng)建的方法:

@SuppressWarnings("deprecation")
  private static void initializeGlide(@NonNull Context context, @NonNull GlideBuilder builder) {
    Context applicationContext = context.getApplicationContext();
    GeneratedAppGlideModule annotationGeneratedModule = getAnnotationGeneratedGlideModules();
  //省略一些代碼
    RequestManagerRetriever.RequestManagerFactory factory =
        annotationGeneratedModule != null
            ? annotationGeneratedModule.getRequestManagerFactory() : null;
    builder.setRequestManagerFactory(factory);
   
    if (annotationGeneratedModule != null) {
      annotationGeneratedModule.applyOptions(applicationContext, builder);
    }
    Glide glide = builder.build(applicationContext);//關(guān)鍵點(diǎn),GlideBuilder創(chuàng)建glide實(shí)例
   
    if (annotationGeneratedModule != null) {
      annotationGeneratedModule.registerComponents(applicationContext, glide, glide.registry);
    }
    applicationContext.registerComponentCallbacks(glide);
    Glide.glide = glide;
  }

再來(lái)看GlideBuilder build glide過(guò)程:

@NonNull
  public Glide build(@NonNull Context context) {
    if (sourceExecutor == null) {
      sourceExecutor = GlideExecutor.newSourceExecutor();//網(wǎng)絡(luò)加載線程池
    }

    if (diskCacheExecutor == null) {
      diskCacheExecutor = GlideExecutor.newDiskCacheExecutor();//磁盤(pán)緩存線程池
    }

    if (animationExecutor == null) {
      animationExecutor = GlideExecutor.newAnimationExecutor();
    }

    if (memorySizeCalculator == null) {
      memorySizeCalculator = new MemorySizeCalculator.Builder(context).build();  
    }

    if (connectivityMonitorFactory == null) {
      connectivityMonitorFactory = new DefaultConnectivityMonitorFactory();
    }

    if (bitmapPool == null) {
     //在memorySizeCalculator中size被計(jì)算,如果當(dāng)前設(shè)備isLowMemoryDevice并且
     //Build.VERSION.SDK_INT >= Build.VERSION_CODES.O,則不會(huì)使用圖片緩存
     // On Android O+ Bitmaps are allocated natively, ART is much more efficient at managing
      // garbage and we rely heavily on HARDWARE Bitmaps, making Bitmap re-use much less important.
      // We prefer to preserve RAM on these devices and take the small performance hit of not
      // re-using Bitmaps and textures when loading very small images or generating thumbnails.
      int size = memorySizeCalculator.getBitmapPoolSize();
      if (size > 0) {
        bitmapPool = new LruBitmapPool(size);
      } else {
        bitmapPool = new BitmapPoolAdapter();//get永遠(yuǎn)返回null,只是為了適配BitmapPool的設(shè)計(jì),利于維護(hù)
      }
    }

    if (arrayPool == null) {
      arrayPool = new LruArrayPool(memorySizeCalculator.getArrayPoolSizeInBytes());
    }

    if (memoryCache == null) {
      memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
    }

    if (diskCacheFactory == null) {
      diskCacheFactory = new InternalCacheDiskCacheFactory(context);
    }
    //創(chuàng)建engine對(duì)象,它將是圖片請(qǐng)求的發(fā)起者
    if (engine == null) {
      engine =
          new Engine(
              memoryCache,
              diskCacheFactory,
              diskCacheExecutor,
              sourceExecutor,
              GlideExecutor.newUnlimitedSourceExecutor(),
              GlideExecutor.newAnimationExecutor(),
              isActiveResourceRetentionAllowed);
    }

    RequestManagerRetriever requestManagerRetriever =
        new RequestManagerRetriever(requestManagerFactory);

    return new Glide(
        context,
        engine,
        memoryCache,
        bitmapPool,
        arrayPool,
        requestManagerRetriever,
        connectivityMonitorFactory,
        logLevel,
        defaultRequestOptions.lock(),
        defaultTransitionOptions);
  }

關(guān)心主流程,到這里,glide對(duì)象被創(chuàng)建,Glide.with()是獲取RequestManager的過(guò)程,開(kāi)始獲取在下面方法中:

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

通過(guò) get((FragmentActivity) context);和get((Activity) context);真正獲取RequestManager在supportFragmentGet(),fragmentGet()兩個(gè)方法,兩個(gè)方法功能一致,分析其中一個(gè):

@NonNull
  private RequestManager supportFragmentGet(@NonNull Context context, @NonNull FragmentManager fm,
      @Nullable Fragment parentHint) {
    //創(chuàng)建一個(gè)隱藏fragment用于管理Glide加載生命周期
    SupportRequestManagerFragment current = getSupportRequestManagerFragment(fm, parentHint);
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
      // TODO(b/27524013): Factor out this Glide.get() call.
      Glide glide = Glide.get(context);
      requestManager =
        //創(chuàng)建RequestManager
          factory.build(
              glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);//
      //將requestManager保存在創(chuàng)建的fragment中,用于生命周期管理,先不分析.
      current.setRequestManager(requestManager);
    }
    return requestManager;
  }

這樣我們就獲取到了requestManager實(shí)例,接下來(lái)看.load(url)這一節(jié),load()方法有很多重載方法,我們找最簡(jiǎn)單的一種load(@Nullable String string):
RequestManager.java中

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

跟蹤asDrawable()方法進(jìn)入下面方法:

@NonNull
 @CheckResult
 public <ResourceType> RequestBuilder<ResourceType> as(
     @NonNull Class<ResourceType> resourceClass) {
//ResourceType類(lèi)型為Drawable
   return new RequestBuilder<>(glide, this, resourceClass, context);//創(chuàng)建一個(gè)RequestBuilder實(shí)例.
 }

于是asDrawable().load(string);變成調(diào)用RequestBuilder中l(wèi)oad(string):

* @param string A file path, or a uri or url handled by
   * {@link com.bumptech.glide.load.model.UriLoader}.
   */
  @NonNull
  @Override
  @CheckResult
  public RequestBuilder<TranscodeType> load(@Nullable String string) {
    return loadGeneric(string);
  }
@NonNull
  private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
    this.model = model;//現(xiàn)在這里是String類(lèi)型
    isModelSet = true;
    return this;
  }

到這里load()結(jié)束,進(jìn)入into(),真正加載開(kāi)始

 @NonNull
  public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
    Util.assertMainThread();
    Preconditions.checkNotNull(view);

    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.
      }
    }
    //重點(diǎn),glideContext是在Glide構(gòu)造方法中創(chuàng)建的,GlideContext類(lèi)是一個(gè)全局的為所有在Glide中
    //加載任務(wù)保存和提供各種registries和classes的類(lèi),就是一個(gè)類(lèi)似工具類(lèi)的存在
    //transcodeClass:這里我們傳入的是Drawable (在RequestManager中的as()方法傳入)
    return into(
        glideContext.buildImageViewTarget(view, transcodeClass),
        /*targetListener=*/ null,
        requestOptions);
  }

跟蹤glideContext.buildImageViewTarget()進(jìn)入ImageViewTargetFactory中,一個(gè)生產(chǎn)為不同View生產(chǎn)正確ViewTarget的工廠:

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);//我們是
      //Drawable所以返回的是DrawableImageViewTarget
    } else {
      throw new IllegalArgumentException(
          "Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)");
    }
  }

ViewTarget創(chuàng)建后我們來(lái)進(jìn)入上面into方法:

 private <Y extends Target<TranscodeType>> Y into(
      @NonNull Y target,
      @Nullable RequestListener<TranscodeType> targetListener,
      @NonNull RequestOptions options) {
    Util.assertMainThread();
    Preconditions.checkNotNull(target);
    if (!isModelSet) {
      throw new IllegalArgumentException("You must call #load() before calling #into()");
    }

    options = options.autoClone();
    Request request = buildRequest(target, targetListener, options);//重點(diǎn),創(chuàng)建加載圖片請(qǐng)求

    Request previous = target.getRequest();
    if (request.isEquivalentTo(previous)
        && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
      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;
    }

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

    return target;
  }

接著跟蹤buildRequest(target, targetListener, options)方法進(jìn)入:

private Request buildRequestRecursive(
      Target<TranscodeType> target,
      @Nullable RequestListener<TranscodeType> targetListener,
      @Nullable RequestCoordinator parentCoordinator,
      TransitionOptions<?, ? super TranscodeType> transitionOptions,
      Priority priority,
      int overrideWidth,
      int overrideHeight,
      RequestOptions requestOptions) {

    // Build the ErrorRequestCoordinator first if necessary so we can update parentCoordinator.
    ErrorRequestCoordinator errorRequestCoordinator = null;
    //這里如果調(diào)用了RequestBuilder error()將會(huì)進(jìn)行錯(cuò)誤處理
    if (errorBuilder != null) {
      errorRequestCoordinator = new ErrorRequestCoordinator(parentCoordinator);
      parentCoordinator = errorRequestCoordinator;
    }
    //重點(diǎn)
    Request mainRequest =
        buildThumbnailRequestRecursive(
            target,
            targetListener,
            parentCoordinator,
            transitionOptions,
            priority,
            overrideWidth,
            overrideHeight,
            requestOptions);

    if (errorRequestCoordinator == null) {
      return mainRequest;
    }

    int errorOverrideWidth = errorBuilder.requestOptions.getOverrideWidth();
    int errorOverrideHeight = errorBuilder.requestOptions.getOverrideHeight();
    if (Util.isValidDimensions(overrideWidth, overrideHeight)
        && !errorBuilder.requestOptions.isValidOverride()) {
      errorOverrideWidth = requestOptions.getOverrideWidth();
      errorOverrideHeight = requestOptions.getOverrideHeight();
    }

    Request errorRequest = errorBuilder.buildRequestRecursive(
        target,
        targetListener,
        errorRequestCoordinator,
        errorBuilder.transitionOptions,
        errorBuilder.requestOptions.getPriority(),
        errorOverrideWidth,
        errorOverrideHeight,
        errorBuilder.requestOptions);
    errorRequestCoordinator.setRequests(mainRequest, errorRequest);
    return errorRequestCoordinator;
  }

繼續(xù)跟蹤:

private Request buildThumbnailRequestRecursive(
      Target<TranscodeType> target,
      RequestListener<TranscodeType> targetListener,
      @Nullable RequestCoordinator parentCoordinator,
      TransitionOptions<?, ? super TranscodeType> transitionOptions,
      Priority priority,
      int overrideWidth,
      int overrideHeight,
      RequestOptions requestOptions) {
      .......
     //省略縮略圖的出來(lái)代碼
      .........
      // Base case: no thumbnail.重點(diǎn)
      return obtainRequest(
          target,
          targetListener,
          requestOptions,
          parentCoordinator,
          transitionOptions,
          priority,
          overrideWidth,
          overrideHeight);
    }
  }

隨后我們進(jìn)入SingleRequest.obtain()方法這個(gè)方法真正生成一個(gè)request:SingleRequest實(shí)例,通過(guò)打斷點(diǎn)發(fā)現(xiàn)請(qǐng)求都會(huì)進(jìn)入SingleRequest的begin()方法,什么時(shí)候調(diào)用這個(gè)方法跟生命周期有關(guān),在onStart()方法執(zhí)行后,也有不是生命周期方法調(diào)用的.比如View在AttachedToWindow后,后面分析具體調(diào)用時(shí)機(jī).

@Override
  public void begin() {
    assertNotCallingCallbacks();
    stateVerifier.throwIfRecycled();
    startTime = LogTime.getLogTime();
    if (model == null) {
      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;
    }

    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) {
      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.

    status = Status.WAITING_FOR_SIZE;
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
      onSizeReady(overrideWidth, overrideHeight);//重點(diǎn)
    } else {
      target.getSize(this);
    }

    if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
        && canNotifyStatusChanged()) {
      target.onLoadStarted(getPlaceholderDrawable());
    }
    if (IS_VERBOSE_LOGGABLE) {
      logV("finished run method in " + LogTime.getElapsedMillis(startTime));
    }
  }

略過(guò)其他方法,看重點(diǎn)方法onSizeReady(int width, int height)

 /**
   * A callback method that should never be invoked directly.
   * 注意這句注釋,callback方法,不會(huì)被直接調(diào)用,說(shuō)明只有當(dāng)target的size確定后才會(huì)調(diào)用
   * 這個(gè)方法,才會(huì)真正的通過(guò)engine加載圖片
   */
  @Override
  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) {
      return;
    }
    status = Status.RUNNING;

    float sizeMultiplier = requestOptions.getSizeMultiplier();
    this.width = maybeApplySizeMultiplier(width, sizeMultiplier);
    this.height = maybeApplySizeMultiplier(height, sizeMultiplier);

    if (IS_VERBOSE_LOGGABLE) {
      logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));
    }
    // 重點(diǎn) 
    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));
    }
  }

繼續(xù)分析重點(diǎn)engine.load()

/**
  * Starts a load for the given arguments.
  *
  * <p>Must be called on the main thread.
  *
  * <p>The flow for any request is as follows:
  * <ul>
  *   <li>Check the current set of actively used resources, return the active resource if
  *   present, and move any newly inactive resources into the memory cache.</li>
  *   <li>Check the memory cache and provide the cached resource if present.</li>
  *   <li>Check the current set of in progress loads and add the cb to the in progress load if
  *   one is present.</li>
  *   <li>Start a new load.</li>
  * </ul>
  *
  * <p>Active resources are those that have been provided to at least one request and have not yet
  * been released. Once all consumers of a resource have released that resource, the resource then
  * goes to cache. If the resource is ever returned to a new consumer from cache, it is re-added to
  * the active resources. If the resource is evicted from the cache, its resources are recycled and
  * re-used if possible and the resource is discarded. There is no strict requirement that
  * consumers release their resources so active resources are held weakly.
  *
  * @param width  The target width in pixels of the desired resource.
  * @param height The target height in pixels of the desired resource.
  * @param cb     The callback that will be called when the load completes.
  */
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();
   //這個(gè)key是用來(lái)標(biāo)記每一個(gè)請(qǐng)求的
   EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
       resourceClass, transcodeClass, options);
   // 請(qǐng)求資源第一步 從active resources 中拿取
   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;
   }
   //請(qǐng)求資源第二步 從MemoryCache 中拿取
   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;
   }
  
   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);
   }
   
// active resources 和 MemoryCache中都沒(méi)有我們要請(qǐng)求的資源,創(chuàng)建一個(gè)EngineJob
   EngineJob<R> engineJob =
       engineJobFactory.build(
           key,
           isMemoryCacheable,
           useUnlimitedSourceExecutorPool,
           useAnimationPool,
           onlyRetrieveFromCache);
 // 創(chuàng)建一個(gè)DecodeJob
   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);//重點(diǎn) 添加圖片資源下載完成后的回調(diào)
   engineJob.start(decodeJob);//重點(diǎn) 開(kāi)始下載任務(wù)

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

繼續(xù)分析engine.start()方法:

public void start(DecodeJob<R> decodeJob) {
    this.decodeJob = decodeJob;
    GlideExecutor executor = decodeJob.willDecodeFromCache()
        ? diskCacheExecutor
        : getActiveSourceExecutor();//這里決定用什么線程池來(lái)執(zhí)行,從磁盤(pán)緩存中獲取使用
        //diskCacheExecutor,我們拿到的是diskCacheExecutor
    executor.execute(decodeJob);
  }
/**
   * Returns true if this job will attempt to decode a resource from the disk cache, and false if it
   * will always decode from source.
   */
  boolean willDecodeFromCache() {
    Stage firstStage = getNextStage(Stage.INITIALIZE);
    return firstStage == Stage.RESOURCE_CACHE || firstStage == Stage.DATA_CACHE;//返回true
  }

DecodeJob是一個(gè)Runnable,接下來(lái)看他的run方法:

 @Override
  public void run() {
    // This should be much more fine grained, but since Java's thread pool implementation silently
    // swallows all otherwise fatal exceptions, this will at least make it obvious to developers
    // that something is failing.
    TraceCompat.beginSection("DecodeJob#run");
    // Methods in the try statement can invalidate currentFetcher, so set a local variable here to
    // ensure that the fetcher is cleaned up either way.
    DataFetcher<?> localFetcher = currentFetcher;
    try {
      if (isCancelled) {
        notifyFailed();
        return;
      }
      runWrapped();//重點(diǎn)
    } catch (Throwable t) {
      // Catch Throwable and not Exception to handle OOMs. Throwables are swallowed by our
      // usage of .submit() in GlideExecutor so we're not silently hiding crashes by doing this. We
      // are however ensuring that our callbacks are always notified when a load fails. Without this
      // notification, uncaught throwables never notify the corresponding callbacks, which can cause
      // loads to silently hang forever, a case that's especially bad for users using Futures on
      // background threads.
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "DecodeJob threw unexpectedly"
            + ", isCancelled: " + isCancelled
            + ", stage: " + stage, t);
      }
      // When we're encoding we've already notified our callback and it isn't safe to do so again.
      if (stage != Stage.ENCODE) {
        throwables.add(t);
        notifyFailed();
      }
      if (!isCancelled) {
        throw t;
      }
    } finally {
      // Keeping track of the fetcher here and calling cleanup is excessively paranoid, we call
      // close in all cases anyway.
      if (localFetcher != null) {
        localFetcher.cleanup();
      }
      TraceCompat.endSection();
    }
  }
//Stage 是一個(gè)狀態(tài)標(biāo)記,標(biāo)記從哪里decode數(shù)據(jù)
//源碼注釋:Where we're trying to decode data from.
private void runWrapped() {
     switch (runReason) {
      case INITIALIZE://請(qǐng)求開(kāi)始是這個(gè)狀態(tài),所以第一次執(zhí)行這里
        stage = getNextStage(Stage.INITIALIZE);
        currentGenerator = getNextGenerator();//第一次拿到ResourceCacheGenerator
        runGenerators();
        break;
      case SWITCH_TO_SOURCE_SERVICE:
        runGenerators();
        break;
      case DECODE_DATA:
        decodeFromRetrievedData();
        break;
      default:
        throw new IllegalStateException("Unrecognized run reason: " + runReason);
    }
  }
//獲取下一次狀態(tài)
private Stage getNextStage(Stage current) {
    switch (current) {
      case INITIALIZE://第一次執(zhí)行這里,默認(rèn)的disk緩存策略是AUTOMATIC,所以狀態(tài)切換成RESOURCE_CACHE
        return diskCacheStrategy.decodeCachedResource()
            ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
      case RESOURCE_CACHE:
      //第二次執(zhí)行到這里,默認(rèn)AUTOMATIC策略decodeCachedData()返回true
        return diskCacheStrategy.decodeCachedData()
            ? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
      case DATA_CACHE:
        // 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:
        return Stage.FINISHED;
      default:
        throw new IllegalArgumentException("Unrecognized stage: " + current);
    }
  }
private DataFetcherGenerator getNextGenerator() {
    switch (stage) {
      case RESOURCE_CACHE:
        return new ResourceCacheGenerator(decodeHelper, this);//ResourceCache,加載本地Uri資源圖片后的緩存
      case DATA_CACHE:
        return new DataCacheGenerator(decodeHelper, this);//DataCache我的理解是網(wǎng)絡(luò)圖片disk緩存
      case SOURCE:
        return new SourceGenerator(decodeHelper, this);//網(wǎng)絡(luò)加載器
      case FINISHED:
        return null;
      default:
        throw new IllegalStateException("Unrecognized stage: " + stage);
    }
  }
private void runGenerators() {
    currentThread = Thread.currentThread();
    startFetchTime = LogTime.getLogTime();
    boolean isStarted = false;
    while (!isCancelled && currentGenerator != null
        && !(isStarted = currentGenerator.startNext())) {//關(guān)鍵點(diǎn)
      stage = getNextStage(stage);
      currentGenerator = getNextGenerator();

      if (stage == Stage.SOURCE) {
        reschedule();//關(guān)鍵點(diǎn)
        return;
      }
    }
    // We've run out of stages and generators, give up.
    if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
      notifyFailed();
    }

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

currentGenerator.startNext(),DataFetcherGenerator 有3個(gè)實(shí)現(xiàn),分別對(duì)應(yīng)ResourceCacheGenerator,DataCacheGenerator,SourceGenerator.總結(jié)下圖片在進(jìn)入disk緩存獲取的步驟,也就是DecodeJob中run()方法執(zhí)行的工作:

  1. runWrapped()方法中,runReason第一次是INITIALIZE,獲取到的stage是Stage.RESOURCE_CACHE,currentGenerator是ResourceCacheGenerator,隨后執(zhí)行runGenerators()方法.


    runWrapped()第一次執(zhí)行.png
Generator切換過(guò)程.png
  1. reschedule方法中再次執(zhí)行run()方法
 @Override
  public void reschedule() {
    runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
    callback.reschedule(this);
  }

4.最后再次運(yùn)行runGenerators()方法,進(jìn)入SourceGenerator的startNext()方法.開(kāi)始網(wǎng)絡(luò)加載

SourceGenerator的startNext()方法:

@Override
  public boolean startNext() {
    if (dataToCache != null) {
      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);//重點(diǎn)開(kāi)始加載,并且傳入回調(diào)this
      }
    }
    return started;
  }

我引入了Okhttp的網(wǎng)絡(luò)加載,所以這里的fetcher是OkHttpStreamFetcher,看他的loadData()實(shí)現(xiàn):

@Override
  public void loadData(@NonNull Priority priority,
      @NonNull final DataCallback<? super InputStream> callback) {
    Request.Builder requestBuilder = new Request.Builder().url(url.toStringUrl());
    for (Map.Entry<String, String> headerEntry : url.getHeaders().entrySet()) {
      String key = headerEntry.getKey();
      requestBuilder.addHeader(key, headerEntry.getValue());
    }
    Request request = requestBuilder.build();
    this.callback = callback;

    call = client.newCall(request);
    call.enqueue(this);
  }

圖片請(qǐng)求回調(diào)

 @Override
  public void onFailure(@NonNull Call call, @NonNull IOException e) {
    if (Log.isLoggable(TAG, Log.DEBUG)) {
      Log.d(TAG, "OkHttp failed to obtain result", e);
    }

    callback.onLoadFailed(e);
  }

  @Override
  public void onResponse(@NonNull Call call, @NonNull Response response) {
    responseBody = response.body();
    if (response.isSuccessful()) {
      long contentLength = Preconditions.checkNotNull(responseBody).contentLength();
      stream = ContentLengthInputStream.obtain(responseBody.byteStream(), contentLength);
      callback.onDataReady(stream);
    } else {
      callback.onLoadFailed(new HttpException(response.message(), response.code()));
    }
  }

這里的callback就是前面?zhèn)魅氲腟ourceGenerator,看他的onDataReady方法:

@Override
  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();//重點(diǎn)這個(gè)cb callback是DecodeJob
    } else {
      cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
          loadData.fetcher.getDataSource(), originalKey);
    }
  }

DecodeJob的reschedule():

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

EngineJob的reschedule()

@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
    // up.
    getActiveSourceExecutor().execute(job);//可以看到這里的線程池已經(jīng)切到網(wǎng)絡(luò)線程池
  }

隨后DecodeJob的run被執(zhí)行,跟蹤方法再次進(jìn)入SourceGenerator的startNext()方法

@Override
  public boolean startNext() {
    if (dataToCache != null) {
      Object data = dataToCache;
      dataToCache = null;
      cacheData(data);//重點(diǎn),前面已經(jīng)獲取到數(shù)據(jù)dataToCache不再為null
    }

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

緩存數(shù)據(jù)

private void cacheData(Object dataToCache) {
    long startTime = LogTime.getLogTime();
    try {
      Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);
      DataCacheWriter<Object> writer =
          new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
      originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
      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();
    }
  //sourceCacheGenerator不再為null
    sourceCacheGenerator =
        new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
  }

所以在上面的方法中會(huì)去執(zhí)行DataCacheGenerator的startNex()方法,和SourceGenerator類(lèi)似,隨后執(zhí)行DataCacheGenerator的對(duì)應(yīng)回調(diào)方法:

@Override
  public void onDataReady(Object data) {
//cb是SourceGenerator
    cb.onDataFetcherReady(sourceKey, data, loadData.fetcher, DataSource.DATA_DISK_CACHE, sourceKey);
  }

  @Override
  public void onLoadFailed(@NonNull Exception e) {
    cb.onDataFetcherFailed(sourceKey, e, loadData.fetcher, DataSource.DATA_DISK_CACHE);
  }

SourceGenerator

// Called from source cache generator.
  @Override
  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 是DecodeJob
    cb.onDataFetcherReady(sourceKey, data, fetcher, loadData.fetcher.getDataSource(), sourceKey);
  }

DecodeJob

@Override
  public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
      DataSource dataSource, Key attemptedKey) {
    this.currentSourceKey = sourceKey;
    this.currentData = data;
    this.currentFetcher = fetcher;
    this.currentDataSource = dataSource;
    this.currentAttemptingKey = attemptedKey;
    if (Thread.currentThread() != currentThread) {
      runReason = RunReason.DECODE_DATA;
      callback.reschedule(this);
    } else {
      TraceCompat.beginSection("DecodeJob.decodeFromRetrievedData");
      try {
        decodeFromRetrievedData();//重點(diǎn)
      } finally {
        TraceCompat.endSection();
      }
    }
  }

decodeFromRetrievedData()中將會(huì)去decode我們需要的數(shù)據(jù)

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 {
      resource = decodeFromData(currentFetcher, currentData, currentDataSource);//解析到資源
    } catch (GlideException e) {
      e.setLoggingDetails(currentAttemptingKey, currentDataSource);
      throwables.add(e);
    }
    if (resource != null) {
      notifyEncodeAndRelease(resource, currentDataSource);//重點(diǎn)
    } else {
      runGenerators();
    }
  }
private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {
    if (resource instanceof Initializable) {
      ((Initializable) resource).initialize();
    }

    Resource<R> result = resource;
    LockedResource<R> lockedResource = null;
    if (deferredEncodeManager.hasResourceToEncode()) {
      lockedResource = LockedResource.obtain(resource);
      result = lockedResource;
    }

    notifyComplete(result, dataSource);//重點(diǎn),這里數(shù)據(jù)已經(jīng)加載出來(lái)

    stage = Stage.ENCODE;
    try {
      if (deferredEncodeManager.hasResourceToEncode()) {
        deferredEncodeManager.encode(diskCacheProvider, options);
      }
    } finally {
      if (lockedResource != null) {
        lockedResource.unlock();
      }
    }
    // Call onEncodeComplete outside the finally block so that it's not called if the encode process
    // throws.
    onEncodeComplete();
  }
private void notifyComplete(Resource<R> resource, DataSource dataSource) {
    setNotifiedOrThrow();
  //callback 是EngineJob
    callback.onResourceReady(resource, dataSource);
  }

EngineJob中

@Override
  public void onResourceReady(Resource<R> resource, DataSource dataSource) {
    this.resource = resource;
    this.dataSource = dataSource;
    MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
  //重點(diǎn),這里通過(guò)主線程handler將當(dāng)前EngineJob發(fā)送到主線程消息隊(duì)列中
  }

主線程handler處理消息:

@Override
    public boolean handleMessage(Message message) {
      EngineJob<?> job = (EngineJob<?>) message.obj;
      switch (message.what) {
        case MSG_COMPLETE:
          job.handleResultOnMainThread();
          break;
        case MSG_EXCEPTION:
          job.handleExceptionOnMainThread();
          break;
        case MSG_CANCELLED:
          job.handleCancelledOnMainThread();
          break;
        default:
          throw new IllegalStateException("Unrecognized message: " + message.what);
      }
      return true;
    }

handleResultOnMainThread()

@Synthetic
  void handleResultOnMainThread() {
    stateVerifier.throwIfRecycled();
    if (isCancelled) {
      resource.recycle();
      release(false /*isRemovedFromQueue*/);
      return;
    } else if (cbs.isEmpty()) {
      throw new IllegalStateException("Received a resource without any callbacks to notify");
    } else if (hasResource) {
      throw new IllegalStateException("Already have resource");
    }
    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.
    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)) {
        engineResource.acquire();
        cb.onResourceReady(engineResource, dataSource);//重點(diǎn)
      }
    }
    // Our request is complete, so we can release the resource.
    engineResource.release();

    release(false /*isRemovedFromQueue*/);
  }

我們只關(guān)心主要方法 cb.onResourceReady(engineResource, dataSource)
cb的實(shí)現(xiàn)是最初的SingleRequest:

private void onResourceReady(Resource<R> resource, R result, DataSource dataSource) {
    // We must call isFirstReadyResource before setting status.
    boolean isFirstResource = isFirstReadyResource();
    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 {
      if ((requestListener == null
          || !requestListener.onResourceReady(result, model, target, dataSource, isFirstResource))
          && (targetListener == null
          || !targetListener.onResourceReady(result, model, target, dataSource, isFirstResource))) {
        Transition<? super R> animation =
            animationFactory.build(dataSource, isFirstResource);
        target.onResourceReady(result, animation);//重點(diǎn)
      }
    } finally {
      isCallingCallbacks = false;
    }

    notifyLoadSuccess();
  }

這里的target的實(shí)現(xiàn)是ImageViewTarget,ImageViewTarget也是一個(gè)抽象類(lèi)

 public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
    if (transition == null || !transition.transition(resource, this)) {
      setResourceInternal(resource);//重點(diǎn)
    } else {
      maybeUpdateAnimatable(resource);
    }
  }
 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);//重點(diǎn)這里是一個(gè)抽象方法,我們這里的實(shí)現(xiàn)是DrawableImageViewTarget
    maybeUpdateAnimatable(resource);
  }

DrawableImageViewTarget

@Override
  protected void setResource(@Nullable Drawable resource) {
    view.setImageDrawable(resource);//到這里view真正獲取到圖片Drawable
  }

總結(jié)一下圖片加載的主流程:

  1. Glide.with(context) 會(huì)去獲取一個(gè)RequestManager,這期間,如果Glide實(shí)例沒(méi)有,會(huì)創(chuàng)建一個(gè)全局單實(shí)例.在創(chuàng)建RequestManager時(shí),會(huì)去將RequestManager綁定到一個(gè)隱藏的RequestManagerFragment中進(jìn)行生命周期管理.
  2. load()將確定具體的加載類(lèi)型保存到一個(gè)RequestBuilder中,它也保存著其他RequestOptions
  3. into()將在RequestBuilder中創(chuàng)建一個(gè)SingRequest,隨后onSizeReady()方法被回調(diào),engine開(kāi)始發(fā)起圖片加載.
  4. Engine圖片加載分為3步,第一步從active resources 中取,active resources 是那些至少已經(jīng)被提供給一個(gè)request的并且還沒(méi)有被釋放的,第二步從MemoryCache中獲取,如果還是沒(méi)有,第三步將會(huì)創(chuàng)建一個(gè)新的請(qǐng)求(ps:這個(gè)方法注釋非常清晰,Active resources are those that have been provided to at least one request and have not yet been released. Once all consumers of a resource have released that resource, the resource then goes to cache. If the resource is ever returned to a new consumer from cache, it is re-added to the active resources. If the resource is evicted from the cache, its resources are recycled and re-used if possible and the resource is discarded. There is no strict requirement that consumers release their resources so active resources are held weakly.)
  5. 在Engine的load()中創(chuàng)建一個(gè)DecodeJob,Engine將它放入線程池中執(zhí)行,decodeJob先從ResourceCache中獲取,再?gòu)腄ataCache中,最后SourceGenerator的startNext()方法中發(fā)起網(wǎng)絡(luò)請(qǐng)求.網(wǎng)絡(luò)加載完成后先緩存下來(lái),DecodeJob再次運(yùn)行,在DataCacheGenerator的startNext()中獲取到數(shù)據(jù),經(jīng)過(guò)層層回調(diào)在DecodeJob的decodeFromRetrievedData()方法中將resource解析出來(lái),最后回調(diào)到EngineJob的onResourceReady()方法,通過(guò)主線程Handler完成線程切換并將當(dāng)前EngineJob通過(guò)Message發(fā)送到主線程的消息隊(duì)列,隨后處理,接著回調(diào)到SingRequest的onResourceReady()方法,隨后回調(diào)target的onResourceReady()方法,到這里圖片加載到對(duì)應(yīng)的target,完成.
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末末誓,一起剝皮案震驚了整個(gè)濱河市壹蔓,隨后出現(xiàn)的幾起案子虏两,更是在濱河造成了極大的恐慌乱豆,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,997評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件遂庄,死亡現(xiàn)場(chǎng)離奇詭異乒融,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)欠橘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)矩肩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人肃续,你說(shuō)我怎么就攤上這事黍檩。” “怎么了始锚?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,359評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵刽酱,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我瞧捌,道長(zhǎng)棵里,這世上最難降的妖魔是什么润文? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,309評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮殿怜,結(jié)果婚禮上典蝌,老公的妹妹穿的比我還像新娘。我一直安慰自己头谜,他們只是感情好骏掀,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著柱告,像睡著了一般截驮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上末荐,一...
    開(kāi)封第一講書(shū)人閱讀 51,258評(píng)論 1 300
  • 那天侧纯,我揣著相機(jī)與錄音,去河邊找鬼甲脏。 笑死眶熬,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的块请。 我是一名探鬼主播娜氏,決...
    沈念sama閱讀 40,122評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼墩新!你這毒婦竟也來(lái)了贸弥?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,970評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤海渊,失蹤者是張志新(化名)和其女友劉穎绵疲,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體臣疑,經(jīng)...
    沈念sama閱讀 45,403評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡盔憨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了讯沈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片郁岩。...
    茶點(diǎn)故事閱讀 39,769評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖缺狠,靈堂內(nèi)的尸體忽然破棺而出问慎,到底是詐尸還是另有隱情,我是刑警寧澤挤茄,帶...
    沈念sama閱讀 35,464評(píng)論 5 344
  • 正文 年R本政府宣布如叼,位于F島的核電站,受9級(jí)特大地震影響穷劈,放射性物質(zhì)發(fā)生泄漏薇正。R本人自食惡果不足惜片酝,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望挖腰。 院中可真熱鬧,春花似錦练湿、人聲如沸猴仑。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,705評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)辽俗。三九已至,卻和暖如春篡诽,著一層夾襖步出監(jiān)牢的瞬間崖飘,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,848評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工杈女, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留朱浴,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,831評(píng)論 2 370
  • 正文 我出身青樓达椰,卻偏偏與公主長(zhǎng)得像翰蠢,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子啰劲,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評(píng)論 2 354

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