Glide V4 源码浅析(下)-- 细节分析功能模块的实现

在上篇我们主要分析了图片加载的主流程,但是Glide框架的某些优势我们还未进行分析,本篇就针对Glide的这些优势时如何实现的进行分析。

细节分析实现

1 Glide如何实现生命周期管理

我们在上篇提到在Glide.with()时,Glide会对传入的参数进行封装处理,我们继续以Activity为例。

  @SuppressWarnings("deprecation")
  @NonNull
  public RequestManager get(@NonNull Activity activity) {
    if (Util.isOnBackgroundThread()) {
      return get(activity.getApplicationContext());
    } else {
      assertNotDestroyed(activity);
      android.app.FragmentManager fm = activity.getFragmentManager();
      //获取FragmentManager后将activity和fm作为参数传入fragmentGet方法
      return fragmentGet(
          activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
    }
  }
  
  @NonNull
  private RequestManager fragmentGet(@NonNull Context context,
      @NonNull android.app.FragmentManager fm,
      @Nullable android.app.Fragment parentHint,
      boolean isParentVisible) {
    //初始化一个RequestManagerFragment
    RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
      Glide glide = Glide.get(context);
      //创建requestManager时将RequestManagerFragment的GlideLifecycle作为参数传递了进去
      requestManager =
          factory.build(
              glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
      current.setRequestManager(requestManager);
    }
    return requestManager;
  }
  
    private RequestManagerFragment getRequestManagerFragment(
      @NonNull final android.app.FragmentManager fm,
      @Nullable android.app.Fragment parentHint,
      boolean isParentVisible) {
    RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
    //判断释放存在已生成的RequestManagerFragment,如果已生成则直接返回,否则直接进行初始化
    if (current == null) {
      current = pendingRequestManagerFragments.get(fm);
      if (current == null) {
        //初始化一个RequestManagerFragment
        current = new RequestManagerFragment();
        current.setParentFragmentHint(parentHint);
        //根据isParentVisible(即传入activity的显示状态)确定是否要直接调用GlideLifecycle的onStart()方法
        if (isParentVisible) {
          current.getGlideLifecycle().onStart();
        }
        pendingRequestManagerFragments.put(fm, current);
        fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
        handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();
      }
    }
    return current;
  }

从上文可以看到Glide在生成requestManager时首先生成了一个RequestManagerFragment,而这个RequestManagerFragment是一个无视图的Fragment,用于启动,停止和管理Glide请求。在这个Fragment中初始化了一个ActivityFragmentLifecycle。它便是Glide进行生命周期管理的一个关键。

public class RequestManagerFragment extends Fragment {
  private final ActivityFragmentLifecycle lifecycle;
  //省略部分代码
  ......
  public RequestManagerFragment() {
    this(new ActivityFragmentLifecycle());
  }
  //省略部分代码
  ......
  
}

而这个ActivityFragmentLifecycle就是是用于追踪Activity或者fragment的生命周期的。代码如下

class ActivityFragmentLifecycle implements Lifecycle {
  private final Set<LifecycleListener> lifecycleListeners =
      Collections.newSetFromMap(new WeakHashMap<LifecycleListener, Boolean>());
  private boolean isStarted;
  private boolean isDestroyed;
  @Override
  public void addListener(@NonNull LifecycleListener listener) {
    lifecycleListeners.add(listener);

    if (isDestroyed) {
      listener.onDestroy();
    } else if (isStarted) {
      listener.onStart();
    } else {
      listener.onStop();
    }
  }

  @Override
  public void removeListener(@NonNull LifecycleListener listener) {
    lifecycleListeners.remove(listener);
  }

  void onStart() {
    isStarted = true;
    for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {
      lifecycleListener.onStart();
    }
  }

  void onStop() {
    isStarted = false;
    for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {
      lifecycleListener.onStop();
    }
  }

  void onDestroy() {
    isDestroyed = true;
    for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {
      lifecycleListener.onDestroy();
    }
  }
}

由代码可以看出,这个类是一个lifecycleListener的管理类,用于在状态改变时通知这些lifecycleListeners。而这个类会在RequestManager.build()时作为参数传递进去

  RequestManager(
      Glide glide,
      Lifecycle lifecycle,
      RequestManagerTreeNode treeNode,
      RequestTracker requestTracker,
      ConnectivityMonitorFactory factory,
      Context context) {
    this.glide = glide;
    this.lifecycle = lifecycle;
    this.treeNode = treeNode;
    this.requestTracker = requestTracker;
    this.context = context;

    //在这里RequestManager,将自身作为callback传入
    } else {
      lifecycle.addListener(this);
    }
    lifecycle.addListener(connectivityMonitor);

    setRequestOptions(glide.getGlideContext().getDefaultRequestOptions());

    glide.registerRequestManager(this);
  }
  
  @Override
  public void onStart() {
    resumeRequests();
    targetTracker.onStart();
  }

  @Override
  public void onStop() {
    pauseRequests();
    targetTracker.onStop();
  }

  @Override
  public void onDestroy() {
    targetTracker.onDestroy();
    for (Target<?> target : targetTracker.getAll()) {
      clear(target);
    }
    targetTracker.clear();
    requestTracker.clearRequests();
    lifecycle.removeListener(this);
    lifecycle.removeListener(connectivityMonitor);
    mainHandler.removeCallbacks(addSelfToLifecycle);
    glide.unregisterRequestManager(this);
  }

