AndroidX的內(nèi)置對象池

我們經(jīng)常會遇到使用對象復(fù)用的場景,比如線程池病毡,數(shù)據(jù)庫連接池啦膜,Android的消息機(jī)制里面的Message的通過靜態(tài)方法obtain()獲取消息,EventBus在的解析監(jiān)聽事件的訂閱者對象里的標(biāo)有@subscribe的方法而引入的SubscriberMethodFinder.FindState對象池FIND_STATE_POOL,Glide在請求加載圖像的時候EngineJob內(nèi)部的Pools.Pool<EngineJob<?>> pool和解碼DecodeJob內(nèi)部的Pools.Pool<DecodeJob<?>> pool僧家。因為對象創(chuàng)建的開銷過大八拱,為了避免每一次用到某個對象的時候都去new一個新的對象涯塔。對象池提供了這樣一種機(jī)制爹谭,當(dāng)我們需要某個對象的時候诺凡,我們希望先嘗試從對象池(相當(dāng)于緩存)中獲取,如果有就直接返回嘶卧,此時對象池應(yīng)該空一個位子出來(即對象池的元素個數(shù)減一),沒有則新創(chuàng)建一個對象返回穷娱,當(dāng)對象用完以后绑蔫,如果對象池還有空余位置則存放入其中同時對象池中元素的個數(shù)+1。
如何實現(xiàn)一個對象池呢泵额?軟件開發(fā)中經(jīng)常說的一句話是面向接口而不是面向?qū)崿F(xiàn)編程配深,因此將對象池抽象成一個接口。由于接口是功能的集合嫁盲,對象池應(yīng)該對外提供哪些功能呢篓叶?既然是對象池,那么它必然有一個獲取對象和回收對象的方法羞秤。由于我們希望它是一個通用型的接口缸托,對任意的類型都適用,任意類型都能實現(xiàn)獲取和回收,因此需要用到泛型棍苹,我們用Pool來表示對象池,用acquire來表示獲取對象,用recycle來表示回收對象,于是就有可以得到下面這個接口皮迟。

public interface Pool<T> {
  //從對象池中獲取對象
  T acquire();
  //將不需要的對象放回對象池,返回值boolen類型表示是否回收成功
  boolean recycle(T t);
}

有了上面的接口,我們就來嘗試何實現(xiàn)一個簡單的對象池辨图。既然是對象池吆豹,必然容量是有限的,同時必須大于0晌砾,如何存放對象呢都伪,最簡單的方式是用數(shù)組。我們獲取對象的時候就查詢一下這個數(shù)組湿刽,看看其中有沒有可用的元素铃芦,有就返回此元素同時將該處位原來的元素置空耸弄,不然會出現(xiàn)內(nèi)存泄漏,沒有就返回null∥辏回收對象的時候先遍歷一下數(shù)組是不是已經(jīng)填滿了贪薪,沒有填滿囱怕,就放入數(shù)組中,于是有下面的簡單實現(xiàn)。

 public class SimplePool<T> implements Pool<T> {
        private final Object[] mElements;
        private final int mMaxPoolSize;
        public SimplePool(int maxPoolSize) {
            if (maxPoolSize <= 0) {
                throw new IllegalArgumentException("The max pool size must be > 0");
            }
            mMaxPoolSize = maxPoolSize;
            mElements = new Object[maxPoolSize];
        }

        @Override
        public T acquire() {
            for (int i = 0; i < mMaxPoolSize; i++) {
                if (mElements[i] != null) {
                    T ele = (T) mElements[i];
                    mElements[i] = null;//置空侠坎,避免內(nèi)存泄漏
                    return ele;
                }
            }
           return null;
        }

        @Override
        public boolean recycle(T t) {
            for (int i = 0; i < mMaxPoolSize; i++) {
                if (mElements[i] == null) {
                    mElements[i] = t;
                    return true;
                }
            }
            return false;
        }
    }

有了這個簡單的設(shè)計之后闷煤,就可以使用了近顷,一般都是將這個類簡單的設(shè)計在某個工具類中使用,我們不妨定義一個工具類叫PoolUtils,定義要復(fù)用的對象類型為HeavyObject,如下所示:

class HeavyObject {
    public HeavyObject (){
      //開銷大的操作
     。。盐须。女坑。
    }
}

class PoolUtils {
  private static final Pool<HeavyObject> sPool = new SimplePool<>(10);
  //獲取對象
  public static HeavyObject obtain() {
    HeavyObject obj = sPool.acquire();
    return obj != null ? obj : new HeavyObject(); //沒有就創(chuàng)建新的
  }
  //回收對象
  public static boolean recycle(HeavyObject object) {
   return sPool.recycle(object);
  }
}

