Android图片加载框架Glide源码解析(二)

Glide的图片加载流程

上一章节Android图片加载框架Glide源码解析(一)中讲Glide的基本使用,对于不会使用的建议先去看下。

体验了Glide的便捷及强大后,我们一定很好奇Glide是如何加载图片的或者说加载流程又是怎样的呢?不用急,本章我们就从源码上一步步分析加载流程。

1、Glide一次调用的代码如下:

Glide.with(context)
    .load(url)
    .apply(RequestOptions.placeholderOf(R.drawable.ic_default)
                .error(R.drawable.ic_error))
    .thumbnail(0.4f)
    .into(imageview);

2、从调用流程入手进行分析

1.调用with()静态方法

Glide类本身是一个单例,调用静态方法with()去实例化Glide类并返回RequestManager对象,以供后面使用。静态方法with()是一个重载方法,参数可以是Context、Activity、Fragment、View、FragmentActivity这五种,源码如下:

/**
 * 传入对象是Context,
 */
@NonNull
public static RequestManager with(@NonNull Context context) {
    return getRetriever(context).get(context);
}
//传入对象是Activity
@NonNull
public static RequestManager with(@NonNull Activity activity) {
    return getRetriever(activity).get(activity);
}
//传入对象是FragmentActivity
@NonNull
public static RequestManager with(@NonNull FragmentActivity activity) {
    return getRetriever(activity).get(activity);
}
//传入对象是Fragment
@NonNull
public static RequestManager with(@NonNull Fragment fragment) {
  return getRetriever(fragment.getActivity()).get(fragment);
}
//传入对象是View
@NonNull
public static RequestManager with(@NonNull View view) {
  return getRetriever(view.getContext()).get(view);
}

从源码可以看到,这几个重载方法内部均调用了同一个静态方法getRetriever(),那么这个方法是干嘛用的呢,看源码:

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

从源码我们可以看到,先去对参数context进行非空检查,然后调用Glide的静态方法get()采用懒汉式的方式实现单例,通过Builder模式实例化Glide对象,由于篇幅这里就不贴初始化的代码了。初始化后调用getRequestManagerRetriever()得到RequestManagerRetriever对象,然后调用get()就能获得一个RequestManager对象。

2.通过RequestManager类管理Request请求

RequestManager类是实现Glide加载资源的管理类,根据Activity生命周期管控资源加载请求,可以开启、暂停及重启。
通过调用load()重载方法创建一个RequestBuilder,去加载资源。

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

其中,asDrawable方法主要是设置加载的资源最终以Drawable对象返回,默认情况下,Glide加载图片资源时以Drawable对象对象,目前Glide支持返回Drawable、Bitmap、gif和File四种,在不想使用默认的drawable返回的情况下可以在调用load前调用对应的方法,如Bitmap对应asBitmap();

Glide.with(context)
      .asBitmap()    //该句表示加载资源后保存为Bitmap对象
      .load(url)
      .into(imageview);

注:如果调用asBitmap()后在调用load方法,此时调用的是RequestBuilder类的load方法,注意区分!!!
好了,回到正题,上述调用load后内部是通过asDrawable()设置资源返回类型,并返回一个RequestBuilder对象,然后调用RequestBuilder的方法load(),看一下源码:

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

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

可以看到这里new了一个requestBuilder对象。

3.RequestOptions实现各种选项设置

通过上述返回requestBuilder对象后,后续的加载资源及一些设置就在requestBuilder类中实现了。在V4版本中,Glide将一些选项设置模块划分出来,放在RequestOptions类中,其中包括设置占位图、错误图、缓存机制、关闭动画、scaleType等。在调用apply()方法时传入RequestOptions对象,apply()方法源码如下:

//传入RequestOptions类对象,并保存起来
public RequestBuilder<TranscodeType> apply(@NonNull RequestOptions requestOptions) {
    Preconditions.checkNotNull(requestOptions);
    this.requestOptions = getMutableOptions().apply(requestOptions);
    return this;
}

