學(xué)習(xí)筆記| (一) MVP

前言:

前幾天去面試凰棉,有一家公司問到會不會mvp供屉,說說對他的理解讳侨,你的項目中是是如何實現(xiàn)的呵萨,然而我只是看了很多講mvp的文章,并沒有真正的在項目中用過跨跨,畢竟也都是小項目......然后的然后就讓我現(xiàn)場寫mvp.....

推薦一篇通俗易懂的文章

http://www.reibang.com/p/389c9ae1a82c
之前也看了很多的文章潮峦,但是看得越多就越亂囱皿,看到最后也沒有真正弄懂mvp到底是怎么個回事,結(jié)合google的todomvp和這篇文章忱嘹,大致理清了mvp是咋回事了嘱腥。

什么是mvp?

  • mvp就是model/view/presenter三者
  • 關(guān)系:presenter相當(dāng)于是model和view之前的橋梁拘悦,他倆是不能直接交互的齿兔,得通過presenter來連接。
  • 那這樣的話础米,view只能和presenter交互分苇,那怎么才能在view中操作presenter呢?只需要把presenter作為參數(shù)傳給view就行了屁桑;那要在presenter中操作view怎么辦呢医寿,也是同樣的方法,只需要把view傳給view就行了蘑斧。因為要在presenter中獲取數(shù)據(jù)靖秩,所以還需要在Presenter中傳入model。

既然說presenter是中間層竖瘾,那在哪實現(xiàn)的presenter呢沟突?

這個是當(dāng)時問到我的一個題目。準(zhǔn)確的說應(yīng)該是在Activity中創(chuàng)建的presenter實例捕传,將view和model作為參數(shù)傳過去了惠拭。

什么是view?

根據(jù)google官方的例子乐横,view其實是Fragment求橄,Activity只是一個全局的控制者,負(fù)責(zé)創(chuàng)建view以及presenter實例葡公。

view和presenter是怎么關(guān)聯(lián)的罐农?

在presenter的構(gòu)造函數(shù)中調(diào)用view的setPresenter(this)就可以將二者綁定了。

前面做了這么多鋪墊催什,接下來分析一下google官方demo(參考上面的鏈接分析的):

  • 功能頁面:


    主頁.jpg
    詳情頁.jpg
編輯頁.jpg
統(tǒng)計頁.jpg
  • 項目結(jié)構(gòu)


    項目結(jié)構(gòu).png
  • 代碼分析

    • 在最底下有兩個基類:
public interface BasePresenter {

    //presenter開始獲取數(shù)據(jù)并調(diào)用view中方法改變界面顯示涵亏,其調(diào)用時機(jī)是在Fragment類的onResume方法中。
    void start();

}
public interface BaseView<T> {

    //view要持有presenter的引用蒲凶,在將presenter實例傳入view中气筋,其調(diào)用時機(jī)是presenter實現(xiàn)類的構(gòu)造函數(shù)中。
    void setPresenter(T presenter);
}
  • 定義了契約類(接口)
/**
 * 契約類來統(tǒng)一管理view與presenter的所有的接口
 */
public interface TaskDetailContract {

    interface View extends BaseView<Presenter> {
        //定義了該界面(功能)中所有的UI狀態(tài)情況
        // 設(shè)置數(shù)據(jù)加載狀態(tài)
        void setLoadingIndicator(boolean active);

        // 處理task加載失敗的情況
        void showMissingTask();

        // 隱藏待辦事項title
        void hideTitle();

        // 顯示待辦事項title
        void showTitle(String title);

        // 隱藏待辦事項的描述
        void hideDescription();

        // 顯示待辦事項的描述
        void showDescription(String description);

        //顯示待辦事項的狀態(tài)旋圆,是否已完成
        void showCompletionStatus(boolean complete);

        //顯示要編輯的待辦事項
        void showEditTask(String taskId);

        //顯示已刪除的待辦事項
        void showTaskDeleted();

        //顯示已完成的待辦事項
        void showTaskMarkedComplete();

        //顯示未完成的點半事項
        void showTaskMarkedActive();

        //是否未完成
        boolean isActive();
    }

    interface Presenter extends BasePresenter {
        //定義了該界面(功能)中所有的用戶操作事件
        // 修改待辦事項
        void editTask();

        // 刪除待辦事項
        void deleteTask();

        // 標(biāo)記完成
        void completeTask();

        // 標(biāo)記未完成
        void activateTask();
    }
}

這樣寫了之后宠默,這個功能有哪些功能操作,以及ui上會有哪些變化都很清楚

  • View
