Glide4.x源碼淺析(三)Glide流程之圖片的獲取

上篇文章說到了RequestBuilder創(chuàng)建了請求,會調(diào)用RequestManager的track方法你虹,請求的創(chuàng)建的調(diào)用鏈很長但是跟蹤下去會發(fā)現(xiàn)最終創(chuàng)建了一個SingleRequest對象

  void track(@NonNull Target<?> target, @NonNull Request request) {
    targetTracker.track(target);
    requestTracker.runRequest(request);
  }

其中track是將請求保存起來

  public void runRequest(@NonNull Request request) {
    requests.add(request);
    if (!isPaused) {
      request.begin();
    } else {
      pendingRequests.add(request);
    }
  }

如果當前不是暫停狀態(tài)則調(diào)用begin方法處理請求,如果是暫停狀態(tài)則加入到pendingRequests集合中

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

根據(jù)請求的狀態(tài)進行相應(yīng)的處理

  • 如果傳入的路徑為空則拋出異常
  • 如果當前請求的狀態(tài)使運行狀態(tài) 同樣拋出異常妄均,請求不能重復(fù)提交
  • 如果當前請求的狀態(tài)使完成狀態(tài)桑孩,則直接返回相應(yīng)的資源

然后將請求狀態(tài)修改為WAITING_FOR_SIZE,以目標view的寬高屬性傳入到onSizeReady中闽巩,修改狀態(tài)值為RUNNING然后調(diào)用Engine.load對請求進行處理

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

額外補充一些钧舌,SingleRequest實現(xiàn)了上面的幾個接口

  • SizeReadyCallback 里面只有一個onSizeReady方法,上面我們已經(jīng)說過了涎跨,這個方法對于SingleReuqest的意義可以說請求的開始
  • ResourceCallBack 里面有兩個方法 void onResourceReady(Resource<?> resource, DataSource dataSource) void onLoadFailed(GlideException e) 從字面也可以看出是請求成功的回調(diào)洼冻,拿到資源之后對資源的解碼都是在這個方法的邏輯里面
public final class SingleRequest<R> implements Request,
    SizeReadyCallback,
    ResourceCallback,
    FactoryPools.Poolable

來看load方法
Engine#load():

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

    EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);

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

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

    EngineJob<R> engineJob =
        engineJobFactory.build(
            key,
            isMemoryCacheable,
            useUnlimitedSourceExecutorPool,
            useAnimationPool,
            onlyRetrieveFromCache);

    DecodeJob<R> decodeJob =
        decodeJobFactory.build(
            glideContext,
            model,
            key,
            signature,
            width,
            height,
            resourceClass,
            transcodeClass,
            priority,
            diskCacheStrategy,
            transformations,
            isTransformationRequired,
            isScaleOnlyOrNoTransform,
            onlyRetrieveFromCache,
            options,
            engineJob);

    jobs.put(key, engineJob);

    engineJob.addCallback(cb);
    engineJob.start(decodeJob);

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

緩存
關(guān)于緩存我會再單獨開一篇來說,前幾篇僅僅是以Glie的流程為主線來說的
首先根據(jù)圖片源的路徑指定view的寬高以及配置屬性等得到一個key值隅很,然后loadFromActiveResources首先去獲取緩存中的圖片資源

  @Nullable
  private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
      return null;
    }
    EngineResource<?> active = activeResources.get(key);
    if (active != null) {
      active.acquire();
    }
    return active;
  }