//判断默认选项和设置的是否一致,如果一致返回this.requestOptions的一个克隆,如果不一致则直接返回    this.requestOptions
protected RequestOptions getMutableOptions() {
    return defaultRequestOptions == this.requestOptions
        ? this.requestOptions.clone() : this.requestOptions;
  }

获取到当前的RequestOptions对象后,通过调用对象方法的apply方法,将上传入的requestOptions对象传入并保持设置,方便后续使用,同时返回了新的RequestOptions对象赋值给this.requestOptions变量,下面是RequestOptions类中的apply()方法:

public RequestOptions apply(@NonNull RequestOptions other) {
//允许自动克隆,则直接克隆一个RequestOptions对象然后设置类各种属性,并直接返回该对象
if (isAutoCloneEnabled) {
  return clone().apply(other);
}

  ...
 中间省略代码主要用于判断是否设置了对应的属性(占位图、错误、缓存等选项),没设置过就去设置,由于代码量大,这里不贴出了
  ...

// Applying options with dontTransform() is expected to clear our transformations.
//这里检查是否要动画效果,如果不需要,就清除所有过场动画效果
if (!isTransformationAllowed) {
  transformations.clear();
  fields &= ~TRANSFORMATION;
  isTransformationRequired = false;
  fields &= ~TRANSFORMATION_REQUIRED;
  isScaleOnlyOrNoTransform = true;
}

fields |= other.fields;
options.putAll(other.options);

return selfOrThrowIfLocked();
}

Glide加载图片资源中的一些选项设置都在这个RequestOptions类中,该类也提供了一些选项设置的静态方法,设置完成直接返回requestOptions对象,不需要用户自己去new。

4.调用RequestBuilder类中into()方法实现资源加载并设置到目标target中

前面几点讲述了资源加载之前的基础设置及选项设置,最后调用into实现资源的加载。into()方法时个重载类,最终几个方法都会进入到into(Y target,RequestListener<TranscodeType> targetListener,RequestOptions options)中,不废话,直接看源码:

private <Y extends Target<TranscodeType>> Y into(@NonNull Y target,
    @Nullable RequestListener<TranscodeType> targetListener,
    @NonNull RequestOptions options) {
  Util.assertMainThread();//检测是否在主线程,非主线程调用时抛异常
  Preconditions.checkNotNull(target);//判断target非空
  //防止在load之前调用into方法
  if (!isModelSet) {
    throw new IllegalArgumentException("You must call #load() before calling #into()");
  }

  options = options.autoClone();
  //通过buildRequest()方法构建一个Request对象
  Request request = buildRequest(target, targetListener, options);
  //获取target之前的request请求对象
  Request previous = target.getRequest();
  //将新构建的request对象与旧的previous对象进行比较,判断是否相同。
  // isSkipMemoryCacheWithCompletePreviousRequest用于判断options是否有使用缓存机制和previous是否已经加载完毕
  if (request.isEquivalentTo(previous)
      && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
    request.recycle();
    //判断previous是否正在运行,如果没在运行,则执行previous这个请求
    if (!Preconditions.checkNotNull(previous).isRunning()) {
     previous.begin();
    }
    return target;
  }

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

  return target;
}

