Glide源码解析(一)

简介

由于在Android项目开发中我们经常会用到图片加载,你会选择什么第三库来加载图片,今天让我们来学习一下Glide图片加载库的源码吧,之前文章有讲解到Glide的简单使用

简单使用

这里就不说添加依赖那些了,大家可以看官方文档,或者我之前的文章Glide的简单使用,但是版本现在最新的版本是4.8.0
接下来我们看看Glide是如何加载图片的,如下代码

public class MainActivity extends AppCompatActivity {
    ImageView imageView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        imageView = findViewById(R.id.imageView);
        Glide.with(this).load("http://goo.gl/gEgYUd").into(imageView);
    }
}

我们通过上述代码中得到,Glide加载的使用方式是非常简单的。好了根据这句代码我们进行Glide的源码分析吧。

Glide.with(this).load("http://goo.gl/gEgYUd").into(imageView);

通过上述代码我们知道Glide中的with(this)调用的方法中携带了Activity或者Fragment的上下文对象其作用是:用于绑定该上下文对象的生命周期,如Activity或者Fragment的声明周期。代码如下:共有6个不同参数构造方法,返回静态的RequestManager,用于加载图片。

//contex对象
public static RequestManager with(@NonNull Context context) {
    return getRetriever(context).get(context);
  }
/**
   * Begin a load with Glide that will be tied to the given {@link android.app.Activity}'s lifecycle
   * and that uses the given {@link Activity}'s default options.
   *
   * @param activity The activity to use.
   * @return A RequestManager for the given activity that can be used to start a load. 用于启动加载的给定activity上下文的RequestManager
   */
public static RequestManager with(@NonNull Activity activity) {
    return getRetriever(activity).get(activity);
  }
//FragmentActivity 上下文对象
public static RequestManager with(@NonNull FragmentActivity activity) {
    return getRetriever(activity).get(activity);
  }
// fragment上下文对象
  public static RequestManager with(@NonNull Fragment fragment) {
    return getRetriever(fragment.getActivity()).get(fragment);
  }
 //V4的fragment对象
  public static RequestManager with(@NonNull android.app.Fragment fragment) {
    return getRetriever(fragment.getActivity()).get(fragment);
  }
 // view对象
  public static RequestManager with(@NonNull View view) {
    return getRetriever(view.getContext()).get(view);
  }

RequestManager是通过RequestManagerRetrieverget() 方法获取,而get() 方法中的参数就是我们上述代码传递过来的上下文对象。
然后getRetriever(Context context)方法返回的对象是RequestManagerRetriever,如下代码所示。

  private static RequestManagerRetriever getRetriever(@Nullable Context context) {
    // 这里通过方法checkNotNull() 校验携带的上下文是否为null,如果是null将抛出空指针异常
    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();
  }

如果校验上下文对象并无异常,则通过Glide.get(context).getRequestManagerRetriever();返回 RequestManagerRetriever对象。

//这里使用的是双重校验单例方式初始化Glide实例
public static Glide get(@NonNull Context context) {
    if (glide == null) {
      synchronized (Glide.class) {
        if (glide == null) {
          //检查并初始化Glide
          checkAndInitializeGlide(context);
        }
      }
    }
    return glide;
  }

在checkAndInitializeGlide(context)方法中的initializeGlide(context,builder)方法进行初始化,实例化唯一的Glide实例(双重校验单例方式获取)。

private static void initializeGlide(@NonNull Context context, @NonNull GlideBuilder builder) {
    Context applicationContext = context.getApplicationContext();//获取长连接上下文对象
    GeneratedAppGlideModule annotationGeneratedModule = getAnnotationGeneratedGlideModules();//通过注解方式生成GeneratedAppGlideModule
List<com.bumptech.glide.module.GlideModule> manifestModules = Collections.emptyList();
    if (annotationGeneratedModule == null || annotationGeneratedModule.isManifestParsingEnabled()) {
      //加载Glide Module    
      manifestModules = new ManifestParser(applicationContext).parse();
    }

    if (annotationGeneratedModule != null
        && !annotationGeneratedModule.getExcludedModuleClasses().isEmpty()) {
      Set<Class<?>> excludedModuleClasses =
          annotationGeneratedModule.getExcludedModuleClasses();
      Iterator<com.bumptech.glide.module.GlideModule> iterator = manifestModules.iterator();
//清除已经存在的Glide Module
      while (iterator.hasNext()) {
        com.bumptech.glide.module.GlideModule current = iterator.next();
        if (!excludedModuleClasses.contains(current.getClass())) {
          continue;
        }
        ...
        //清除
        iterator.remove();
      }
    }
     ...
    }
    RequestManagerRetriever.RequestManagerFactory factory =
        annotationGeneratedModule != null
            ? annotationGeneratedModule.getRequestManagerFactory() : null;