activeResources中維護著一個HashMap撞牢,key為前面我們得的key,value為一個弱引用叔营,里面有我們的圖片資源屋彪,通過get獲取到資源,如果不為空审编,調(diào)用acquire對對象的引用數(shù)量進行加一操作撼班,內(nèi)存緩存是通過計數(shù)散列算法來進行相應(yīng)的操作的,如果對象的計數(shù)樹為0垒酬,則說明暫無其他對象引用此資源砰嘁,那么此資源可以被釋放了,如果不為>0 則說明還有其他對象引用此資源勘究,那么就不釋放此資源矮湘,這樣的話就可以保證圖片資源在Lru中不存在但是需要引用的情況。最后通過ResourceCallback.onResourceReady將圖片返回回去口糕,這里的cb即SingleRequest
如果activeResources中沒有缅阳,那么通過Lru進行獲取


  private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
      return null;
    }

    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
      cached.acquire();
      activeResources.activate(key, cached);
    }
    return cached;
  }

  private EngineResource<?> getEngineResourceFromCache(Key key) {
    Resource<?> cached = cache.remove(key);

    final EngineResource<?> result;
    if (cached == null) {
      result = null;
    } else if (cached instanceof EngineResource) {
      // Save an object allocation if we've cached an EngineResource (the typical case).
      result = (EngineResource<?>) cached;
    } else {
      result = new EngineResource<>(cached, true /*isMemoryCacheable*/, true /*isRecyclable*/);
    }
    return result;
  }

其中的cached就是我們在GlideBuilder中進行初始化的Lru對象,下面有源碼這里不再多說,
GlideBuilder#build():

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

對于Lru的數(shù)據(jù)獲取是通過刪除的方式來獲取景描,并且如果獲取成功的話會十办,放入到activiteResources中,這樣的話就可以對資源再次進行緩存了超棺,最后如果成功的話也是通過ResourceCallback.onResourceReady將圖片返回回去向族。

最后一種則是通過Jobs根據(jù)key獲取EngineJob對象,然后將自身的 EngineResource和DataSource對象傳入到ResourceCallback中,Jobs中維護著一個Map集合,Key為我們圖片的key棠绘,value為EngineJob件相,onlyRetrieveFromCache默認為true,可以在RequestQoptions中設(shè)置再扭。Jobs則是在Engine創(chuàng)建的時候新創(chuàng)建的。如果集合中有EngineJob對象夜矗,那么新建一個LoadStatus并返回泛范,如果沒有,那就新建一個EngineJob對象以及DecodeJob對象紊撕,并將新建的EngineJob加入大Jobs中罢荡,然后返回
最后調(diào)用EngineJob.Start()開啟線程獲取圖片

  public void start(DecodeJob<R> decodeJob) {
    this.decodeJob = decodeJob;
    GlideExecutor executor = decodeJob.willDecodeFromCache()
        ? diskCacheExecutor
        : getActiveSourceExecutor();
    executor.execute(decodeJob);
  }

首先判斷是否需要磁盤緩存,是則使用磁盤線程池逛揩,否則使用內(nèi)存緩存線程池

  boolean willDecodeFromCache() {
    Stage firstStage = getNextStage(Stage.INITIALIZE);
    return firstStage == Stage.RESOURCE_CACHE || firstStage == Stage.DATA_CACHE;
  }
  
  private Stage getNextStage(Stage current) {
    switch (current) {
      case INITIALIZE:
        return diskCacheStrategy.decodeCachedResource()
            ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
      case RESOURCE_CACHE:
        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);
    }
  }  

diskCacheStrategy是在ReuqestOption中設(shè)置的柠傍,對應(yīng)

  public static final DiskCacheStrategy AUTOMATIC = new DiskCacheStrategy() {
    @Override
    public boolean isDataCacheable(DataSource dataSource) {
      return dataSource == DataSource.REMOTE;
    }

    @Override
    public boolean isResourceCacheable(boolean isFromAlternateCacheKey, DataSource dataSource,
        EncodeStrategy encodeStrategy) {
      return ((isFromAlternateCacheKey && dataSource == DataSource.DATA_DISK_CACHE)
          || dataSource == DataSource.LOCAL)
          && encodeStrategy == EncodeStrategy.TRANSFORMED;
    }
// 是否允許幾碼緩存中的圖片資源
    @Override
    public boolean decodeCachedResource() {
      return true;
    }

// 是否允許解碼緩存中的源數(shù)據(jù)
    @Override
    public boolean decodeCachedData() {
      return true;
    }
  };

當然了 不管是磁盤緩存還是內(nèi)存最終都會調(diào)用DecodeJob的run()麸俘,接著會調(diào)用runWrapped()


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

