AAC-ViewModel实现分析

Jetpack是Google I / O 2017 中引入, 其中的架构组件 Android Architecture Components , 就是我们常说的AAC. 包括Databinding, Lifecycles, LiveData, ViewModel, Navigation, Paging, Room, WorkManager组件. 每个组件都是独立的,你可以使用一个或者组合使用.

这里介绍的是 ViewModel 组件, 主要分为 :

  • ViewModel的作用
  • ViewModel对ConfigChange的抵抗性实现原理
  • ViewModel和View层的通信
  • ViewModel的销毁

1. 作用

在介绍 ViewModel 的作用前, 先看看 ViewModel 类和 MVVM 中的 VM.

1.1 ViewModel层和ViewModel类

在 AAC-ViewModel 出现之前, 我们已经在 MVVM 架构模式中使用过 ViewModel 层, 可以参考官方的 todo-mvvm-rxjava 项目.

MVVM中的 ViewModel 作为层级, 担任分离 View(视图) 和 Model(数据模型)的职责.
ViewModel类是 Jetpack 架构组件的一部分, 他的实现可以扮演MVVM中的 ViewModel 层, 但两者并不是一个东西, 前者是分离数据/视图的逻辑层, 后者的官方定义是 :

以注重生命周期的方式管理界面相关的数据. ViewModel类旨在以生命周期有意识的方式存储和管理ui相关的数据. ViewModel类允许数据在配置更改(如屏幕旋转)之后存活.

参考 ConfigChange 时的生命周期图 :
Activity 经历了创建页面, 销毁页面, 重建页面的过程, 而 ViewModelScope(ViewModel的集合容器) 在创建后, 无视销毁/重建的过程.

image

借住这个特性, 将 Model 保存在 ViewModel 中, 能帮助我们快速恢复 ConfigChange 重建后的页面状态.

1.2 保存UI的状态

跨系统发起的活动或应用程序破坏及时保存和恢复活动的UI状态是用户体验的一个重要部分。在这些情况下,用户希望UI状态保持不变,但是系统会销毁活动和其中存储的任何状态.

当用户对UI状态的期望与默认系统行为不匹配时,必须保存并恢复用户的UI状态,以确保系统启动的销毁对用户是透明的.

在大多数情况下,应该同时使用ViewModel和onSaveInstanceState().

Save/Restore InstanceState

系统会为每个打开的APP分配独立的进程, 但是内存资源并不是无限的, 在资源不足的情况下系统会回收进程. 根据被回收的优先级, 进程分为前台进程,可见进程,服务进程,缓存进程.

当应用的进程被回收后, Activity实例和其中保存的对象都会被回收. 当用户重启应用后, 会创建全新的Activity实例, 之前页面上的数据丢失. 这时我们就要借助安卓提供的保存/恢复机制 onSaveInstanceState(bundle) .

参考官网对 onSaveInstanceState(bundle) 的描述: 当 Activity 处于可能被回收的状态, 系统就会调用 onSaveInstanceState(bundle) 保存数据供我们将来重新打开 Activity 时恢复界面使用.

This method is called before an activity may be killed so that when it comes back some time in the future it can restore its state

configuration changes

运行时发生变化(例如屏幕方向、键盘可用性,以及当用户启用[多窗口模式])时,
Android 会重启正在运行的 Activity(先后调用 onDestroy()onCreate()). 重启行为旨在通过利用与新设备配置相匹配的备用资源来自动重新加载您的应用,从而帮助它适应新配置。
如果重启 Activity 需要恢复大量数据、重新建立网络连接或执行其他密集操作,那么因配置变更而引起的完全重启可能会给用户留下应用运行缓慢的体验。这时我们就需要使用 ViewModel 来帮助我们进行重启后的数据恢复. 因为 ViewModel 是在内存中的, 从内存中获取重建前保存的 ViewModel 过程非常快.

两者的关系
  1. 已经有了 onSaveInstanceState(bundle), 为什么还需要 ViewModel ?
    onSaveInstanceState(bundle) 是通过 Bundle 存储数据的, 最终会通过 Binder 做一个跨进程的通信将 Bundle 的数据传递给 ActivityManager , 而安卓中 Binder 缓冲区 参考官方文档 只有1MB, 只能存储轻量级的数据. 并且 Bundle 的序列化/反序列化会带来额外的开销.
    而 ViewModel 在ConfigChange 前后一直存在于内存中(参考生命周期图), 重建后可以从内存中直接获取 ViewModel, 没有任何额外的开销.

  2. ViewModel 能代替 onSaveInstanceState(bundle) 吗?
    ViewModel 只能用于 ConfigChange 后数据的恢复. 如果是进程被回收的情况, ViewModel 也会随着页面的销毁而销毁. 这时候必须借助onSaveInstanceState(bundle) 机制来重建.
    所以 ViewModel 和 onSaveInstanceState(bundle) 应该同时存在.