public class TaskDetailFragment extends Fragment implements TaskDetailContract.View {
private TaskDetailContract.Presenter mPresenter;
     .....
  @Override
    public void setPresenter(@NonNull TaskDetailContract.Presenter presenter) {
       //判空灵巧,在Presenter中會將view和presenter進(jìn)行綁定搀矫,這里是檢查傳入的presenter是否為空
        mPresenter = checkNotNull(presenter);
    }
      ......
    @Override
    public void onResume() {
        super.onResume();
        //開始獲取model中的數(shù)據(jù)
        mPresenter.start();
    }
      ......
  @Override
    public void hideDescription() {
        //隱藏待辦事件的描述
        mDetailDescription.setVisibility(View.GONE);
    }
      ......
}

TaskDetailFragment 作為view層抹沪,主要負(fù)責(zé)ui相關(guān)的狀態(tài)更新,實現(xiàn)了契約類中的View接口后,在相關(guān)的代碼中實現(xiàn)具體的內(nèi)容瓤球。

  • Presenter
    Activity在mvp中的作用:
/**
 * Activity在項目中是一個全局的控制者融欧,負(fù)責(zé)創(chuàng)建view以及presenter實例,并將二者聯(lián)系起來卦羡。
 */
public class TaskDetailActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState){
   //創(chuàng)建view
  TaskDetailFragment taskDetailFragment = (TaskDetailFragment) getSupportFragmentManager()
                .findFragmentById(R.id.contentFrame);

 //創(chuàng)建presenter實例
        new TaskDetailPresenter(
                taskId,
                Injection.provideTasksRepository(getApplicationContext()),
                taskDetailFragment);
 }
}

創(chuàng)建Presenter


/**
 * 作為Presenter層噪馏,實現(xiàn)了該接口,如此 TaskDetailPresenter 則只關(guān)注業(yè)務(wù)層的邏輯相關(guān)绿饵,UI的更新只需調(diào)用View的狀態(tài)方法欠肾。
 */
public class TaskDetailPresenter implements TaskDetailContract.Presenter {
   /**
     * 構(gòu)造函數(shù),presenter作為中間層鏈接model和view蝴罪,為了相互交互董济,則要持有他們的引用
     * @param taskId : 待辦事項id
     * @param tasksRepository : model
     * @param taskDetailView : view
     */
    public TaskDetailPresenter(@Nullable String taskId,
                               @NonNull TasksRepository tasksRepository,
                               @NonNull TaskDetailContract.View taskDetailView) {
        mTaskId = taskId;
        mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null!");

        // 保持對View(TaskDetailFragment)的引用
        mTaskDetailView = checkNotNull(taskDetailView, "taskDetailView cannot be null!");

        // 使View(TaskDetailFragment)也保持對自身(TaskDetailPresenter)的引用
        mTaskDetailView.setPresenter(this);
    }

  @Override
    public void start() {
        //開始獲取數(shù)據(jù)
        openTask();
    }
}
  • Model層:
    model層中主要是數(shù)據(jù)的一些操作步清,數(shù)據(jù)的獲取要门,數(shù)據(jù)的添加,數(shù)據(jù)的刪除等
    在Presenter的start()中獲取數(shù)據(jù):
/**
     * 獲取數(shù)據(jù)
     */
    private void openTask() {
        // 判空處理
        if (Strings.isNullOrEmpty(mTaskId)) {
            //加載數(shù)據(jù)的時候廓啊,如果taskId為空欢搜,則調(diào)用view的加載失敗的情況
            mTaskDetailView.showMissingTask();
            return;
        }

        // 更新狀態(tài),加載成功的時候,設(shè)置加載的狀態(tài)谴轮,
        mTaskDetailView.setLoadingIndicator(true);

        // modele,獲取該條Task數(shù)據(jù)
        mTasksRepository.getTask(mTaskId, new TasksDataSource.GetTaskCallback() {
            @Override
            public void onTaskLoaded(Task task) {
                //數(shù)據(jù)加載成功
                // View已經(jīng)被用戶回退(已銷毀)
                if (!mTaskDetailView.isActive()) {
                    return;
                }

                // 獲取到task數(shù)據(jù)炒瘟,并更新UI
                mTaskDetailView.setLoadingIndicator(false);
                if (null == task) {
                    //如果沒有加載到task,則顯示加載失敗的ui
                    mTaskDetailView.showMissingTask();
                } else {
                    //否則第步,顯示這個待辦事項
                    showTask(task);
                }
            }

            @Override
            public void onDataNotAvailable() {
                // 顯示數(shù)據(jù)獲取失敗時的狀態(tài)
                if (!mTaskDetailView.isActive()) {
                    return;
                }
                mTaskDetailView.showMissingTask();
            }
        });
    }