runReason在初始化DecodeJob的時候設(shè)置初始為INITIALIZE辩稽,getNextStage()上面有說,最后stage則是設(shè)置為了RESOURCE_CACHE
調(diào)用getNextGenerator()返回了一個ResourceCacheGenerator對象从媚,

  private DataFetcherGenerator getNextGenerator() {
    switch (stage) {
      case RESOURCE_CACHE:
        return new ResourceCacheGenerator(decodeHelper, this);
      case DATA_CACHE:
        return new DataCacheGenerator(decodeHelper, this);
      case SOURCE:
        return new SourceGenerator(decodeHelper, this);
      case FINISHED:
        return null;
      default:
        throw new IllegalStateException("Unrecognized stage: " + stage);
    }
  }

注意實例化ResourceCacheGenerator第二個參數(shù)逞泄,是DecodeJob本身,而DecodeJob本身實現(xiàn)了下面幾個接口,有個簡單的印象就可以了

class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback,
    Runnable,
    Comparable<DecodeJob<?>>,
    Poolable 

最后調(diào)用了runGenerators方法

 private void runGenerators() {
    currentThread = Thread.currentThread();
    startFetchTime = LogTime.getLogTime();
    boolean isStarted = false;
    while (!isCancelled && currentGenerator != null
        && !(isStarted = currentGenerator.startNext())) {
      stage = getNextStage(stage);
      currentGenerator = getNextGenerator();

      if (stage == Stage.SOURCE) {
        reschedule();
        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.
  }

while的判斷條件是 請求沒有被取消并且currentGenterator != null 并且要currentGenterator.startNext()返回false才可以拜效。currentGentGenterator就是上面我們實例化的那個ResourceCacheGenerator對象喷众。

 @Override
  public boolean startNext() {
    List<Key> sourceIds = helper.getCacheKeys();
    if (sourceIds.isEmpty()) {
      return false;
    }
    List<Class<?>> resourceClasses = helper.getRegisteredResourceClasses();
    if (resourceClasses.isEmpty()) {
      if (File.class.equals(helper.getTranscodeClass())) {
        return false;
      }
      throw new IllegalStateException(
          "Failed to find any load path from " + helper.getModelClass() + " to "
              + helper.getTranscodeClass());
    }
    while (modelLoaders == null || !hasNextModelLoader()) {
      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.
      currentKey =
          new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops
              helper.getArrayPool(),
              sourceId,
              helper.getSignature(),
              helper.getWidth(),
              helper.getHeight(),
              transformation,
              resourceClass,
              helper.getOptions());
      cacheFile = helper.getDiskCache().get(currentKey);
      if (cacheFile != null) {
        sourceKey = sourceId;
        modelLoaders = helper.getModelLoaders(cacheFile);
        modelLoaderIndex = 0;
      }
    }

    loadData = null;
    boolean started = false;
    while (!started && hasNextModelLoader()) {
      ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
      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;
  }

hepler為新建的DecodeHelper上面我們有說到,

  List<Key> getCacheKeys() {
    if (!isCacheKeysSet) {
      isCacheKeysSet = true;
      cacheKeys.clear();
      List<LoadData<?>> loadData = getLoadData();
      //noinspection ForLoopReplaceableByForEach to improve perf
      for (int i = 0, size = loadData.size(); i < size; i++) {
        LoadData<?> data = loadData.get(i);
        if (!cacheKeys.contains(data.sourceKey)) {
          cacheKeys.add(data.sourceKey);
        }
        for (int j = 0; j < data.alternateKeys.size(); j++) {
          if (!cacheKeys.contains(data.alternateKeys.get(j))) {
            cacheKeys.add(data.alternateKeys.get(j));
          }
        }
      }
    }
    return cacheKeys;
  }

首次創(chuàng)建使用isCacheKeysSet為false紧憾,進入判斷體到千,首先根據(jù)getLoadData()獲取一個LoadData的集合。

  List<LoadData<?>> getLoadData() {
    if (!isLoadDataSet) {
      isLoadDataSet = true;
      loadData.clear();
      List<ModelLoader<Object, ?>> modelLoaders = glideContext.getRegistry().getModelLoaders(model);
      //noinspection ForLoopReplaceableByForEach to improve perf
      for (int i = 0, size = modelLoaders.size(); i < size; i++) {
        ModelLoader<Object, ?> modelLoader = modelLoaders.get(i);
        LoadData<?> current =
            modelLoader.buildLoadData(model, width, height, options);
        if (current != null) {
          loadData.add(current);
        }
      }
    }
    return loadData;
  }

這個方法先獲取一個ModelLoader的集合赴穗,然后遍歷集合將其中的ModelLoader取出憔四,并調(diào)用buildLoadData方法創(chuàng)建一個LoadData對象 將其加入loadData集合中 并最終返回 model為圖片源地址。

  @NonNull
  public synchronized <A> List<ModelLoader<A, ?>> getModelLoaders(@NonNull A model) {
    List<ModelLoader<A, ?>> modelLoaders = getModelLoadersForClass(getClass(model));
    int size = modelLoaders.size();
    List<ModelLoader<A, ?>> filteredLoaders = new ArrayList<>(size);
    //noinspection ForLoopReplaceableByForEach to improve perf
    for (int i = 0; i < size; i++) {
      ModelLoader<A, ?> loader = modelLoaders.get(i);
      if (loader.handles(model)) {
        filteredLoaders.add(loader);
      }
    }
    return filteredLoaders;
  }

getModelLoadersForClass下面關(guān)聯(lián)的代碼邏輯很長般眉,但是都很簡單這里就不貼不出來了赵,大致作用就是根據(jù)在實例化Glide對象的時候注冊的ModelLoader對象集,根據(jù)圖片源的類型篩選出部分ModelLoader對象集并最終返回
modelLoader.buildLoadData()這個方法在我們自定義的ModelLoader中也有一個

@GlideModule
public class OkHttpGlideModule extends AppGlideModule {
    private static final String TAG = "通訊顧問";

    @Override
    public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
        Log.i(TAG, "registerComponents: ");
        registry.replace(GlideUrl.class,InputStream.class,new OkHttpUrlLoader.Factory(new HTTPSUtils(context).getInstance()));
    }
}

public class OkHttpUrlLoader implements ModelLoader<GlideUrl, InputStream> {

    private final Call.Factory mClient;
    @Nullable
    @Override
    public LoadData<InputStream> buildLoadData(@NonNull GlideUrl glideUrl, int width, int height, @NonNull Options options) {
        return new LoadData<>(glideUrl,new OkHttpStreamFetcher(mClient,glideUrl));
    }
}    

回到getCacheKeys()中甸赃,遍歷得到的LoadData對象集,并將loadData所持有的key加入到cacheKeys中,其中data.alternateKeys為空柿汛,為什么來看loadData的構(gòu)造器


    public LoadData(@NonNull Key sourceKey, @NonNull DataFetcher<Data> fetcher) {
      this(sourceKey, Collections.<Key>emptyList(), fetcher);
    }

    public LoadData(@NonNull Key sourceKey, @NonNull List<Key> alternateKeys,
        @NonNull DataFetcher<Data> fetcher) {
      this.sourceKey = Preconditions.checkNotNull(sourceKey);
      this.alternateKeys = Preconditions.checkNotNull(alternateKeys);
      this.fetcher = Preconditions.checkNotNull(fetcher);
    }

新建的LoadData中,alternateKeys為空埠对,除了BaseGlideUrlLoader這個類其他的類都是沒有傳入alternateKeys的络断。所以這里也就不再多說了。sourceKey就是我們傳入的圖片源项玛,所以這里的集合里面也就一個key值
回到startNext()貌笨,得到key之后,然后調(diào)用DecodeHepler.getRegisteredResourceClasses()

  List<Class<?>> getRegisteredResourceClasses() {
    return glideContext.getRegistry()
        .getRegisteredResourceClasses(model.getClass(), resourceClass, transcodeClass);
  }
Registry#getRegisteredResourceClasses()
  @NonNull
  public <Model, TResource, Transcode> List<Class<?>> getRegisteredResourceClasses(
      @NonNull Class<Model> modelClass, @NonNull Class<TResource> resourceClass,
      @NonNull Class<Transcode> transcodeClass) {
    List<Class<?>> result = modelToResourceClassCache.get(modelClass, resourceClass);

    if (result == null) {
      result = new ArrayList<>();
      List<Class<?>> dataClasses = modelLoaderRegistry.getDataClasses(modelClass);
      for (Class<?> dataClass : dataClasses) {
        List<? extends Class<?>> registeredResourceClasses =
            decoderRegistry.getResourceClasses(dataClass, resourceClass);
        for (Class<?> registeredResourceClass : registeredResourceClasses) {
          List<Class<Transcode>> registeredTranscodeClasses = transcoderRegistry
              .getTranscodeClasses(registeredResourceClass, transcodeClass);
          if (!registeredTranscodeClasses.isEmpty() && !result.contains(registeredResourceClass)) {
            result.add(registeredResourceClass);
          }
        }
      }
      modelToResourceClassCache.put(modelClass, resourceClass,
          Collections.unmodifiableList(result));
    }

    return result;
  }  

resourceClass代表圖片的源文件類型稍计,transcodeClass代表需要轉(zhuǎn)換為的圖片類型
RequestManager#asBitmap():

  public RequestBuilder<Bitmap> asBitmap() {
    return as(Bitmap.class).apply(DECODE_TYPE_BITMAP);
  }

其中as方法的入?yún)itmap.class為源文件的類型躁绸,appley代表設(shè)置需要轉(zhuǎn)換為的圖片類型,即transcodeClass

 private static final RequestOptions DECODE_TYPE_BITMAP = decodeTypeOf(Bitmap.class).lock()
  public static RequestOptions decodeTypeOf(@NonNull Class<?> resourceClass) {
    return new RequestOptions().decode(resourceClass);
  }
    public RequestOptions decode(@NonNull Class<?> resourceClass) {
    if (isAutoCloneEnabled) {
      return clone().decode(resourceClass);
    }

    this.resourceClass = Preconditions.checkNotNull(resourceClass);
    fields |= RESOURCE_CLASS;
    return selfOrThrowIfLocked();
  }
 