into方法中主要做了以下两个方面的事情:

  • 通过buildRequest()方法构建一个Request;

  • 获取target的Request对象,与构建的Request对象进行比较,并检测是否采用缓存及target的上次请求是否已经完成:
    1.两个对象相等,并且options采用了缓存或target的上次请求未完成,则将构建的request进行回收,并且 判断target的request是否正在运行,正在运行则直接返回target,若为正在运行则调用Request的begin方法 启动同步加载资源请求;
    2.两个对象不等,或者option没使用缓存机制和target的请求已经完成,清除target之前的设置,并将新构建的request对象设置给target,通过track方法启动Request请求;
    buildRequest()方法源码:

    //构建一个Request对象
    private Request buildRequest(Target<TranscodeType> target,
           @Nullable RequestListener<TranscodeType> targetListener,RequestOptions requestOptions) {
    return buildRequestRecursive(target,targetListener, /*parentCoordinator=*/ null,
      transitionOptions, requestOptions.getPriority(), requestOptions.getOverrideWidth(),
      requestOptions.getOverrideHeight(), requestOptions);
    }
    
    /**
     * 主要做的事情就是先创建一个主加载资源的Request对象,然后根据是否设置了加载错误时
     * 显示资源选项,创建一个errorRequest加载资源对象,最后返回Request
     **/
    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;
    if (errorBuilder != null) {
      errorRequestCoordinator = new ErrorRequestCoordinator(parentCoordinator);
      parentCoordinator = errorRequestCoordinator;
    }
    //创建加载资源的Request请求
    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;
    }
    

isSkipMemoryCacheWithCompletePreviousRequest方法源码:

  private boolean isSkipMemoryCacheWithCompletePreviousRequest( RequestOptions options, Request previous) {
      return !options.isMemoryCacheable() && previous.isComplete();
}

当条件不满足时,先执行clear,在执行track方法,实现Request请求,源码如下:

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

该方法,将target加入到targetTracker列表中,然后运行Request,看下runRequest方法:

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

代码很简单,一看就懂。
到目前为止一次Glide加载图片资源的流程已经完成大半了,后面就是看Request如何加载资源并显示到target的。

5.Request实现加载资源及显示

上面讲到创建了Request对象,但是Request对象是一个interface,那么Request是在哪里实现的呢?追踪上面创建的Request对象,很容易在RequestBuilder中发现obtainRequest方法,该方法如下:

private Request obtainRequest( Target<TranscodeType> target,
  RequestListener<TranscodeType> targetListener,RequestOptions requestOptions,
  RequestCoordinator requestCoordinator,TransitionOptions<?, ? super TranscodeType> transitionOptions,
  Priority priority,int overrideWidth,int overrideHeight) {
return SingleRequest.obtain(context,glideContext,model,transcodeClass,requestOptions,
    overrideWidth,overrideHeight,priority,target,
    targetListener, requestListeners, requestCoordinator, glideContext.getEngine(),
    transitionOptions.getTransitionFactory());
}

该方法通过SingleRequest类的方法obtain去创建一个Request对象,该类实现了Request、ResourceCallback、SizeReadyCallback等接口,可以说这个类就是我们要找的。那么我们看下该类如何实现begin方法的,首先看下源码:

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

方法经过一系列的条件判断,当status是Running或者WAITING_FOR_SIZE状态,并且需要设置占位图的时候,会调用onLoadStarted设置占位图,当status是Status.COMPLETE时会执行ResouceCallback接口的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())) {
  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;
}
//判断是否需要设置资源,如果不需要设置则直接释放资源并设置状态为complete
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);
}

方法主要对条件判断,对于不符合条件的直接跑出异常并调用onLoadFailed回调失败结果,成功了后先判断是否需要设置资源,不设置的话就吧资源释放然后设置状态为complete,否则就调用onResourceReady()的重载方法进一步处理,

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;
...
isCallingCallbacks = true;
try {
  boolean anyListenerHandledUpdatingTarget = false;
  if (requestListeners != null) {
    for (RequestListener<R> listener : requestListeners) {
      anyListenerHandledUpdatingTarget |=
          listener.onResourceReady(result, model, target, dataSource, isFirstResource);
    }
  }
  anyListenerHandledUpdatingTarget |=
      targetListener != null
          && targetListener.onResourceReady(result, model, target, dataSource, isFirstResource);
  //设置资源及动画相关信息
  if (!anyListenerHandledUpdatingTarget) {
    Transition<? super R> animation =
        animationFactory.build(dataSource, isFirstResource);
    target.onResourceReady(result, animation);
  }
} finally {
  isCallingCallbacks = false;
}
//最后通知加载成功
notifyLoadSuccess();
}