在回頭看看我們實現(xiàn)的SimplePool設(shè)計得如何呢?emmm,我們發(fā)現(xiàn)aquirerecycle方法總會從從頭到尾到數(shù)組的遍歷。假如我們的緩存數(shù)組mElements的大小為n,開始時數(shù)組里面其實并沒有元素,此時aquire方法的時間復(fù)雜度讀為O(n)(并且還沒有獲取到元素),recycle方法的時間復(fù)雜度為O(1)(放在下標(biāo)為0的位置),而當(dāng)數(shù)組mElements中填滿元素后叮叹,aquire方法的時間復(fù)雜度讀為O(1)(返回下標(biāo)為0處的元素)悼粮,recycle方法的時間復(fù)雜度為O(n)(遍歷完數(shù)組后仍然不能存放)。我們希望aquirerecycle方法無論在mElements是否有數(shù)據(jù)的情況下都能做到時間復(fù)雜度為O(1)臭杰,那么如何做到這一點呢磁奖?
不難通過分析發(fā)現(xiàn)蜜托,aquirerecycle方法只要記住上次存取的位置就行了,我們將元素連續(xù)存放,比如上次是在下標(biāo)為i處存放了元素(下標(biāo)從0~i都有數(shù)據(jù)),那么aquire則從i處獲取,recycle方法則在i+1處存放充蓝,只要i+1<n.實際上i+1即為緩存池里面已經(jīng)存放的元素的個數(shù)卑笨,于是我們引入一個變量mPoolSize用于記錄緩存池里面已經(jīng)存放的元素個數(shù)。對SimplePool做一次修改。

    public class SimplePool<T> implements Pool<T> {
        private final Object[] mElements;
        private final int mMaxPoolSize;
        private int mPoolSize;

        public SimplePool(int maxPoolSize) {
            if (maxPoolSize <= 0) {
                throw new IllegalArgumentException("The max pool size must be > 0");
            }
            mMaxPoolSize = maxPoolSize;
            mElements = new Object[maxPoolSize];
        }

        @Override
        public T acquire() {
            if (mPoolSize > 0) {
                int lastPooledIndex = mPoolSize - 1;
                T t= (T) mElements[lastPooledIndex];
                mElements[lastPooledIndex] = null;//置空,避免內(nèi)存泄漏
                mPoolSize--;
                return t;
            }
            return null;
        }

        @Override
        public boolean recycle(T t) {
            if (mPoolSize < mMaxPoolSize) {
                mElements[mPoolSize] = t;
                mPoolSize++;
                return true;
            }
            return false;
        }
    }

通過重構(gòu)袁滥,現(xiàn)在的acquirerecycle方法只需要做一次判斷塑荒,而不用從頭開始遍歷數(shù)組,因此時間復(fù)雜度都成了O(1).
看起來一切都很完美了他托,但是事情好像并沒有那么簡單纫溃,在多線程的情況下,比如某一時刻A線程正在執(zhí)行aquire操作,B線程正在執(zhí)行recycle操作很容易出現(xiàn)很容出現(xiàn)數(shù)據(jù)不一致的的安全性問題小染÷梢蹋看來我們得考慮一下線程安全的問題了。
為了能給用戶提供一個分別線程安全和線程不安全的對象池富蓄,我們將線程安全的對象池的實現(xiàn)命名為SynchronizedPool,并給出實現(xiàn)

public class SynchronizedPool<T> implements Pool<T> {
        private final Object mLock = new Object();
        private final Object[] mElements;
        private final int mMaxPoolSize;
        private int mPoolSize;

        public SynchronizedPool(int maxPoolSize) {
            if (maxPoolSize <= 0) {
                throw new IllegalArgumentException("The max pool size must be > 0");
            }
            mMaxPoolSize = maxPoolSize;
            mElements = new Object[maxPoolSize];
        }

        @Override
        public T acquire() {
            synchronized (mLock) {
                if (mPoolSize > 0) {
                    final int lastPooledIndex = mPoolSize - 1;
                    T instance = (T) mElements[lastPooledIndex];
                    mElements[lastPooledIndex] = null;//置空娇斑,避免內(nèi)存泄漏
                    mPoolSize--;
                    return instance;
                }
                return null;
            }
        }