可以看到这样一来,当ActivityFragmentLifecycle状态改变时就会通知到RequestManager。而RequestManager便会在相应的回调方法中来控制targetTracker和requestTracker。而这两个类我们在上篇中以及提到了,分别用来控制viewTarget和requests,而这两者又是对要显示的view和相应的加载请求的封装。而最后在我们getRequestManagerFragment()方法中生成的RequestManagerFragment,会在生命周期发生变化时通知ActivityFragmentLifecycle。

  @Override
  public void onStart() {
    super.onStart();
    lifecycle.onStart();
  }

  @Override
  public void onStop() {
    super.onStop();
    lifecycle.onStop();
  }

  @Override
  public void onDestroy() {
    super.onDestroy();
    lifecycle.onDestroy();
    unregisterFragmentWithRoot();
  }

这样以来便可以实现通过生命周期来控制图片加载的功能了。

2 Glide如何实现缓存

Glide的缓存也是一个经常被人称道的地方,我们一般都知道内存-文件-网络三级缓存,那么Glide又是如何实现缓存的呢?

2.1 内存缓存

在上文提到过Glide调用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 = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;

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

    //尝试获取ActiveResources
    EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
    if (active != null) {
      cb.onResourceReady(active, DataSource.MEMORY_CACHE);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }
      return null;
    }

    //尝试获取CacheResources
    EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
      cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }
      return null;
    }
    
    //省略部分代码
    .....
  }

首先获取第一阶段的内存缓存ActiveResources

  @Nullable
  private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
    //不允许读取缓存直接返回null
    if (!isMemoryCacheable) {
      return null;
    }
    //根据key从activeResources中获取对应的EngineResource
    EngineResource<?> active = activeResources.get(key);
    if (active != null) {
      active.acquire();
    }
    return active;
  }

这里我们可以看到读取缓存资源时时会从activeResources中查找,而activeResources是一个使用HashMap和WeakReference对EngineResource进行保存的管理类。它的代码逻辑如下:

final class ActiveResources {
  private static final int MSG_CLEAN_REF = 1;

  private final boolean isActiveResourceRetentionAllowed;
  private final Handler mainHandler = new Handler(Looper.getMainLooper(), new Callback() {
    @Override
    public boolean handleMessage(Message msg) {
      if (msg.what == MSG_CLEAN_REF) {
        cleanupActiveReference((ResourceWeakReference) msg.obj);
        return true;
      }
      return false;
    }
  });
  //用于保存EngineResource的HashMap
  @VisibleForTesting
  final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();

  private ResourceListener listener;

  @Nullable
  private ReferenceQueue<EngineResource<?>> resourceReferenceQueue;
  @Nullable
  private Thread cleanReferenceQueueThread;
  private volatile boolean isShutdown;
  @Nullable
  private volatile DequeuedResourceCallback cb;

  ActiveResources(boolean isActiveResourceRetentionAllowed) {
    this.isActiveResourceRetentionAllowed = isActiveResourceRetentionAllowed;
  }

  void setListener(ResourceListener listener) {
    this.listener = listener;
  }

  //添加对应键值对
  void activate(Key key, EngineResource<?> resource) {
    ResourceWeakReference toPut =
        new ResourceWeakReference(
            key,
            resource,
            getReferenceQueue(),
            isActiveResourceRetentionAllowed);

    ResourceWeakReference removed = activeEngineResources.put(key, toPut);
    if (removed != null) {
      removed.reset();
    }
  }

