ViewModel、LiveData浅析(MVVM)

由于MVP固有的一些因素,比如随着项目越来越大P层越来越膨胀和内存泄露问题,MVVM就面世了。MVVM组成:Model,ViewModel,View,其实和MVP差不多,就是把Presenter换成了VIewModel。简单的概括关系就是:
M --> VM --> V,
M被VM持有,由VM来处理业务逻辑,而VM又被V持有,实际上是形成一条单向线的。不过VM和V会通过binder来实现双向绑定,这样也就进一步达到了解耦的目的。配上一张看到的很形象的图:


mvvm-arch.jpg

由于谷歌爸爸提供的JetPack库,我们可以很容易的拿到相应的组件来实现。简单的提一下相关的组件:
ViewModel、LiveData/DataBinding、

ViewModel

作为核心成员,实际上是一个抽象类:


ViewModel.jpg
/**
 * ViewModel is a class that is responsible for preparing and managing the data for
 * an {@link android.app.Activity Activity} or a {@link androidx.fragment.app.Fragment Fragment}.
 * It also handles the communication of the Activity / Fragment with the rest of the application
 * (e.g. calling the business logic classes).
 * <p>
 * A ViewModel is always created in association with a scope (an fragment or an activity) and will
 * be retained as long as the scope is alive. E.g. if it is an Activity, until it is
 * finished.
 * <p>
 * In other words, this means that a ViewModel will not be destroyed if its owner is destroyed for a
 * configuration change (e.g. rotation). The new instance of the owner will just re-connected to the
 * existing ViewModel.
 * <p>
 * The purpose of the ViewModel is to acquire and keep the information that is necessary for an
 * Activity or a Fragment. The Activity or the Fragment should be able to observe changes in the
 * ViewModel. ViewModels usually expose this information via {@link LiveData} or Android Data
 * Binding. You can also use any observability construct from you favorite framework.
 * <p>
 * ViewModel's only responsibility is to manage the data for the UI. It <b>should never</b> access
 * your view hierarchy or hold a reference back to the Activity or the Fragment.
 * <p>
 * ViewModels can also be used as a communication layer between different Fragments of an Activity.
 * Each Fragment can acquire the ViewModel using the same key via their Activity. This allows
 * communication between Fragments in a de-coupled fashion such that they never need to talk to
 * the other Fragment directly.
* <p>
 */
public abstract class ViewModel {
        ...
}

经典的注解更重要系列,甚至连用法都告诉我们了但太长被我截了(不自觉得跪在了地上)。
注解:

1.ViewModel是一个类,负责为Activity或Fragment准备和管理数据。它还处理Activity/Fragment与应用程序其余部分的通信(例如,调用业务逻辑类)。

2.ViewModel始终与范围(片段或活动)相关联地创建,并且只要范围是活动的,就将被保留。例如。如果是活动,则直到完成。换句话说,这意味着如果ViewModel的所有者因配置更改(例如旋转)而被销毁,则不会销毁它。所有者的新实例将重新连接到现有的ViewModel。

3.ViewModel的目的是获取并保留Activity或Fragment所需的信息。Activity或Fragment应该能够观察 ViewModel中的更改。 ViewModel通常通过{@link LiveData}或Android Data Binding公开此信息。您也可以使用自己喜欢的框架中的任何可观察性构造。

4.ViewModel的唯一责任是管理UI的数据。它绝不能访问您的视图层次结构或保留对活动或片段的引用。

5.ViewModels也可以用作Activity的不同Fragment之间的通信层。每个Fragment都可以通过其Activity使用相同的键获取ViewModel。这允许Fragment之间以解耦的方式进行通信,从而使它们无需直接与另一个片段交谈。

这样看来,ViewModel的职责就很清晰了,就是替Activity或Fragment管理数据的,也就是前面说的相当于Presenter,用来处理业务逻辑,所以本身并没有很神秘。注意一下第三点,View层是应该能观察到ViewModel中的数据更改的,可以通过LiveData、DataBinding或者其他的观察者模式框架来实现,这也就是View和ViewModel实现双向绑定的方法。

附一张作用域图:


ViewModelScope.png

简单使用:

public class UserModel extends ViewModel {

    @Override
    protected void onCleared() {
        super.onCleared();
        //do cleared work
    }

    void doAction(){
        // depending on the action, do necessary business logic calls 
        // and update the userLiveData.
    }
}

还是借鉴一下官网的例子:

public class MyViewModel extends ViewModel {
    private MutableLiveData<List<User>> users;
    public LiveData<List<User>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<List<User>>();
            loadUsers();
        }
        return users;
    }

    private void loadUsers() {
        // Do an asynchronous operation to fetch users.
        //这里就可以做一些异步操作来获取数据
    }
}
public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same MyViewModel instance created by the first activity.

        MyViewModel model = new ViewModelProvider(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> {
            // update UI
        });
    }
}