        @Override
        public boolean recycle(T t) {
            synchronized (mLock) {
                if (mPoolSize < mMaxPoolSize) {
                    mElements[mPoolSize] = t;
                    mPoolSize++;
                    return true;
                }
                return false;
            }
        }
    }

于是我們能夠提供了一個用戶自己選擇的對象池創(chuàng)建工廠方法,,讓用戶自定義是需要線程安全的還是線程不安全的版本:

class PoolFactory {
  public static <T> Pool<T> createPool(boolean threadSafe, int maxPoolSize) {
            return threadSafe ? new SynchronizedPool<T>(maxPoolSize) : new SimplePool<T>(maxPoolSize);
    }
 }

看到到這里讀者會問姻僧,這和標(biāo)題有什么關(guān)系?實際上androidX已經(jīng)提供了一個內(nèi)置的對象復(fù)用池遵倦,如下所示

package androidx.core.util;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

/**
 * Helper class for creating pools of objects. An example use looks like this:
 * <pre>
 * public class MyPooledClass {
 *
 *     private static final SynchronizedPool<MyPooledClass> sPool =
 *             new SynchronizedPool<MyPooledClass>(10);
 *
 *     public static MyPooledClass obtain() {
 *         MyPooledClass instance = sPool.acquire();
 *         return (instance != null) ? instance : new MyPooledClass();
 *     }
 *
 *     public void recycle() {
 *          // Clear state if needed.
 *          sPool.release(this);
 *     }
 *
 *     . . .
 * }
 * </pre>
 *
 */
public final class Pools {

    /**
     * Interface for managing a pool of objects.
     *
     * @param <T> The pooled type.
     */
    public interface Pool<T> {

        /**
         * @return An instance from the pool if such, null otherwise.
         */
        @Nullable
        T acquire();

        /**
         * Release an instance to the pool.
         *
         * @param instance The instance to release.
         * @return Whether the instance was put in the pool.
         *
         * @throws IllegalStateException If the instance is already in the pool.
         */
        boolean release(@NonNull T instance);
    }

    private Pools() {
        /* do nothing - hiding constructor */
    }

    /**
     * Simple (non-synchronized) pool of objects.
     *
     * @param <T> The pooled type.
     */
    public static class SimplePool<T> implements Pool<T> {
        private final Object[] mPool;

        private int mPoolSize;

        /**
         * Creates a new instance.
         *
         * @param maxPoolSize The max pool size.
         *
         * @throws IllegalArgumentException If the max pool size is less than zero.
         */
        public SimplePool(int maxPoolSize) {
            if (maxPoolSize <= 0) {
                throw new IllegalArgumentException("The max pool size must be > 0");
            }
            mPool = new Object[maxPoolSize];
        }

        @Override
        @SuppressWarnings("unchecked")
        public T acquire() {
            if (mPoolSize > 0) {
                final int lastPooledIndex = mPoolSize - 1;
                T instance = (T) mPool[lastPooledIndex];
                mPool[lastPooledIndex] = null;//置空,避免內(nèi)存泄漏
                mPoolSize--;
                return instance;
            }
            return null;
        }

        @Override
        public boolean release(@NonNull T instance) {
            if (isInPool(instance)) {
                throw new IllegalStateException("Already in the pool!");
            }
            if (mPoolSize < mPool.length) {
                mPool[mPoolSize] = instance;
                mPoolSize++;
                return true;
            }
            return false;
        }

        private boolean isInPool(@NonNull T instance) {
            for (int i = 0; i < mPoolSize; i++) {
                if (mPool[i] == instance) {
                    return true;
                }
            }
            return false;
        }
    }

    /**
     * Synchronized) pool of objects.
     *
     * @param <T> The pooled type.
     */
    public static class SynchronizedPool<T> extends SimplePool<T> {
        private final Object mLock = new Object();

        /**
         * Creates a new instance.
         *
         * @param maxPoolSize The max pool size.
         *
         * @throws IllegalArgumentException If the max pool size is less than zero.
         */
        public SynchronizedPool(int maxPoolSize) {
            super(maxPoolSize);
        }

        @Override
        public T acquire() {
            synchronized (mLock) {
                return super.acquire();
            }
        }

        @Override
        public boolean release(@NonNull T element) {
            synchronized (mLock) {
                return super.release(element);
            }
        }
    }
}