回到getRegisteredResourceClasses(),先從集合中根據(jù)圖片源的類型以及圖片類型獲取净刮,如果成功獲取到直接返回剥哑,如果沒有獲取到則進入判斷方法,到此為止我們還不知這里面存的到底是什么淹父,要獲取的是什么
記得我們在初始化Glide的時候株婴,會使用Registry通過append注冊一大堆的亂七八糟的東西,

Registry#append():
  @NonNull
  public <Model, Data> Registry append(
      @NonNull Class<Model> modelClass, @NonNull Class<Data> dataClass,
      @NonNull ModelLoaderFactory<Model, Data> factory) {
    modelLoaderRegistry.append(modelClass, dataClass, factory);
    return this;
  }

ModelLoaerRegistry#append():  
  public synchronized <Model, Data> void append(
      @NonNull Class<Model> modelClass,
      @NonNull Class<Data> dataClass,
      @NonNull ModelLoaderFactory<? extends Model, ? extends Data> factory) {
    multiModelLoaderFactory.append(modelClass, dataClass, factory);
    cache.clear();
  }  

multiModelLoaderFactory#append():
  synchronized <Model, Data> void append(
      @NonNull Class<Model> modelClass,
      @NonNull Class<Data> dataClass,
      @NonNull ModelLoaderFactory<? extends Model, ? extends Data> factory) {
    add(modelClass, dataClass, factory, /*append=*/ true);
  }

  private <Model, Data> void add(
      @NonNull Class<Model> modelClass,
      @NonNull Class<Data> dataClass,
      @NonNull ModelLoaderFactory<? extends Model, ? extends Data> factory,
      boolean append) {
    Entry<Model, Data> entry = new Entry<>(modelClass, dataClass, factory);
    entries.add(append ? entries.size() : 0, entry);
  }  