举个例子

我们需要通过 userId 来请求 userInfo , 在 onSaveInstanceState(bundle)userId 保存.

  • 正常情况下我们在 ViewModel 中根据 userId 来请求 userInfo
  • onSaveInstanceState(bundle) 保存 userId 而不是复杂的 userInfo. 当进程可能被回收时, 调用该方法.
  • 如果是进程被回收后再次打开的页面重建, 此时ViewModel 也是全新的对象. 这时我们可以获取 onSaveInstanceState(bundle) 中保存的 userId, 请求 userInfo 交给 ViewModel 完成.
  • 如果是 ConfigChange 导致的页面重建, 此时 ViewModel 并没有被销毁, userInfo 依然存在. 这时 onSaveInstanceState(bundle) 里的数据仍然会被传递给 ViewModel, 只需要在 ViewModel 里判断是否已经维护了数据, 直接返回之前的 userInfo 即可.

伪代码如下 :

class DetailActivity : BaseActivity<ActivityDetailBinding>() {

    private val viewModel by lazy { getInjectViewModel<DetailViewModel>() }

    override fun onSaveInstanceState(outState: Bundle?) {
        super.onSaveInstanceState(outState)
        outState?.putInt("id", userId)  //保存userId
    }

    override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
        super.onRestoreInstanceState(savedInstanceState)
        //获取userId
        viewModel.setUserId(savedInstanceState?.getInt("id"))
    }
}

class DetailViewModel{
    private val _userId = MutableLiveData<Int>()

    fun setUserId(userId: Int?) {
        //ConfigChange重建后, Activity还会调用这个方法, 这时没必要再次请求.
        if (_userId.value == userId) {   
            return
        }
        _userId.value = userId
    }

    val userInfo: LiveData<Resources<DetailModel>> = Transformations.switchMap(_userId) { topicId ->
        if (topicId == null) {
            AbsentLiveData.create()
        } else {
            getUserInfo(_userId)  //获取userInfo
        }
    }
}

2. 源码分析

2.1 创建ViewModel

创建ViewModel的过程分为两步

val detailViewModel = ViewModelProviders.of(this@DetailActivity).get(DetailViewModel::class.java)

第一步 ViewModelProviders.of() 有四个重载方法

image

ViewModelProviders.of(activity) 为例, 返回创建的 ViewModelProvider 对象

public static ViewModelProvider of(@NonNull FragmentActivity activity) {
    return of(activity, null);
}

public static ViewModelProvider of(@NonNull FragmentActivity activity,
        @Nullable Factory factory) {
    //Application非空判断
    Application application = checkApplication(activity); 
    if (factory == null) {  //factory为空就指定一个默认的factory
        factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
    }
    //创建ViewModelProvider
    return new ViewModelProvider(activity.getViewModelStore(), factory);
}

第二步会调用 ViewModelProvider#get(modelClass) 根据 ViewMdoel.class 对象获取 ViewModel

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

public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
    //获得class的规范名称, 可以理解为包名
    String canonicalName = modelClass.getCanonicalName(); 
    if (canonicalName == null) {
        throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
    }
    //获取ViewModel
    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}

public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    //mViewModelStore是个hashMap, 用ViewModel的包名作为key查找hashMap中是否已经添加了ViewModel.
    ViewModel viewModel = mViewModelStore.get(key);
    if (modelClass.isInstance(viewModel)) {  //如果存在ViewModel,直接返回
        //noinspection unchecked
        return (T) viewModel;
    } else {
        //noinspection StatementWithEmptyBody
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }
    //根据ViewModel的class创建 ViewModel
    viewModel = mFactory.create(modelClass);
    // 新建的ViewModel会添加到mViewModelStore的hashMap中
    mViewModelStore.put(key, viewModel);
    //noinspection unchecked
    return (T) viewModel;
}

首先尝试从 ViewModelStore 中获取 ViewModel, 如果没有就通过 viewModel = mFactory.create(modelClass) 创建并添加到 ViewModelStore 中.

ViewModelStore 内部维护了一个 HashMap<ViewModel包名, ViewModel> 存储Activity的所有ViewModel.

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

既然 ViewModel 是通过 ViewModelStore 存取的, 那么 ViewModel 在重建后仍然存在也和 ViewModelStore 相关.

2.2 ViewModelStore

SDK中使用了大量的面向接口编程, ViewModelStore 也是通过接口获取的. Fragment/FragmentActivity 都实现了该接口.

public interface ViewModelStoreOwner {
    @NonNull
    ViewModelStore getViewModelStore();
}

以 FragmentActivity 的 getViewModelStore() 实现为例, 会尝试从 NonConfigurationInstances 中获取 mViewModelStore.