就是这么简单,就把界面和业务逻辑分离了,这里用到了LiveData,后面再说。重写onCleared()做一些资源的释放,以及我们自己提供一些业务逻辑的操作。onCleared()会在Activity/Fragment被回收时调用,这是怎么做到的呢?

先看下ViewModel是怎么获取的:

UserModel userModel = new ViewModelProvider(this, new UserModelFactory()).get(UserModel.class);

引入implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'库则可以使用单参数构造器:

UserModel userModel = new ViewModelProvider(this).get(UserModel.class);

通过ViewModelProvider的get()方法来获取,先看下这个ViewModelProvider


ViewModelProvider.png

这里要注意的是androidx.lifecycle:lifecycle-viewmodel:2.2.0包才提供了单参数的构造方法。

get()方法:

    @NonNull
    @MainThread
    public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
        String canonicalName = modelClass.getCanonicalName();
        if (canonicalName == null) {
            throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
        }
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }

    @NonNull
    @MainThread
    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key);

        if (modelClass.isInstance(viewModel)) {
            //noinspection unchecked
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
        } else {
            viewModel = (mFactory).create(modelClass);
        }
        mViewModelStore.put(key, viewModel);
        //noinspection unchecked
        return (T) viewModel;
    }

可以看到,先根据类名和DEFAULT_KEY进行拼装得到key,然后在去mViewModelStore里取。如果取出来的viewModel是我们要的类,那就直接返回(此时viewModel可能为null),否则就“log a warning”。

接着判断持有的mFactory是否是KeyedFactory的类型,如果是就通过这个Factory来构造出viewModel,如果是别的类型,就直接create。然后把viewModel再放进mViewModelStore里,其实这里也就表明了,如果不同的Activity/Fragment去获取同类型的ViewModel的话,那么取到的实例(引用)就是一样的,那当然持有的数据就是一致的,也就能达到通信的效果。

而ViewModelProvider之所以为我们提供了Factory,就是因为在获取ViewModel的时候我们是不能直接通过new来用构造器创建的,这样的话获取的都是全新的ViewModel对象,那就没有意义了。所以由Factory来提供,并可以让我们创建有参的ViewModel。(这里保留观点,待核实)

看下ViewModelStore:

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
    }

    final ViewModel get(String key) {
        return mMap.get(key);
    }

    Set<String> keys() {
        return new HashSet<>(mMap.keySet());
    }

    /**
     *  Clears internal storage and notifies ViewModels that they are no longer used.
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}

很简单,通过HashMap来存ViewModel,注意一下clear()方法,会将持有的VM都进行清理

    @MainThread
    final void clear() {
        mCleared = true;
        // Since clear() is final, this method is still called on mock objects
        // and in those cases, mBagOfTags is null. It'll always be empty though
        // because setTagIfAbsent and getTag are not final so we can skip
        // clearing it
        if (mBagOfTags != null) {
            synchronized (mBagOfTags) {
                for (Object value : mBagOfTags.values()) {
                    // see comment for the similar call in setTagIfAbsent
                    closeWithRuntimeException(value);
                }
            }
        }
        onCleared();
    }

可以看到clear()内部就调用了给我们自己实现的onCleared()方法,到这里我们只知道逻辑,但还是不知道调用时机。回过头来看下ViewModelProvider的构造函数:

public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
        this(owner.getViewModelStore(), factory);
    }

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        mViewModelStore = store;
    }

提供了两种构造函数,但最终都是通过ViewModelStore和Factory来构造的。那这个ViewModelStoreOwner又是什么呢,其实看名字就应该知道,就是用来提供ViewModelStore的。

public interface ViewModelStoreOwner {
    /**
     * Returns owned {@link ViewModelStore}
     *
     * @return a {@code ViewModelStore}
     */
    @NonNull
    ViewModelStore getViewModelStore();
}

实际上,就是一个接口。看下它的实现类,就是ComponentActivity:

    @Override
    public ViewModelStore getViewModelStore() {
        if (getApplication() == null) {
            throw new IllegalStateException("Your activity is not yet attached to the "
                    + "Application instance. You can't request ViewModel before onCreate call.");
        }
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    }

重写了该方法,目的也就是为了提供mViewModelStore。再看下ComponentActivity的继承关系:


ComponentActivity.png

这样就很清晰了,也就是说我们的Activity也实现了ViewModelStoreOwner接口,再看下ComponentActivity的构造函数:

public ComponentActivity() {
        Lifecycle lifecycle = getLifecycle();
        //noinspection ConstantConditions
        if (lifecycle == null) {
            throw new IllegalStateException("getLifecycle() returned null in ComponentActivity's "
                    + "constructor. Please make sure you are lazily constructing your Lifecycle "
                    + "in the first call to getLifecycle() rather than relying on field "
                    + "initialization.");
        }
      
        getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
                    if (!isChangingConfigurations()) {
                        getViewModelStore().clear();
                    }
                }
            }
        });
    }

这里删减了其他不相关的代码,可以看到,持有了Lifecycle实例,通过对生命周期的监听(这里是ON_DESTROY),来实现clear逻辑。这里还要注意的一点是,当配置改变的时候(翻转),是不会去清理的。这也很好理解,因为只是界面重绘,数据如果重新获取(ViewModel)那还是一样的数据。

到这里,为什么ViewModel能在Activity被回收时去清理资源就很清晰了。

LiveData

上面也有提到,其实真正存储数据的是LiveData。先从用法入手:

      userModel.liveData.observe(this, new Observer<Integer>() {
            @Override
            public void onChanged(Integer integer) {
                textView.setText(String.valueOf(integer));
            }
        });

LiveData由ViewModel来持有,为其添加观察者并指定动作则可以监听数据,当数据变化时我们就可以更新UI或者做其他的事情,经典的观察者模式。

public abstract class LiveData<T> {
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    final Object mDataLock = new Object();
    static final int START_VERSION = -1;
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    static final Object NOT_SET = new Object();

    private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers =
            new SafeIterableMap<>();

    // how many observers are in active state
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    int mActiveCount = 0;
    private volatile Object mData;
    // when setData is called, we set the pending data and actual data swap happens on the main
    // thread
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    volatile Object mPendingData = NOT_SET;
    private int mVersion;

    private boolean mDispatchingValue;
    @SuppressWarnings("FieldCanBeLocal")
    private boolean mDispatchInvalidated;

    private final Runnable mPostValueRunnable = new Runnable() {
        @SuppressWarnings("unchecked")
        @Override
        public void run() {
            Object newValue;
            synchronized (mDataLock) {
                newValue = mPendingData;
                mPendingData = NOT_SET;
            }
            setValue((T) newValue);
        }
    };

    /**
     * Creates a LiveData initialized with the given {@code value}.
     *
     * @param value initial value
     */
    public LiveData(T value) {
        mData = value;
        mVersion = START_VERSION + 1;
    }

    /**
     * Creates a LiveData with no value assigned to it.
     */
    public LiveData() {
        mData = NOT_SET;
        mVersion = START_VERSION;
    }
}

注释比较长就没贴了,大致如下:

1.LiveData是可以在给定生命周期内观察到的数据持有者类。这意味着可以将{@link Observer}与{@link LifecycleOwner}成对添加,并且只有配对的LifecycleOwner处于活动状态时,才会向该观察者通知有关包装数据的修改。如果LifecycleOwner的状态为{@link Lifecycle.StateSTARTED}或{@link Lifecycle.StateRESUMED},则将其视为活动状态。通过{@link watchForever(Observer)}添加的观察者被视为始终处于活动状态,因此将始终收到有关修改的通知。对于这些观察者,您应该手动调用{@link removeObserver(Observer)}。

2.如果相应的生命周期变为{@link Lifecycle.StateDESTROYED}状态,则添加了生命周期的观察者将被自动删除。这对于活动和片段可以安全地观察LiveData而不用担心泄漏的活动特别有用:销毁它们时,它们将立即被取消订阅。

3.此外,LiveData具有{@link LiveData#onActive()}和{@link LiveData#onInactive()}方法,可在活动{@link Observer}的数量在0到1之间变化时得到通知。这就允许LiveData可以释放大量的资源,当没有任何正在观察的观察者时。

4.此类旨在容纳{@link ViewModel}的各个数据字段,但也可以用于以解耦的方式在应用程序中的不同模块之间共享数据。

几个重要的字段:
mDataLock:设值时的锁
START_VERSION:数据(消息)起始版本
mObservers:观察者集合SafeIterableMap,其实是由双向链表来伪装成的Map
mData:持有的数据
mVersion:当前数据版本
mPostValueRunnable:提供设值逻辑

有两个构造函数,带值的就直接将值赋给mData,并将起始版本号加1(因为已经有数据了嘛);无参构造函数就正常初始化。

我们就先从observe()方法看起,这也是第一条注释提到的,可以将Observer(观察者)和LifecycleOwner(生命周期持有者)成对添加。

   /**
    * @param owner    The LifecycleOwner which controls the observer
     * @param observer The observer that will receive the events
     */
    @MainThread
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        assertMainThread("observe");
        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
            // ignore
            return;
        }
        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
        ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
        if (existing != null && !existing.isAttachedTo(owner)) {
            throw new IllegalArgumentException("Cannot add the same observer"
                    + " with different lifecycles");
        }
        if (existing != null) {
            return;
        }
        owner.getLifecycle().addObserver(wrapper);
    }