  //移除对应键值对
  void deactivate(Key key) {
    ResourceWeakReference removed = activeEngineResources.remove(key);
    if (removed != null) {
      removed.reset();
    }
  }

  //get方法
  @Nullable
  EngineResource<?> get(Key key) {
    //先从HashMap中获取ResourceWeakReference
    ResourceWeakReference activeRef = activeEngineResources.get(key);
    if (activeRef == null) {
      return null;
    }
    //再从ResourceWeakReference中获取对应的EngineResource
    EngineResource<?> active = activeRef.get();
    //当发现此弱引用不存在时进去清除逻辑
    if (active == null) {
      cleanupActiveReference(activeRef);
    }
    return active;
  }

  //清除对应的弱引用
  @SuppressWarnings("WeakerAccess")
  @Synthetic void cleanupActiveReference(@NonNull ResourceWeakReference ref) {
    Util.assertMainThread();
    //hashmap中移除此索引
    activeEngineResources.remove(ref.key);

    //判断该ResourceWeakReference对应的资源文件是否还存在
    if (!ref.isCacheable || ref.resource == null) {
      return;
    }
    //创建新的EngineResource
    EngineResource<?> newResource =
        new EngineResource<>(ref.resource, /*isCacheable=*/ true, /*isRecyclable=*/ false);
    newResource.setResourceListener(ref.key, listener);
    //通知监听器
    listener.onResourceReleased(ref.key, newResource);
  }

  private ReferenceQueue<EngineResource<?>> getReferenceQueue() {
    if (resourceReferenceQueue == null) {
      resourceReferenceQueue = new ReferenceQueue<>();
      cleanReferenceQueueThread = new Thread(new Runnable() {
        @SuppressWarnings("InfiniteLoopStatement")
        @Override
        public void run() {
          Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
          cleanReferenceQueue();
        }
      }, "glide-active-resources");
      cleanReferenceQueueThread.start();
    }
    return resourceReferenceQueue;
  }

  //清理资源引用队列
  @SuppressWarnings("WeakerAccess")
  @Synthetic void cleanReferenceQueue() {
    while (!isShutdown) {
      try {
        ResourceWeakReference ref = (ResourceWeakReference) resourceReferenceQueue.remove();
        mainHandler.obtainMessage(MSG_CLEAN_REF, ref).sendToTarget();

        // This section for testing only.
        DequeuedResourceCallback current = cb;
        if (current != null) {
          current.onResourceDequeued();
        }
        // End for testing only.
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
      }
    }
  }

  @VisibleForTesting
  void setDequeuedResourceCallback(DequeuedResourceCallback cb) {
    this.cb = cb;
  }

  @VisibleForTesting
  interface DequeuedResourceCallback {
    void onResourceDequeued();
  }

  //停止cleanReferenceQueueThread线程,中断操作
  @VisibleForTesting
  void shutdown() {
    isShutdown = true;
    if (cleanReferenceQueueThread == null) {
      return;
    }

    cleanReferenceQueueThread.interrupt();
    try {
      cleanReferenceQueueThread.join(TimeUnit.SECONDS.toMillis(5));
      if (cleanReferenceQueueThread.isAlive()) {
        throw new RuntimeException("Failed to join in time");
      }
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
    }
  }

  //弱引用对象,但是对于资源文件Resource却是直接用强引用进行保存的
  @VisibleForTesting
  static final class ResourceWeakReference extends WeakReference<EngineResource<?>> {
    @SuppressWarnings("WeakerAccess") @Synthetic final Key key;
    @SuppressWarnings("WeakerAccess") @Synthetic final boolean isCacheable;

    @Nullable @SuppressWarnings("WeakerAccess") @Synthetic Resource<?> resource;

    @Synthetic
    @SuppressWarnings("WeakerAccess")
    ResourceWeakReference(
        @NonNull Key key,
        @NonNull EngineResource<?> referent,
        @NonNull ReferenceQueue<? super EngineResource<?>> queue,
        boolean isActiveResourceRetentionAllowed) {
      super(referent, queue);
      this.key = Preconditions.checkNotNull(key);
      this.resource =
          referent.isCacheable() && isActiveResourceRetentionAllowed
              ? Preconditions.checkNotNull(referent.getResource()) : null;
      isCacheable = referent.isCacheable();
    }

    //清除自身
    void reset() {
      resource = null;
      clear();
    }
  }
}