//设置Glide Builder 用于建造 Glide 
    builder.setRequestManagerFactory(factory);
    for (com.bumptech.glide.module.GlideModule module : manifestModules) {
      module.applyOptions(applicationContext, builder);
    }
    if (annotationGeneratedModule != null) {
      annotationGeneratedModule.applyOptions(applicationContext, builder);
    }
    //构建Glide  这步非常重要,接下来另一篇文章将重点分析这个模块!!!
    Glide glide = builder.build(applicationContext);
    for (com.bumptech.glide.module.GlideModule module : manifestModules) {
     //注册组件 此方法只调用一次
      module.registerComponents(applicationContext, glide, glide.registry);
    }
    if (annotationGeneratedModule != null) {
      annotationGeneratedModule.registerComponents(applicationContext, glide, glide.registry);
    }
    //上下文注册回调
    applicationContext.registerComponentCallbacks(glide);
   //设置glide,初始化Glide完成
    Glide.glide = glide;
  }

有上面我们知道要获取到RequestManager是通过Glide.get(context).getRequestManagerRetriever();调用get()方法。我们来看看getRequestManagerRetriever()方法。

//返回RequestManagerRetrieve对象
public RequestManagerRetriever getRequestManagerRetriever() {
    return requestManagerRetriever;
  }

现在我得到了RequestManagerRetrieve以后通过RequestManagerRetrieve.get(context)方法就可以获取到我们的RequestManager了。
这里需要注意的是get()方法下面有不同之处。FragmentActivity或者是``

@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);
  }
public RequestManager get(FragmentActivity activity) {
    if (Util.isOnBackgroundThread()) { ///当前是不是后台线程
      return get(activity.getApplicationContext()); //从新调用上边get()方法
    } else {
      assertNotDestroyed(activity);  //通过断言判断是否是已经销毁的上下文对象
      FragmentManager fm = activity.getSupportFragmentManager();
      return supportFragmentGet(
          activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
    }
  }

supportFragmentGet()方法得到RequestManager对象,当前Activity不能是Finished关闭状态。

// 获取RequestManager 对象
  private RequestManager supportFragmentGet(
      @NonNull Context context,
      @NonNull FragmentManager fm,
      @Nullable Fragment parentHint,
      boolean isParentVisible) {
//无界面的Fragment,即SupportRequestManagerFragment
    SupportRequestManagerFragment current =
        getSupportRequestManagerFragment(fm, parentHint, isParentVisible);
// 获取RequestManager
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
      //获取Glide 实例
      Glide glide = Glide.get(context);
      //通过RequestManagerFactory构建RequestManager。
      //current.getGlideLifecycle()获取生命周期,getRequestManagerTreeNode获取RequestManagerTreeNode
      requestManager =
          factory.build(
              glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
      current.setRequestManager(requestManager);
    }
    return requestManager;
  }

到这里我们已经得到了一个无界面的Fragment,即SupportRequestManagerFragment ,让请求和你的activity的生命周期同步。
接下来我们进行到调用load()方法中,看看发生什么情况。我们知道调用load()方法是RequestManager.class,所以我们进入该类发现该类实现了LifecycleListenerModelTypes<T>接口,而ModelTypes<T>是我们所需的load()方法的接口。

//表示回调是Activity生命周期的三个方法
public interface LifecycleListener {
  void onStart();
  void onStop();
  void onDestroy();
}

ModelTypes<T>接口刚好是定义了load()方法,如下

interface ModelTypes<T> {
   //bitmap参数类型
  T load(@Nullable Bitmap bitmap);
  //drawable参数类型
  T load(@Nullable Drawable drawable);
   //string参数类型
  T load(@Nullable String string);
   //uri参数类型
  T load(@Nullable Uri uri);
  //file参数类型
  T load(@Nullable File file);
   //resourceId参数类型
  T load(@RawRes @DrawableRes @Nullable Integer resourceId);
  //url参数类型
  T load(@Nullable URL url);
  //byte[]参数类型
  T load(@Nullable byte[] model);
  //Object 参数类型
  T load(@Nullable Object model);
}

RequestManager调用方法load()代码如下,返回的是RequestBuilder<Drawable>实例,该实例可以进行一些常用的操作,如占位符如:placeholder、error、fallback等方式接下来我们会进行分析该占位符。

 //返回一个请求的构建器RequestBuilder<Drawable>
  @Override  
  public RequestBuilder<Drawable> load(@Nullable String string) {
    return asDrawable().load(string);
  }

我们进入asDrawable()方法看看返回RequestBuilder<Drawable>

//默认情况下,可以返回android.graphics.drawable.BitmapDrawable或GifDrawable,但如果为其他Drawable子类注册了其他解码器,也可以返回任何子类。
public RequestBuilder<Drawable> asDrawable() {
    return as(Drawable.class);
  }
//用于加载给定资源类的新请求构建器:RequestBuilder<ResourceType>
public <ResourceType> RequestBuilder<ResourceType> as(
      @NonNull Class<ResourceType> resourceClass) {
    return new RequestBuilder<>(glide, this, resourceClass, context);
  }

得到RequestBuilder<ResourceType>以后调用load(),而该类实现的接口是ModelTypes<RequestBuilder<TranscodeType>>,这里区别于RequestManager实现的接口参数ModelTypes<RequestBuilder<Drawable>>,说明在这里已经进行了转码了,从Drawable发生了转码。

@Override
public RequestBuilder<TranscodeType> load(@Nullable String string) {
    return loadGeneric(string);
  }
@Override
  public RequestBuilder<TranscodeType> load(@Nullable Bitmap bitmap) {
    return loadGeneric(bitmap)
        .apply(diskCacheStrategyOf(DiskCacheStrategy.NONE)); //diskCacheStrategyOf(DiskCacheStrategy.NONE)表示磁盘缓存策略
//apply方法在load(@RawRes @DrawableRes @Nullable Integer resourceId) 、load(@Nullable Drawable drawable)、load(@Nullable Bitmap bitmap)调用
  }
//返回当前RequestBuilder<TranscodeType>对象
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
    this.model = model;
    isModelSet = true;
    return this;
  }