再看一下TasksRepository的getTask():

@Override
    public void getTask(@NonNull final String taskId, @NonNull final GetTaskCallback callback) {
        // 判空處理
        checkNotNull(taskId);
        checkNotNull(callback);

        // 獲取緩存數(shù)據(jù)
        Task cachedTask = getTaskWithId(taskId);

        // Respond immediately with cache if available
        if (cachedTask != null) {
            callback.onTaskLoaded(cachedTask);
            return;
        }

        // Load from server/persisted if needed.

        // Is the task in the local data source? If not, query the network.
        // 從本地數(shù)據(jù)源(SQLite數(shù)據(jù)庫)中獲取,沒有獲取到疮装,再從網(wǎng)絡(luò)數(shù)據(jù)源獲取
        mTasksLocalDataSource.getTask(taskId, new GetTaskCallback() {
            @Override
            public void onTaskLoaded(Task task) {
                // Do in memory cache update to keep the app UI up to date
                if (mCachedTasks == null) {
                    mCachedTasks = new LinkedHashMap<>();
                }
                mCachedTasks.put(task.getId(), task);
                // 成功,則回調(diào)
                callback.onTaskLoaded(task);
            }

            @Override
            public void onDataNotAvailable() {
                // 失敗粘都,則從遠(yuǎn)程數(shù)據(jù)源(網(wǎng)絡(luò))中獲取
                mTasksRemoteDataSource.getTask(taskId, new GetTaskCallback() {
                    @Override
                    public void onTaskLoaded(Task task) {
                        // Do in memory cache update to keep the app UI up to date
                        if (mCachedTasks == null) {
                            mCachedTasks = new LinkedHashMap<>();
                        }
                        mCachedTasks.put(task.getId(), task);
                        // 回調(diào)成功時的方法
                        callback.onTaskLoaded(task);
                    }

                    @Override
                    public void onDataNotAvailable() {
                        // 回調(diào)失敗時的方法
                        callback.onDataNotAvailable();
                    }
                });
            }
        });
    }
  • 分析Model(TasksRepositor)代碼實現(xiàn):
    • TasksDataSource 接口分析:
      里面包含了所有對數(shù)據(jù)的操作
public interface TasksDataSource {

    //加載數(shù)據(jù)的回調(diào)
    interface LoadTasksCallback {

        void onTasksLoaded(List<Task> tasks);

        void onDataNotAvailable();
    }

    //獲取數(shù)據(jù)的回調(diào)
    interface GetTaskCallback {

        void onTaskLoaded(Task task);

        void onDataNotAvailable();
    }

    /**
     * 獲取所有task
     * @param callback
     */
    void getTasks(@NonNull LoadTasksCallback callback);

    /**
     * 根據(jù)id獲取單個task
     * @param taskId
     * @param callback
     */
    void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback);

    /**
     * 保存task
     * @param task
     */
    void saveTask(@NonNull Task task);

    /**
     * 標(biāo)記為完成
     * @param task
     */
    void completeTask(@NonNull Task task);

    /**
     * 標(biāo)記為完成
     * @param taskId
     */
    void completeTask(@NonNull String taskId);

    /**
     * 標(biāo)記為未完成
     * @param task
     */
    void activateTask(@NonNull Task task);

    /**
     * 標(biāo)記為未完成
     * @param taskId
     */
    void activateTask(@NonNull String taskId);

    /**
     * 刪除所有已完成task
     */
    void clearCompletedTasks();

    /**
     * 刷新task
     */
    void refreshTasks();

    /**
     * 刪除所有task
     */
    void deleteAllTasks();

    /**
     * 刪除單個task
     * @param taskId
     */
    void deleteTask(@NonNull String taskId);
}
  • TasksRepository實現(xiàn)了TasksDataSource接口廓推,維護(hù)了兩個數(shù)據(jù)源。一個是本地(SQLite數(shù)據(jù)庫)翩隧,一個是遠(yuǎn)程(網(wǎng)絡(luò)服務(wù)器)樊展,他們都是TasksDataSource類型。