通過對比我們的設(shè)計和androidX的設(shè)計,發(fā)現(xiàn)androidX的SimplePool多了一個isInPool方法,也就是校驗是否復(fù)用池里面已經(jīng)有相同的對象了雀瓢,如果存在就沒有必要再放入一個元素,不然也就失去了復(fù)用的意義。同時SynchronizedPool的方法的實現(xiàn)是繼承了SimplePool垦垂,只是在復(fù)寫acquirerelease方法的時候加了一個鎖胁附,這樣代碼的復(fù)用率更高了。
如果讀者有看過Glide源碼的話他匪,會發(fā)現(xiàn)Glide我們前面的PoolFactory上面更進(jìn)了一步,它提供了將工廠模式和對象池復(fù)用的精妙操作杯缺,一起來學(xué)習(xí)一下包雀。
定義一個工廠接口Factory

    /**
     * Creates new instances of the given type.
     * 創(chuàng)建指定類型的新的實例
     *
     * @param <T> The type of Object that will be created.
     */
    public interface Factory<T> {
        T create();
    }

定義了一個對象被放回對象池時可能進(jìn)行重置狀態(tài)操作的接口Resetter

      /**
     * Resets state when objects are returned to the pool.
     * 當(dāng)對象放回復(fù)用池時重置狀態(tài)
     *
     * @param <T> The type of Object that will be reset.
     */
    public interface Resetter<T> {
        void reset(@NonNull T object);
    }

定義了處理回收標(biāo)記的抽象類StateVerifier

    public abstract class StateVerifier {

        /**
         * Throws an exception if we believe our object is recycled and inactive (i.e. is currently in an
         * object pool).
         * 如果我們確信我們的對象已經(jīng)被回收并且不再處于活躍狀態(tài)(例如當(dāng)前在對象池)時拋出異常
         */
        public abstract void throwIfRecycled();

        /** 
         * Sets whether or not our object is recycled. 
         * 標(biāo)記對象是否已經(jīng)被回收
         * */
        abstract void setRecycled(boolean isRecycled);
        
    }

定義了需要可回收時的校驗接口Poolable

   /**
     * Allows additional verification to catch errors caused by using objects while they are in an
     * object pool.
     * 允許額外的校驗來捕獲因使用在對象池的對象而產(chǎn)生的異常岖沛,
     */
    public interface Poolable {
        @NonNull
        StateVerifier getVerifier();
    }

將上面的整合一下

  private static final class FactoryPool<T> implements Pool<T> {
    private final Factory<T> factory;
    private final Resetter<T> resetter;
    private final Pool<T> pool;

    FactoryPool(@NonNull Pool<T> pool, @NonNull Factory<T> factory, @NonNull Resetter<T> resetter) {
      this.pool = pool;
      this.factory = factory;
      this.resetter = resetter;
    }

    @Override
    public T acquire() {
      T result = pool.acquire();//先從對象池中獲取
      if (result == null) {
        result = factory.create();//沒獲取到則通過工廠方法來創(chuàng)建
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
          Log.v(TAG, "Created new " + result.getClass());
        }
      }
      if (result instanceof Poolable) {
        ((Poolable) result).getVerifier().setRecycled(false /*isRecycled*/);//標(biāo)記不在對象池中
      }
      return result;
    }

    @Override
    public boolean release(@NonNull T instance) {
      if (instance instanceof Poolable) {
        ((Poolable) instance).getVerifier().setRecycled(true /*isRecycled*/);//標(biāo)記在復(fù)用池中
      }
      resetter.reset(instance);//重置狀態(tài)的相關(guān)操作
      return pool.release(instance);//復(fù)用池復(fù)用
    }
  }

再對外提供工廠方法

package com.bumptech.glide.util.pool;

import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.util.Pools.Pool;
import androidx.core.util.Pools.SimplePool;
import androidx.core.util.Pools.SynchronizedPool;
import java.util.ArrayList;
import java.util.List;
/**
 * Provides implementations of {@link Pool} never return {@code null}, log when new instances are
 * created, and that can use the {@link com.bumptech.glide.util.pool.FactoryPools.Poolable}
 * interface to ensure objects aren't used while inside the pool.
 */
public final class FactoryPools {
  private static final String TAG = "FactoryPools";
  private static final int DEFAULT_POOL_SIZE = 20;
  private static final Resetter<Object> EMPTY_RESETTER =
      new Resetter<Object>() {
        @Override
        public void reset(@NonNull Object object) {
          // Do nothing.
        }
      };

  private FactoryPools() {}