上面的一長串調(diào)用鏈的最終形態(tài)就是將傳入的圖片源路徑類型暑认、圖片類型困介、以及一個位置的工廠類封裝到一個Entry中,再將這個Entry加入到集合中,為什么要說這個呢,因為這里用到了Entries.

Registry#RegisteredResourceClasses():

  @NonNull
  public <Model, TResource, Transcode> List<Class<?>> getRegisteredResourceClasses(
      @NonNull Class<Model> modelClass, @NonNull Class<TResource> resourceClass,
      @NonNull Class<Transcode> transcodeClass) {
    List<Class<?>> result = modelToResourceClassCache.get(modelClass, resourceClass);

    if (result == null) {
      result = new ArrayList<>();
      List<Class<?>> dataClasses = modelLoaderRegistry.getDataClasses(modelClass);
      for (Class<?> dataClass : dataClasses) {
        List<? extends Class<?>> registeredResourceClasses =
            decoderRegistry.getResourceClasses(dataClass, resourceClass);
        for (Class<?> registeredResourceClass : registeredResourceClasses) {
          List<Class<Transcode>> registeredTranscodeClasses = transcoderRegistry
              .getTranscodeClasses(registeredResourceClass, transcodeClass);
          if (!registeredTranscodeClasses.isEmpty() && !result.contains(registeredResourceClass)) {
            result.add(registeredResourceClass);
          }
        }
      }
      modelToResourceClassCache.put(modelClass, resourceClass,
          Collections.unmodifiableList(result));
    }

    return result;
  }