接下来我们进入into()方法。

public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
    Util.assertMainThread(); // 检查是否为后台线程也就是UI线程,必须在主线程调用Into()方法
    Preconditions.checkNotNull(view); //当前view是否为空,是空就报空指针异常。

    RequestOptions requestOptions = this.requestOptions;
    if (!requestOptions.isTransformationSet() 
        && requestOptions.isTransformationAllowed()
        && view.getScaleType() != null) {
    //在此方法中克隆,以便如果我们使用此RequestBuilder加载到View中,然后加载到不同的目标中,我们不会保留基于先前View的缩放类型应用的转换。
    //获取view的ScaleType
      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.
      }
    }
   //调用into(),返回ViewTarget<ImageView, TranscodeType> 对象
    return into(
        glideContext.buildImageViewTarget(view, transcodeClass),//构建ImageViewTarget,这个类扩展自BaseTarget.class
        /*targetListener=*/ null,
        requestOptions);
  }

buildImageViewTarget(view, transcodeClass)通过imageViewTargetFactory.buildTarget()工厂方法来返回ViewTarget<ImageView, X>

  public <X> ViewTarget<ImageView, X> buildImageViewTarget(
      @NonNull ImageView imageView, @NonNull Class<X> transcodeClass) {
    return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
  }

通过ImageViewTargetFactory工厂方法得到ViewTarget实例,这个过程为后面设置图片加载view.setImageBitmap(resource);使用。

public class ImageViewTargetFactory {
  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); //创建了BitmapImageViewTarget转换为ViewTarget
    } else if (Drawable.class.isAssignableFrom(clazz)) {
      return (ViewTarget<ImageView, Z>) new DrawableImageViewTarget(view);
    } else {
      throw new IllegalArgumentException(
          "Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)");
    } } }

然后进入上述的into()方法中,该方法构建了Request对象实例过程并进行加载图片。如下代码