  /**
   * Returns a non-thread safe {@link Pool} that never returns {@code null} from {@link
   * Pool#acquire()} and that contains objects of the type created by the given {@link Factory} with
   * the given maximum size.
   *
   * <p>If the pool is empty when {@link Pool#acquire()} is called, the given {@link Factory} will
   * be used to create a new instance.
   *  線程不安全
   * @param <T> The type of object the pool will contains.
   */
  @NonNull
  public static <T extends Poolable> Pool<T> simple(int size, @NonNull Factory<T> factory) {
    return build(new SimplePool<T>(size), factory);
  }

  /**
   * Returns a new thread safe {@link Pool} that never returns {@code null} from {@link
   * Pool#acquire()} and that contains objects of the type created by the given {@link Factory} with
   * the given maximum size.
   *
   * <p>If the pool is empty when {@link Pool#acquire()} is called, the given {@link Factory} will
   * be used to create a new instance.
   *線程安全
   * @param <T> The type of object the pool will contains.
   */
  @NonNull
  public static <T extends Poolable> Pool<T> threadSafe(int size, @NonNull Factory<T> factory) {
    return build(new SynchronizedPool<T>(size), factory);
  }

  /**
   * Returns a new {@link Pool} that never returns {@code null} and that contains {@link List Lists}
   * of a specific generic type with a standard maximum size of 20.
   *
   * <p>If the pool is empty when {@link Pool#acquire()} is called, a new {@link List} will be
   * created.
   *
   * @param <T> The type of object that the {@link List Lists} will contain.
   */
  @NonNull
  public static <T> Pool<List<T>> threadSafeList() {
    return threadSafeList(DEFAULT_POOL_SIZE);
  }

  /**
   * Returns a new thread safe {@link Pool} that never returns {@code null} and that contains {@link
   * List Lists} of a specific generic type with the given maximum size.
   *
   * <p>If the pool is empty when {@link Pool#acquire()} is called, a new {@link List} will be
   * created.
   *
   * @param <T> The type of object that the {@link List Lists} will contain.
   */
  // Public API.
  @SuppressWarnings("WeakerAccess")
  @NonNull
  public static <T> Pool<List<T>> threadSafeList(int size) {
    return build(
        new SynchronizedPool<List<T>>(size),
        new Factory<List<T>>() {
          @NonNull
          @Override
          public List<T> create() {
            return new ArrayList<>();
          }
        },
        new Resetter<List<T>>() {
          @Override
          public void reset(@NonNull List<T> object) {
            object.clear();
          }
        });
  }

  @NonNull
  private static <T extends Poolable> Pool<T> build(
      @NonNull Pool<T> pool, @NonNull Factory<T> factory) {
    return build(pool, factory, FactoryPools.<T>emptyResetter());
  }

  @NonNull
  private static <T> Pool<T> build(
      @NonNull Pool<T> pool, @NonNull Factory<T> factory, @NonNull Resetter<T> resetter) {
    return new FactoryPool<>(pool, factory, resetter);
  }

}

相信通過上面這一些列的流程,我們對對象池的設(shè)計和實現(xiàn)有了一個大體的了解聘殖,同時對設(shè)計模式,算法的復(fù)雜度末盔,甚至后面的架構(gòu)設(shè)計積累了靈感和素材蛮粮,值得反復(fù)揣摩令哟。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市昂验,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖涎劈,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異浦旱,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)吴侦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門尿瞭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人谁榜,你說我怎么就攤上這事∫称瑁” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長厕诡。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么绍傲? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮务蝠,結(jié)果婚禮上拍谐,老公的妹妹穿的比我還像新娘崖面。我一直安慰自己,他們只是感情好简识,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著锐膜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪渐溶。 梳的紋絲不亂的頭發(fā)上掂恕,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天拖陆,我揣著相機(jī)與錄音,去河邊找鬼懊亡。 笑死依啰,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的店枣。 我是一名探鬼主播速警,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼鸯两!你這毒婦竟也來了闷旧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤甩卓,失蹤者是張志新(化名)和其女友劉穎鸠匀,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體逾柿,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年宅此,在試婚紗的時候發(fā)現(xiàn)自己被綠了机错。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡父腕,死狀恐怖弱匪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情璧亮,我是刑警寧澤萧诫,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站枝嘶,受9級特大地震影響帘饶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜群扶,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一及刻、第九天 我趴在偏房一處隱蔽的房頂上張望镀裤。 院中可真熱鬧,春花似錦缴饭、人聲如沸暑劝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽担猛。三九已至,卻和暖如春丢氢,著一層夾襖步出監(jiān)牢的瞬間傅联,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工卖丸, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留纺且,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓稍浆,卻偏偏與公主長得像载碌,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子衅枫,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354