可以把TasksRepository理解為一個大糧倉堆生,里面又有2個小糧倉专缠,有我的糧食庫,也有你的糧食庫淑仆,我們需要糧食的時候都要到對應(yīng)的糧食庫中取涝婉。所有的糧食庫都實現(xiàn)TasksDataSource接口

   //遠(yuǎn)程數(shù)據(jù)源
    private final TasksDataSource mTasksRemoteDataSource;

    //本地數(shù)據(jù)源
    private final TasksDataSource mTasksLocalDataSource;
  • TasksRepository是一個單例模式,在構(gòu)造函數(shù)中要有兩個數(shù)據(jù)源:
  private static TasksRepository INSTANCE = null;

  // Prevent direct instantiation.
    private TasksRepository(@NonNull TasksDataSource tasksRemoteDataSource,
                            @NonNull TasksDataSource tasksLocalDataSource) {
        mTasksRemoteDataSource = checkNotNull(tasksRemoteDataSource);
        mTasksLocalDataSource = checkNotNull(tasksLocalDataSource);
    }

    public static TasksRepository getInstance(TasksDataSource tasksRemoteDataSource,
                                              TasksDataSource tasksLocalDataSource) {
        if (INSTANCE == null) {
            INSTANCE = new TasksRepository(tasksRemoteDataSource, tasksLocalDataSource);
        }
        return INSTANCE;
    }
  • 實現(xiàn)接口中的方法:
  ......
@Override
    public void deleteTask(@NonNull String taskId) {
     //根據(jù)id刪除Task
        ① mTasksRemoteDataSource.deleteTask(checkNotNull(taskId));
        ② mTasksLocalDataSource.deleteTask(checkNotNull(taskId));

        mCachedTasks.remove(taskId);
    }
  ......
  • ①和②中又執(zhí)行了各自的操作
  • 這時候會想到一個問題蔗怠,這些數(shù)據(jù)到底是在哪里進(jìn)行操作的呢?
    繼續(xù)看TasksLocalDataSource這個類墩弯,這是本地數(shù)據(jù)源
public class TasksLocalDataSource implements TasksDataSource {
    private static volatile TasksLocalDataSource INSTANCE;

  // Prevent direct instantiation.
    private TasksLocalDataSource(@NonNull AppExecutors appExecutors,
            @NonNull TasksDao tasksDao) {
        mAppExecutors = appExecutors;
        mTasksDao = tasksDao;
    }

    public static TasksLocalDataSource getInstance(@NonNull AppExecutors appExecutors,
            @NonNull TasksDao tasksDao) {
        if (INSTANCE == null) {
            synchronized (TasksLocalDataSource.class) {
                if (INSTANCE == null) {
                    INSTANCE = new TasksLocalDataSource(appExecutors, tasksDao);
                }
            }
        }
        return INSTANCE;
    }
}

這個類是一個單例省骂,實現(xiàn)了TasksDataSource ,剛剛在上面說過"本地數(shù)據(jù)源"就相當(dāng)于一個小糧倉最住,要從里面取糧食就要實現(xiàn)TasksDataSource 這個接口才行钞澳。

然后實現(xiàn)接口后做具體的實現(xiàn):

  ......
 @Override
    public void saveTask(@NonNull final Task task) {
        checkNotNull(task);
        Runnable saveRunnable = new Runnable() {
            @Override
            public void run() {
              //在數(shù)據(jù)庫中插入數(shù)據(jù)
                mTasksDao.insertTask(task);
            }
        };
        mAppExecutors.diskIO().execute(saveRunnable);
    }
    ......
    @Override
    public void completeTask(@NonNull final Task task) {
        Runnable completeRunnable = new Runnable() {
            @Override
            public void run() {
                  //在數(shù)據(jù)庫中更新數(shù)據(jù)
                mTasksDao.updateCompleted(task.getId(), true);
            }
        };

        mAppExecutors.diskIO().execute(completeRunnable);
    }
    ......
  • 知道了這個是對本地數(shù)據(jù)庫的具體實現(xiàn)后,那在哪里調(diào)用的它呢涨缚?
    按住TasksLocalDataSource 轧粟,找到它在Injection這個類中用到了,接下來看一下Injection:
public class Injection {

    /**
     * 返回的是model
     * @param context
     * @return
     */
    public static TasksRepository provideTasksRepository(@NonNull Context context) {
        checkNotNull(context);
        //創(chuàng)建數(shù)據(jù)庫
        ToDoDatabase database = ToDoDatabase.getInstance(context);

        //創(chuàng)建TasksRepository :遠(yuǎn)程數(shù)據(jù)源脓魏、本地數(shù)據(jù)源
        return TasksRepository.getInstance(FakeTasksRemoteDataSource.getInstance(),
                TasksLocalDataSource.getInstance(new AppExecutors(), database.taskDao()));
    }
}