从名字也可以看出,这个方法就是用来添加(注册)观察者的,而LifecycleOwner则是对应的观察者的生命周期持有者,是一个有关生命周期的接口,由ComponentActivity实现。目的很简单,匿名内部类持有外部类引发的内存泄漏,以及当我们页面都已经关闭了,那么再来接收事件(更新界面)那就没有意义了。

首先assertMainThread()方法就是来判断当前是否为主线程,如果不是就抛出异常(这里因该是持有了LifecycleOwner的原因)。接着判断当前LifecycleOwner的状态,如果是DESTROYED状态就直接return掉。

紧接着封装了一个LifecycleBoundObserver对象,主要提供了解绑Observer方法,然后添加到mObservers中。如果观察者已经存在并且绑定到了owner,那就抛出异常。

最后拿到Lifecycle(抽象类,实现类为LifecycleRegistry)对象,调用addObserver()添加wrapper,这里主要就是监听生命周期状态。

还提供了observeForever(),也就是无论生命周期状态如何都一直监听,那就需要我们自己去移除监听者,这里就不看了。

接着看下setValue()方法:

   /**
     * Sets the value. If there are active observers, the value will be dispatched to them.
     * <p>
     * This method must be called from the main thread. If you need set a value from a background
     * thread, you can use {@link #postValue(Object)}
     *
     * @param value The new value
     */
    @MainThread
    protected void setValue(T value) {
        assertMainThread("setValue");
        mVersion++;
        mData = value;
        dispatchingValue(null);
    }

设置值,如果有存活的observer就去分发。一样先判断是否是主线程,将mVersion加1(相当于这是新版本的数据),将新值赋给mData,进行分发:

void dispatchingValue(@Nullable ObserverWrapper initiator) {
        if (mDispatchingValue) {
            mDispatchInvalidated = true;
            return;
        }
        mDispatchingValue = true;
        do {
            mDispatchInvalidated = false;
            if (initiator != null) {
                considerNotify(initiator);
                initiator = null;
            } else {
                for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                    considerNotify(iterator.next().getValue());
                    if (mDispatchInvalidated) {
                        break;
                    }
                }
            }
        } while (mDispatchInvalidated);
        mDispatchingValue = false;
    }

在setValue()方法中调用dispatchingValue()时传入了null,所以先看initiator为null的情况。很简单,就是拿到mObservers的迭代器,然后调用considerNotify()方法判断这个Observer是否要通知,看下considerNotify()方法:

private void considerNotify(ObserverWrapper observer) {
        if (!observer.mActive) {
            return;
        }
        // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
        //
        // we still first check observer.active to keep it as the entrance for events. So even if
        // the observer moved to an active state, if we've not received that event, we better not
        // notify for a more predictable notification order.
        if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        observer.mLastVersion = mVersion;
        observer.mObserver.onChanged((T) mData);
    }

首先还是先判断了observer是否是Active状态,如果不是就调用observer.activeStateChanged(false)去更新状态;
接着判断observer的mLastVersion是否大于或等于LiveData的mVersion,如果是就直接return。也就是说,数据(消息)的版本(mVersion)要大于监听者的版本(mLastVersion )时,才会去做数据的分发通知,我们只对新版本的数据感兴趣(也可以把mVersion当做最新的消息版本,把mLastVersion当做上一次的消息版本,只有新消息的版本大于上一次消息的版本,我们才去通知)。其实这里也是引发粘性事件的地方,EventBus也是。往回看就可以知道,不管是observer.mLastVersion还是mVersion,它们的起始值都是START_VERSION(-1),而在setValue()的时候,mVersion进行了自加,也就是第一次的时候就变为了0,而mLastVersion并没有改动。那么这就导致了,即使是后于设值注册的observer,走到这里的时候,mLastVersion(-1)是小于mVersion(0)的,这也就让数据正常的往下走通知了出去。这也是为什么我们在使用EventBus去发消息的时候,明明消息已经发出去了,而新开启的Activity注册之后仍然能接收到的原因。当然这点也可以利用来作为Activity间开启的数据传递。

接着就是将mVersion赋给mLastVersion,告诉observer:这个数据已经旧了,下次要获取就要获取新的。然后回调observer的onChanged()方法把数据更新出去,这也就是一开始调用observe()来添加observer需要重写的方法。

后记

不知不觉,这篇文章居然在草稿箱里躺了至少快两年,当初是想说感觉写的还不够成熟想在完善一下再发,没想到一搁置就这么久...懒真的是要命啊啊啊ε=ε=ε=(#>д<)ノ

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

推荐阅读更多精彩内容