@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) {
            // 从NonConfigurationInstances中获取viewModelStore
            mViewModelStore = nc.viewModelStore;
        }
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
    return mViewModelStore;
}

要了解重建后 ViewModel 的来源, 所以我们只需要继续看重建后 NonConfigurationInstances 的来源.

2.3 NonConfigurationInstances

Activity 的生命周期都是通过 ActivityThread 管理的, 重建Activity通过 ActivityThread # handleRelaunchActivity 实现.

    final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();

    public void handleRelaunchActivity(ActivityClientRecord tmp,
            PendingTransactionActions pendingActions) {
        ....
        ActivityClientRecord r = mActivities.get(tmp.token);
        ....
        handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
                pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
        ....
    }

ActivityThread # handleRelaunchActivityInner() 分为两步 :

  • handleDestroyActivity()销毁当前的页面
  • handleLaunchActivity()重建新的页面
    private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
            List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
            PendingTransactionActions pendingActions, boolean startsNotResumed,
            Configuration overrideConfig, String reason) {
        ...
        //销毁页面
        handleDestroyActivity(r.token, false, configChanges, true, reason);
        ...
        //启动页面
        handleLaunchActivity(r, pendingActions, customIntent);
        ...
    }

1. ActivityThread # handleDestroyActivity()

    public void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
            boolean getNonConfigInstance, String reason) {
        ActivityClientRecord r = performDestroyActivity(token, finishing,
                configChanges, getNonConfigInstance, reason);
    }

performDestroyActivity() 在调用 mInstrumentation.callActivityOnDestroy(r.activity) 也就是销毁 Activity 之前, 会调用 activity.retainNonConfigurationInstances() 将返回的 NonConfigurationInstances 对象保存到 ActivityClientRecord 中.

/** Core implementation of activity destroy call. */
ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
        int configChanges, boolean getNonConfigInstance, String reason) {
    //获取当前Activity对应的 ActivityClientRecord
    ActivityClientRecord r = mActivities.get(token);
    ....
        if (getNonConfigInstance) {
            try {
                //在销毁前保存 NonConfigurationInstances
                r.lastNonConfigurationInstances
                        = r.activity.retainNonConfigurationInstances();
            } catch (Exception e) {
                if (!mInstrumentation.onException(r.activity, e)) {
                    throw new RuntimeException(
                            "Unable to retain activity "
                            + r.intent.getComponent().toShortString()
                            + ": " + e.toString(), e);
                }
            }
        }
    //调用Activity.onDestory()
    mInstrumentation.callActivityOnDestroy(r.activity);
    ....
}
``

Activity # retainNonConfigurationInstances() 中调用 onRetainNonConfigurationInstance() 保存 NonConfigurationInstance, 但是 Activity 自己的实现为null, 肯定是交给子类 FragmentActivity 实现了.

public class Activity
    NonConfigurationInstances retainNonConfigurationInstances() {
        //这一步保存NonConfigurationInstance
        Object activity = onRetainNonConfigurationInstance();
        ....
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.activity = activity;  //用变量activity来保存
        ...
        return nci;
    }
  
    public Object onRetainNonConfigurationInstance() {
        return null;
    }

查看 FragmentActivity 的 onRetainNonConfigurationInstance() 实现, 静态内部类 FragmentActivity.NonConfigurationInstances 保存了 mViewModelStore .

    @Override
    public final Object onRetainNonConfigurationInstance() {
        Object custom = onRetainCustomNonConfigurationInstance();

        FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();

        if (fragments == null && mViewModelStore == null && custom == null) {
            return null;
        }

        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = mViewModelStore;
        nci.fragments = fragments;
        return nci;
    }

    static final class NonConfigurationInstances {
        Object custom;
        ViewModelStore viewModelStore;
        FragmentManagerNonConfig fragments;
    }

2.ActivityThread # handleLaunchActivity()

handleLaunchActivity(ActivityClientRecord r)handleDestroyActivity(ActivityClientRecord r) 中的是同一个.

    public Activity handleLaunchActivity(ActivityClientRecord r,
            PendingTransactionActions pendingActions, Intent customIntent) {
        ....
        final Activity a = performLaunchActivity(r, customIntent);
        ....
    }

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent){
        ....
        //调用Activity.attach() 保存record变量
        activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);

                //调用Activity的onCreate()
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
        ....
    }

Activity.attach() 中保存 ActivityRecord 的属性, 包括lastNonConfigurationInstances并提供 getLastNonConfigurationChildInstances() 方法

public class Activity extends ContextThemeWrapper
    final void attach(....){
        ...
        mLastNonConfigurationInstances = lastNonConfigurationInstances;
        ...
    }  

    HashMap<String, Object> getLastNonConfigurationChildInstances() {
        return mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.children : null;
    }
}