這里的modelLoaderRegistry.getDataClasses(modelClass)就是根據(jù)遍歷entries集合中的Entry然后對比找到與傳入相同的modelClass,并返回一個圖片源類型的集合
ResourceDecoderRegistry#getResourceClasses():

  @NonNull
  @SuppressWarnings("unchecked")
  public synchronized <T, R> List<Class<R>> getResourceClasses(@NonNull Class<T> dataClass,
      @NonNull Class<R> resourceClass) {
    List<Class<R>> result = new ArrayList<>();
    for (String bucket : bucketPriorityList) {
      List<Entry<?, ?>> entries = decoders.get(bucket);
      if (entries == null) {
        continue;
      }
      for (Entry<?, ?> entry : entries) {
        if (entry.handles(dataClass, resourceClass)
            && !result.contains((Class<R>) entry.resourceClass)) {
          result.add((Class<R>) entry.resourceClass);
        }
      }
    }
    return result;
  }

外部for循環(huán)的代碼不用看 看里面的蘸际,這里也是遍歷ectries座哩,然后匹配與傳入的圖片原地址相同類型以及原圖片類型相同的Entry,最后返回圖片類型的集合
然后遍歷這個集合,并根據(jù)源圖片類型與要轉(zhuǎn)換的指定類型為參粮彤,獲取到指定的轉(zhuǎn)換類型集合根穷,最后加入到modelToResourceClassCache中,并返回源圖片類型型集合导坟。
回到ResourceCacheGenerator#startNext(),modelLoaders是磁盤緩存中的ModelLoader對象屿良,下面代碼下半部分有寫,如果創(chuàng)建一個key對象惫周,如果本地磁盤中有對應(yīng)的文件尘惧,那么就根據(jù)此文件獲取到modelLoaders,當然此時的modelLoaders == null,transfromation表示對圖形的變換递递,然后通過一些屬性組成一個key值喷橙,并在磁盤中嘗試獲取
然后下面接著會遍歷modelLoaders,并通過遍歷出來的ModelLoaders調(diào)用其 buildLoadData()方法 生成一個LoadData對象 漾狼,然后調(diào)用其成員屬性fetcher的loadData進行加載圖片的操作