从上文代码中可以看出,当我们通过ActiveResources的activate方法加入一个新的键值对时,会创建一个ResourceWeakReference用于保存key和resource,而同时会调用getReferenceQueue()方法,在这个方法中会启动一个后台线程cleanReferenceQueueThread,在此线程中会进入一个循环,当弱引用被回收时,就会将通知handle,调用cleanupActiveReference方法,而此时由于ResourceWeakReference中保存key和resource是通过强引用的方式进行保存的,所以可以取出对应的resource,创建新的EngineResource,并调用listener.onResourceReleased()方法,ActiveResources的listener就是Engine,因此我们可以看到Engine的onResourceReleased方法

  @Override
  public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    Util.assertMainThread();
    //清除对应的key,并解除对资源的强引用
    activeResources.deactivate(cacheKey);
    if (resource.isCacheable()) {
      //将传入的key、resource放入新的cache中
      cache.put(cacheKey, resource);
    } else {
      resourceRecycler.recycle(resource);
    }
  }

此时的cache为LruResourceCache,是在Glide创建过程中进行初始化的

  Glide build(@NonNull Context context) {
    //省略部分代码
    ......
    if (memoryCache == null) {
      memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
    }
    //省略部分代码
    ......
  }

LruResourceCache用于在ActiveResources未命中时进行缓存查找,即上文中调用到的loadFromCache()方法

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

    //获取CacheResource
    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
      //引用加一
      cached.acquire();
      //将其添加到ActiveResources
      activeResources.activate(key, cached);
    }
    return cached;
  }
  
  
  private EngineResource<?> getEngineResourceFromCache(Key key) {
    //获取资源文件,并在cache中移除
    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;
  }

LruResourceCache本身是LruCache的子类,并且实现MemoryCache接口(用于添加和移除资源),其中LRU实现也是通过LinkedHashMap来实现的。
至此Glide的内存缓存已经结束了,可以看到Glide设计了两层缓存,分别是ActiveResources和LruResourceCache,用弱引用和Lru来进行资源的保存,并且它们之间有相互添加的过程。

2.2 文件缓存

上篇中提到了,当一个任务未能从内存缓存中获取相应的资源时,会启动DecodeJob来执行加载任务,而在第一次启动DecodeJob时runReason为INITIALIZE,此时会尝试从文件内存中获取对应资源

  private void runWrapped() {
    switch (runReason) {
      case INITIALIZE://初始
        stage = getNextStage(Stage.INITIALIZE);
        currentGenerator = getNextGenerator();
        runGenerators();
        break;
      case SWITCH_TO_SOURCE_SERVICE://从CACHE跳转到SOURCE
        runGenerators();
        break;
      case DECODE_DATA:
        decodeFromRetrievedData();//DECODE_DATA
        break;
      default:
        throw new IllegalStateException("Unrecognized run reason: " + runReason);
    }
  }

其中主要的标识位为Stage,Stage有6种状态

  private enum Stage {
    //初始状态
    INITIALIZE,
    //裁剪图缓存
    RESOURCE_CACHE,
    //原图缓存
    DATA_CACHE,
    //原图地址
    SOURCE,
    //解析
    ENCODE,
    //完成
    FINISHED,
  }
  
  private Stage getNextStage(Stage current) {
    switch (current) {
      case INITIALIZE:
        //判断是否支持使用裁剪图缓存若支持则返回状态为RESOURCE_CACHE,否则接着进行状态查询
        return diskCacheStrategy.decodeCachedResource()
            ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
      case RESOURCE_CACHE:
        //判断是否支持使用原图缓存若支持则返回状态为DATA_CACHE,否则接着进行状态查询
        return diskCacheStrategy.decodeCachedData()
            ? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
      case DATA_CACHE:
        //如果该任务只允许从缓存中读取,则返回状态为FINISHED,否则就是SOURCE
        return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
      case SOURCE:
      case FINISHED:
        return Stage.FINISHED;
      default:
        throw new IllegalArgumentException("Unrecognized stage: " + current);
    }
  }

当确认了状态后会根据stage获取对应的currentGenerator,

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

而各种Generator中会调用对应的缓存信息来获取对应的资源,从而完成文件缓存。

至此我们分析了在上篇中未提到的部分功能的实现细节,由于Glide是一个十分完善的图片加载库,因此还有许多功能并未进行分析。
本篇文章是个人学习的总结,本人能力有限,如果有错误欢迎斧正,谢谢。

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

推荐阅读更多精彩内容