FragmentActivity.onCreate() 中, 从 nc.viewModelStore 中获得 ViewModelStore.

public class FragmentActivity extends ComponentActivity implements
        ViewModelStoreOwner

    protected void onCreate(@Nullable Bundle savedInstanceState) {
        mFragments.attachHost(null /*parent*/);

        super.onCreate(savedInstanceState);
        //获得 NonConfigurationInstances 对象, 里面记录了销毁前保存的 viewModelStore
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            mViewModelStore = nc.viewModelStore;
        }

    static final class NonConfigurationInstances {
        Object custom;
        ViewModelStore viewModelStore;
        FragmentManagerNonConfig fragments;
    }
}

通过上面的分析可以知道 ViewModel 在ConfigChange时的缓存是通过ActivityRecord实现的 :

ActivityClientRecord r 中保存了 Activity.NonConfigurationInstances.

Activity.NonConfigurationInstances 中保存了 FragmentActivity.NonConfigurationInstances.

FragmentActivity.NonConfigurationInstances 中保存了 ViewModelStore viewModelStore

ViewModelStore viewModelStore 内部通过HashMap保存了 当前Activity的所有 ViewModel

销毁页面时, ViewModel 最终将被保存到 ActivityRecord 中.
重建页面时, 使用了同一个 ActivityRecord 来进行数据的恢复, 从中可以获得销毁前页面上所有 ViewModel 的容器 ViewModelStore, 再次调用 ViewModelProviders.of(Activity.class).get(ViewModel.class) 根据类名从 HashMap 中获得已经创建过的 ViewModel.

3. 和视图层的通信

由于ViewModel类实现了对 ConfigChange 抵抗性, 更加适合作为MVVM中的 ViewModel 层使用.

我们使用 MVP 和 MVVM 时, 都是为了进行视图层 View 和模型 Model 的分离. 所以Model和View的通信需要我们自己实现.

  • 在 MVP 中, Presenter使用定义好的接口和View进行交互.
  • 在 MVVM 中没有提供这样的接口, ViewModel可以借助DataBinding或者观察者模式将Model的变更通知到View层.

Databinding

使用 DataBinding 库将 Model 绑定到 View. Dtabinding库还支持双向绑定, View 层的变化也会影响到 Model.

使用观察者模式

以 ViewModel 中使用 LiveData 为例 :

  1. 使用 ViewModelProvider 在 View层(Activity或者Fragment) 中创建 ViewModel.
  2. ViewModel 中通过 Repository 从Db(AAC-Room或者其他ORM)或者network获取数据, 使用 LiveData 保存这些数据.
  3. 在 View 层中观察 LiveData<Model> 来进行视图的响应.
View层和ViewModel通信

4. ViewModel的销毁

4.1 ConfigChange引发的视图销毁

当ConfigChange发生时, ViewModel 的生命周期明显超出了 View层 的生命周期. 所以 ViewModel 不能直接或间接持有 View 的引用:
包括Activity/Fragment和包含他们引用的对象Dialog, SnackBar等等.

一个通用的法则是,你的 ViewModel 中没有导入像 android.这样的包(像 android.arch. 这样的AAC组件除外)。这个经验也同样适用于 MVP 模式中的 Presenter .

4.2 正常的视图销毁

当页面正常销毁而非ConfigChange引起的销毁时, ViewMdoel 对象应当被正常回收.

在 ViewModel 内我们进行了大量获取 Model 的操作, 例如请求网络,读取数据库,文件IO等等. 而 ViewModel 又无法感知 View 的生命周期, 所以 ViewModel 提供了 onCleared() 抽象方法作为释放资源的时机.

public class FragmentActivity extends ComponentActivity {
    @Override
    protected void onDestroy() {
        super.onDestroy();
        //页面销毁时候调用ViewModelStore.clear()
        if (mViewModelStore != null && !isChangingConfigurations()) {
            mViewModelStore.clear();
        }

        mFragments.dispatchDestroy();
    }
}

public class ViewModelStore {
    public final void clear() {
        //遍历所有ViewModel的onCleared()方法
        for (ViewModel vm : mMap.values()) {
            vm.onCleared();
        }
        mMap.clear();
    }
}

public abstract class ViewModel {
    @SuppressWarnings("WeakerAccess")
    protected void onCleared() {
    }
}

我们可以在这里释放资源,例如取消订阅, 防止造成页面的泄露或者崩溃异常.

class DetailViewModel: ViewModel()
    override fun onCleared() {
        super.onCleared()
        //通知repository取消订阅
        repository.unbindViewModel()
    }
}

class DetailRepository{
    public fun unbindViewModel(){
        //取消订阅
        mSubscription.unsubscribe() 
    }
}

参考

ViewModel Guide
Saving UI States

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

推荐阅读更多精彩内容