上面的操作都是基于DataFetcherGenerator的startNext()執(zhí)行的重慢,我們上面的操作都是在ResourceCacheGenerator中執(zhí)行的,這個類的意義是從緩存中加載數(shù)據(jù)逊躁,而且是經(jīng)過處理的圖片資源緩存似踱,如果在此類中沒有找到對應(yīng)的資源,會在startNext()中的第一個while循環(huán)中返回false稽煤,然后回到DecodeJob#runGenerators的判斷語句中,如果返回false會執(zhí)行方法體內(nèi)的代碼核芽,首先得到stage的下一個狀態(tài),當前狀態(tài)使RESOURCE_CACHE酵熙,下一狀態(tài)就是DATA_CACHE轧简,對應(yīng)的類是DataCacheGenerator,此類的作用是從未經(jīng)過處理的圖片資源緩存中獲取匾二,邏輯上基本同ResourceCacheGenerator哮独,當然如果緩存中也沒有拳芙,也會返回false繼續(xù)下一次的遍歷,如果設(shè)置了只從緩存中讀取,那么下一狀態(tài)就是結(jié)束狀態(tài)FINISHED皮璧,如果不只是緩存中讀取那就開始聯(lián)網(wǎng)下載獲取SourceGenerator

關(guān)于buildLoadData,我們在自定義的ModelLoader中用到了,我們上面說過了 ,而loaddata.fetcher.loadata()如下


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

        mCallback = callback;
        Request request = requestBuilder.build();
        mCall = mFactory.newCall(request);
        mCall.enqueue(this);
    }
    
        @Override
    public void onResponse(@NonNull Call call, @NonNull Response response) {
        Log.i(TAG, "onResponse: ");
        mResponseBody = response.body();
        if (response.isSuccessful()) {
            long contentLength = Preconditions.checkNotNull(mResponseBody).contentLength();
            mStream = ContentLengthInputStream.obtain(mResponseBody.byteStream(), contentLength);
            mCallback.onDataReady(mStream);
        } else {
            mCallback.onLoadFailed(new HttpException(response.message(), response.code()));
        }
    }

可以看到我們直接調(diào)用了Okhttp進行了請求,成功的話會通過CallBack.onDataReady()將資源傳送回去.callBack在調(diào)用loadData的時候傳遞過來舟扎,也就是加載器本身。我們這里以SourceGenerator為例

  @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();
    } else {
      cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
          loadData.fetcher.getDataSource(), originalKey);
    }
  }

首先獲取到磁盤加載策略悴务,磁盤緩存有幾種策略睹限,全部緩存,只緩存原始數(shù)據(jù)讯檐,只緩存轉(zhuǎn)換完畢的數(shù)據(jù)羡疗。這里判斷是否緩存原始數(shù)據(jù),如果緩存則調(diào)用cb.reschedule(),否則調(diào)用cb.onDataFetcherReady,其中cb為DecodeJob

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

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

callback在DecodeJob初始化的時候傳入的,在Engine中初始化的别洪,對應(yīng)EngineJob,最后調(diào)用的是DecodeJob中的run()叨恨,runWarpped(),因為修改了runReason,所以直接調(diào)用runGenerators(),在之后就是進入SourceGenerator中的startNext(),此時的dataToCache不在為空蕉拢,直接調(diào)用cacheData()
SourceGenerator#cacehData():

  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 =
        new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
  }

中間一句helper.getDiskCache().put(originalKey, writer)則是將資源保存到了磁盤中,并為sourceCacheGenerator重新賦值,然后在runGenerators()繼續(xù)遍歷特碳,調(diào)用DataCacheGenerator中的startNext(),從磁盤中讀取資源诚亚,讀取結(jié)果的處理跟從網(wǎng)絡(luò)獲取成功的處理大致相同
從磁盤讀取完之后會調(diào)用DecodeJob.onDataFetcherReady();

  @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();
      } finally {
        TraceCompat.endSection();
      }
    }
  }

上面我們儲存資源到磁盤的時候進行了一次線程切換,所以這里會調(diào)用callback.reschedule(),注意runReason的值晕换,然后最后又回到了run(),最后調(diào)用decodeFromRetrievedData

  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);
    } else {
      runGenerators();
    }
  }