它的代碼不多兰吟,提供了一個靜態(tài)方法供外部調(diào)用,而這個方法居然返回的是 TasksRepository ,根據(jù)上面的分析茂翔,知道他相當(dāng)于是model混蔼,為什么要這么寫呢?

  • 接下來看 Injection 的這個方法在哪里使用了珊燎?
//創(chuàng)建presenter實例
        new TaskDetailPresenter(
                taskId,
                Injection.provideTasksRepository(getApplicationContext()),
                taskDetailFragment);

原來在一開始創(chuàng)建Presenter的時候就使用了惭嚣,Presenter要連接view和model,所有就要傳入他們的引用悔政。

現(xiàn)在整個邏輯就很清楚了晚吞。

  • 本地數(shù)據(jù)源分析完了,還有一個遠(yuǎn)程數(shù)據(jù)源谋国,接下來看一下 TasksRemoteDataSource這個類槽地。和上面一樣,他也要實現(xiàn) TasksDataSource接口:
public class TasksRemoteDataSource implements TasksDataSource {
private static TasksRemoteDataSource INSTANCE;

 // Prevent direct instantiation.
private TasksRemoteDataSource() {}

public static TasksRemoteDataSource getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new TasksRemoteDataSource();
        }
        return INSTANCE;
    }
}

其余的和本地數(shù)據(jù)源一樣芦瘾,只是它是從服務(wù)端讀取數(shù)據(jù)

  • 總結(jié):
    1.寫一個契約類管理所有的view和presenter接口捌蚊,這樣所有的方法和ui狀態(tài)都會很清楚了
    2.Acitivity或Fragment相當(dāng)于是View層,要實現(xiàn)契約類中的view接口近弟,并在對應(yīng)方法中寫具體的實現(xiàn)缅糟,因為view層可以和Presenter層交互,所以得在View層拿到Presenter藐吮,在setPresenter()方法中可以將綁定的Presenter傳進(jìn)來溺拱,比如點擊了刪除按鈕,就要調(diào)用Presnter中的刪除事件谣辞。
    3.Model層主要是對數(shù)據(jù)的一些操作迫摔,可以寫一個接口,里面寫上對數(shù)據(jù)的一些操作泥从,model實現(xiàn)這個接口后做具體的實現(xiàn)(進(jìn)行本地數(shù)據(jù)源和遠(yuǎn)程數(shù)據(jù)源的增刪改查)
    4.Presenter是中間層句占,在創(chuàng)建的時候要傳入View和model的引用,在Presenter的構(gòu)造方法中通過 view.setPresenter(this)傳入當(dāng)前Presenter進(jìn)行綁定

over.....

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末躯嫉,一起剝皮案震驚了整個濱河市纱烘,隨后出現(xiàn)的幾起案子杨拐,更是在濱河造成了極大的恐慌,老刑警劉巖擂啥,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哄陶,死亡現(xiàn)場離奇詭異,居然都是意外死亡哺壶,警方通過查閱死者的電腦和手機(jī)屋吨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來山宾,“玉大人至扰,你說我怎么就攤上這事∽拭蹋” “怎么了敢课?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長绷杜。 經(jīng)常有香客問我直秆,道長,這世上最難降的妖魔是什么接剩? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任切厘,我火速辦了婚禮萨咳,結(jié)果婚禮上懊缺,老公的妹妹穿的比我還像新娘。我一直安慰自己培他,他們只是感情好鹃两,可當(dāng)我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布谣拣。 她就那樣靜靜地躺著极祸,像睡著了一般芯丧。 火紅的嫁衣襯著肌膚如雪豪诲。 梳的紋絲不亂的頭發(fā)上球切,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天祖屏,我揣著相機(jī)與錄音腻窒,去河邊找鬼肆资。 笑死懊烤,一個胖子當(dāng)著我的面吹牛梯醒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播腌紧,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼茸习,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了壁肋?” 一聲冷哼從身側(cè)響起号胚,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤籽慢,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后猫胁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體箱亿,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年弃秆,在試婚紗的時候發(fā)現(xiàn)自己被綠了极景。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡驾茴,死狀恐怖盼樟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情锈至,我是刑警寧澤晨缴,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站峡捡,受9級特大地震影響击碗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜们拙,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一稍途、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧砚婆,春花似錦械拍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至埂奈,卻和暖如春迄损,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背账磺。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工芹敌, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人垮抗。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓氏捞,卻偏偏與公主長得像,于是被迫代替她去往敵國和親借宵。 傳聞我的和親對象是個殘疾皇子幌衣,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容