可以看到有这么一句:

target.onResourceReady(result, animation);

其实这里就是设置target资源,target分别与要设置资源的组件有关,比如Imageview,对应ImageViewTarget,Glide中对应如下几个Target:


QQ截图20180903095355.png

以ImageViewTarget类为例,onReourceReady()方法的实现如下:

@Override
public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
  if (transition == null || !transition.transition(resource, this)) {
    setResourceInternal(resource);
  } 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);
  maybeUpdateAnimatable(resource);
}

protected abstract void setResource(@Nullable Z resource);

那么setResource()抽象方法是在哪里实现的呢,这就要根据设置资源的返回类型决定了,比如我们的返回类型是Drawable,则实现是在DrawableImageViewTarget类中,实现如下:

/**
 * A target for display {@link Drawable} objects in {@link ImageView}s.
 */
public class DrawableImageViewTarget extends ImageViewTarget<Drawable> {

  ...

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

好了终于看到显示资源的地方了。那么我们回到SingleRequest类的onResourceReady(Resource<R> resource, R result, DataSource dataSource)方法中,最后又一句:

notifyLoadSuccess();

这个方法时做什么的呢,先看notifyLoadSuccess()的源码:

private void notifyLoadSuccess() {
    if (requestCoordinator != null) {
      requestCoordinator.onRequestSuccess(this);
    }
}

方法很简单,就是判断下requestCoordinator 非空,然后执行requestCoordinator对象方法onRequestSuccess(),那么requestCoordinator的实现在哪里呢,搜索很容易发现继承RequestCoordinator接口的只有两个类ThumbnailRequestCoordinator和ErrorRequestCoordinator,打开ThumbnailRequestCoordinator类,找到onRequestSuccess方法,可以看到:

@Override
 public void onRequestSuccess(Request request) {
    if (request.equals(thumb)) {
      return;
  }
  if (parent != null) {
    parent.onRequestSuccess(this);
  }
  // Clearing the thumb is not necessarily safe if the thumb is being displayed in the Target,
  // as a layer in a cross fade for example. The only way we know the thumb is not being
  // displayed and is therefore safe to clear is if the thumb request has not yet completed.
  if (!thumb.isComplete()) {
    thumb.clear();
  }
}

从源码中看到这个方法主要做一些加载成功后的清理工作。
由此可以看出,Glide加载成功后,分别由Target和RequestCoordinator接管资源展示和request回收工作。

3、总结

源码分析完了,我们总结出下面一个简易的加载流程图。


QQ截图20180831171920.png

总结

  • 1.Glide类作为入口类,调用静态方法with(),实例化了Glide这个单例,取得RequestManager对象并返回;
  • 2.RequestManager去管理Request请求,根据Activity或Fragment生命周期,管理request请求,包括:onStop,onStart,onDestory,pauseRequest,resumeRequests等,并可以设置加载资源要返回的资源类型,Bitmap,Drawable,Gif。通过调用load()、asBitmap()、asFile()等方法均返回一个RquestBuilder对象,将资源的请求处理移交给了RequestBuilder,实现将功能分离,模块化处理。
  • 3.RquestBuilder处理资源加载请求,调用apply()方法传入RequestOptions对象进行一些选项设置;调用transition()方法实现资源加载显示时过度动画;最后调用into()方法,去构建Rquest对象或使用Target设置过的Request对象,调用Rquest的对象方法begin()开启加载请求。
  • 4.开启请求后的工作就移交到Request类中,加载完成后,Request调用onResourceReady()方法去判断加载结果,若加载成功,调用Target类的onReadySuccess()方法设置资源,调用RequestCoordinator中方法onRequestSuccess()执行加载成功后的资源清理工作。若加载失败,Request直接调用notifyLoadFailed()方法,将清理资源工作移交给RequestCoordinator类处理。



参考资源:
Glide源码


Android图片加载框架Glide源码解析(一)
Android图片加载框架Glide源码解析(二)

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