decodeFromData就不說了,可以簡單理解為解析圖片


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

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

調(diào)用notifyComplete通知圖片獲取完畢


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

EngineJob#onResourceReady():
  @Override
  public void onResourceReady(Resource<R> resource, DataSource dataSource) {
    this.resource = resource;
    this.dataSource = dataSource;
    MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
  }  

發(fā)送了一條消息站宗,最后到了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);
      }
    }
    // Our request is complete, so we can release the resource.
    engineResource.release();

    release(false /*isRemovedFromQueue*/);
  }

前面判斷如果請求取消了闸准,則釋放資源,如果沒有取消梢灭,注意下面夷家,先是調(diào)用engineResource.acquired()對資源的引用加1,然后下面判斷cbs的數(shù)量,每遍歷一次就計數(shù)+1敏释,因為如果同一個界面多個地方同時需要使用這張圖片库快,每調(diào)用一次就會產(chǎn)生一個cb,但是最終只有一個請求執(zhí)行,等到這個請求完成之后然后共同使用這張圖片,遍歷完之后钥顽,在調(diào)用engineResource.release()釋放-1;遍歷的過程中义屏,每遍歷一次便通過cb.onResourceReady()將資源傳遞出去 ,這里的cb為SingleRequest

  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())) {
      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()) {
      releaseResource(resource);
      // We can't put the status to complete before asking canSetResource().
      status = Status.COMPLETE;
      return;
    }

    onResourceReady((Resource<R>) resource, (R) received, dataSource);
  }
  
  
  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);
      }
    } finally {
      isCallingCallbacks = false;
    }

    notifyLoadSuccess();
  }  

主要看try-cache中的代碼,如果requestListener.onResourceReady()返回了true,或是targetListener.onResourceReady()返回了true,那么target將不會去設(shè)置圖片,意思也就是Glide將不會主動為我們的ImageView設(shè)置圖片蜂大,如果false則可以

    Glide.with(activity)
                .load(finalPath)
                .apply(new RequestOptions().placeholder(loadingResId).error(failResId).override(width, height))
                .listener(new RequestListener<Drawable>() {
                    @Override
                    public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
                        return false;
                    }

                    @Override
                    public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
                        if (displayImageListener != null) {
                            displayImageListener.onSuccess(imageView, finalPath);
                        }
                        return false;
                    }
                })
                .into(imageView);

OK,到這里Glide的流程就結(jié)束了

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末闽铐,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子奶浦,更是在濱河造成了極大的恐慌兄墅,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件澳叉,死亡現(xiàn)場離奇詭異隙咸,居然都是意外死亡沐悦,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門五督,熙熙樓的掌柜王于貴愁眉苦臉地迎上來所踊,“玉大人,你說我怎么就攤上這事概荷★醯海” “怎么了?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵误证,是天一觀的道長继薛。 經(jīng)常有香客問我,道長愈捅,這世上最難降的妖魔是什么遏考? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮蓝谨,結(jié)果婚禮上灌具,老公的妹妹穿的比我還像新娘。我一直安慰自己譬巫,他們只是感情好咖楣,可當我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著芦昔,像睡著了一般诱贿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上咕缎,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天珠十,我揣著相機與錄音,去河邊找鬼凭豪。 笑死焙蹭,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的嫂伞。 我是一名探鬼主播孔厉,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼末早!你這毒婦竟也來了烟馅?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤然磷,失蹤者是張志新(化名)和其女友劉穎郑趁,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體姿搜,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡寡润,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年捆憎,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片梭纹。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡躲惰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出变抽,到底是詐尸還是另有隱情础拨,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布绍载,位于F島的核電站诡宗,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏击儡。R本人自食惡果不足惜塔沃,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望阳谍。 院中可真熱鬧蛀柴,春花似錦、人聲如沸矫夯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽茧痒。三九已至肮韧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間旺订,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工超燃, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留区拳,地道東北人。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓意乓,卻偏偏與公主長得像樱调,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子届良,可洞房花燭夜當晚...
    茶點故事閱讀 43,697評論 2 351

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