//返回ViewTarget<ImageView, TranscodeType>
private <Y extends Target<TranscodeType>> Y into(
      @NonNull Y target,
      @Nullable RequestListener<TranscodeType> targetListener,
      @NonNull RequestOptions options) {
    Util.assertMainThread();
    Preconditions.checkNotNull(target);
    if (!isModelSet) { //是否调用load()方法标志
      throw new IllegalArgumentException("You must call #load() before calling #into()");
    }
    options = options.autoClone(); //设置自动克隆,加锁过程
//构造一个请求Request,通过构建请求递归方式返回Request,调用方法是buildRequestRecursive(), 构建缩略图buildThumbnailRequestRecursive()
//这个方法最终会调用到`SingleRequest.obtain()`并且初始化SingleRequest,而我们想要的Request对象就是SingleRequest返回的
    Request request = buildRequest(target, targetListener, options); 
    Request previous = target.getRequest();
    if (request.isEquivalentTo(previous)  //判断这个请求和之前的是否是一样的
        && !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {//如果之前已经保存在内存缓存了则跳过内存缓存
      request.recycle(); //回收request
   //如果请求完成,则再次开始将确保重新传递结果,触发RequestListeners和Targets。 如果请求失败,则再次开始将重新启动请求,
   //从而再次完成请求。 如果请求已在运行,我们可以让它继续运行而不会中断。
      if (!Preconditions.checkNotNull(previous).isRunning()) {
      //使用先前的请求而不是新请求来允许优化,例如跳过设置占位符,跟踪和取消跟踪目标,以及获取在单个请求中完成的视图维度。
        previous.begin(); //启动异步加载
      }
      return target;
    }
    requestManager.clear(target); //清除target对象
    target.setRequest(request); //设置此目标(View)保留的当前请求,不应在Glide外部调用
    requestManager.track(target, request); //追踪请求操作
    return target;
  }

通过上面代码注解我们知道Request初始化过程,并返回了SingleRequest对象。我们进入 requestManager.track(target, request)追踪方法,我要知道target是之前传递过来的View的对象转换而成的,在TargetTracker.class中实现了生命周期LifecycleListener接口,通过弱引用集合WeakHashMap保存了target对象(记得这个对象是View转换而成的),这里确保了在线程同步状态中执行。然后在requestTracker.runRequest(request);方法中启动了给定了请求request.begin();,在SingleRequest<R>.class中实现了该接口,如下。

@Override
  public void begin() {
    assertNotCallingCallbacks(); //是否允许回调,如果是true则抛出异常无法在RequestListener或Target回调中启动或清除加载...
    stateVerifier.throwIfRecycled();
    startTime = LogTime.getLogTime();
    if (model == null) {
      if (Util.isValidDimensions(overrideWidth, overrideHeight)) { //是否是有效尺寸
        width = overrideWidth;
        height = overrideHeight;
      }
      // 如果用户设置了回退可绘制,则仅记录更详细的日志级别,因为回退Drawable表示用户偶尔需要空模型。
      int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
      onLoadFailed(new GlideException("Received null model"), logLevel);
      return;
    }
    if (status == Status.RUNNING) {
      throw new IllegalArgumentException("Cannot restart a running request");
    }
    if (status == Status.COMPLETE) { //成功加载媒体
      onResourceReady(resource, DataSource.MEMORY_CACHE); // DataSource.MEMORY_CACHE表示从内存缓存中获取数据,该方法表示发布资源,并发布完成并设置为noll :engine.release(resource); Engine.class负责启动负载并管理活动和缓存资源  这个方法重点
      return;
    }
    status = Status.WAITING_FOR_SIZE; //等待给予目标的回调以确定目标尺寸
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
      onSizeReady(overrideWidth, overrideHeight); //目标图片大小读取,这个方法中开始调用了请求方法,追踪到Engine.load,设置一下缓存策略,并从磁盘,缓存中读取
    } 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));
    }}

从上述代码中成功加载的状态跟踪方法到这里,我们从开始的加载Resource的对象是ImageView通过转换为最终的target到这里我们将弄明白了这个加载情况,代码如下。

private void onResourceReady(Resource<R> resource, R result, DataSource dataSource) {
    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); //target最后调用加载资源文件读取
      }
    } finally {
      isCallingCallbacks = false;
    }
    notifyLoadSuccess(); //通知加载成功!
  }

我们回再到into()方法中,由于ImageViewTarget<Z>扩展自抽象类ViewTarget<ImageView, Z>ViewTarget<ImageView, Z>扩展自BaseTarget<Z>该父类实现了Target<Z>接口,所以ImageViewTarget<Z>重写了该方法,而DrawableImageViewTarget.class扩展自ImageViewTarget<Z>,这样最后将会调用setResource()方法

@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) {
    setResource(resource);
    maybeUpdateAnimatable(resource);
  }
public class DrawableImageViewTarget extends ImageViewTarget<Bitmap> {
@Override
  protected void setResource(Bitmap resource) {
    view.setImageBitmap(resource);
  }
}

总结

接下来我们将细分模块进行讲解,本篇只是大致的讲解Glide加载的过程,知道加载完成的步骤,还有某些重要的模块这里没有讲解到。如占位符、缓存、变化等等,下篇见分晓。

推荐

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

推荐阅读更多精彩内容