淺談 Android MVC MVP MVVM

前言:

各位同學(xué)大家好 有段時間沒有給各位更新文章了重归, 具體多久我也不清楚 ,最近整理了一下andorid 開發(fā)中幾種常用的代碼架構(gòu)模式 (這里要跟java的設(shè)計模式區(qū)分開, 是代碼整體架構(gòu)不是 傳統(tǒng)java 23種設(shè)計模式)今天就寫了一個簡單例子分享給大家俘枫,那么廢話不多說 我們正式開始囱晴。

具體使用場景

效果圖

image.png

我們這邊在輸入框 輸入我們要查詢的賬號 然后點擊中間button 完成查詢 箱季,然后將結(jié)果顯示在屏幕上面的textview里面 (這里只是模式的查詢效果)然后分析其功能在不同架構(gòu)上面的實現(xiàn)方式

  • 無框架

image.png

具體代碼實現(xiàn)(無框架)

package com.app.mvc_demo.normal;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.app.mvc_demo.R;
import com.app.mvc_demo.bean.Account;
import com.app.mvc_demo.callback.Mcallback;
import java.util.Random;
/***
 *
 *創(chuàng)建人:xuqing
 * 類說明:無架構(gòu)實現(xiàn)
 *
 *
 */public class NormalActivity extends AppCompatActivity  implements View.OnClickListener{
    private EditText ed_account;
    private TextView text_account;
    private Button getacount_btn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_normal);
        initview();
    }
  private void initview() {
            ed_account=findViewById(R.id.ed_account);
            text_account=findViewById(R.id.account_text);
            getacount_btn=findViewById(R.id.get_account_btn);
            getacount_btn.setOnClickListener(this);
        }
        private String  getUserInput(){
            return ed_account.getText().toString().trim();
        }
        private void  showSuccessPage(Account account){
            text_account.setText("用戶賬號:"+account.getName()+"用戶等級"+account.getLevel());
        }

        private void  showFailedPage(){
            text_account.setText("獲取用戶數(shù)據(jù)失敗");
        }

        private  void  getAccountData(String accountName, Mcallback mcallback){

            Random random=new Random();
            boolean isSuccess=random.nextBoolean();
            if(isSuccess){
                Account account=new Account();
                account.setLevel(100);
                account.setName(accountName);
                mcallback.onSuccess(account);
            }else{
                mcallback.onFailed();
            }

        }
    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case  R.id.get_account_btn:
                String userInput=getUserInput();
                getAccountData(userInput, new Mcallback() {
                    @Override
                    public void onSuccess(Account account) {
                        showSuccessPage(account);
                    }
                    @Override
                    public void onFailed() {
                        showFailedPage();
                    }
                });
        }

    }
}

我們觀察整個實現(xiàn)的代碼 裸删,我們可以看到為了實現(xiàn)上面的查詢賬戶的功能 在無架構(gòu)的情況下 我們把所有的邏輯代碼都寫在一個activity 里面 雖然功能可以實現(xiàn),但其負(fù)擔(dān)過重,代碼復(fù)查時繁瑣换帜,一旦需要修改楔壤,復(fù)雜項目極難維護(hù) ,所以一般實戰(zhàn)項目開發(fā)我們非常不推薦這種做法 除非你只是一個簡單的學(xué)習(xí)demo邏輯很少 可以簡單實現(xiàn)。

  • mvc架構(gòu)

image.png

在mvc 架構(gòu)里面我們把這個項目分成 model view controller 三層 model 是我們獲取數(shù)據(jù)的具體方法(mvcmodel )view就是我們的xml布局文件 controller 就是我們的activity fragment 等

具體代碼實現(xiàn)(mvc 架構(gòu))

activity 代碼

package com.app.mvc_demo.mvc;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import com.app.mvc_demo.R;
import com.app.mvc_demo.bean.Account;
import com.app.mvc_demo.callback.Mcallback;

public class MvcActivity extends AppCompatActivity  implements View.OnClickListener{
    private EditText ed_account;
    private TextView text_account;
    private Button getacount_btn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_normal);
        initview();
    }
    private void initview() {
        ed_account=findViewById(R.id.ed_account);
        text_account=findViewById(R.id.account_text);
        getacount_btn=findViewById(R.id.get_account_btn);
        getacount_btn.setOnClickListener(this);
    }
    private String  getUserInput(){
        return ed_account.getText().toString().trim();
    }
    private void  showSuccessPage(Account account){
        text_account.setText("用戶賬號:"+account.getName()+"用戶等級"+account.getLevel());
    }
    private void  showFailedPage(){
        text_account.setText("獲取用戶數(shù)據(jù)失敗");
    }
    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case  R.id.get_account_btn:
                String userInput=getUserInput();
               MvcModel.getAccountData(userInput, new Mcallback() {
                    @Override
                    public void onSuccess(Account account) {
                        showSuccessPage(account);
                    }
                    @Override
                    public void onFailed() {
                        showFailedPage();
                    }
                });
        }
    }
}

mvc model

package com.app.mvc_demo.mvc;
import com.ap.mvc_demo.bean.Account;
import com.app.mvc_demo.callback.Mcallback;
import java.util.Random;

public class MvcModel {
    public  static   void  getAccountData(String accountName, Mcallback mcallback){
        Random random=new Random();
        boolean isSuccess=random.nextBoolean();
        if(isSuccess){
            Account account=new Account();
            account.setLevel(100);
            account.setName(accountName);
            mcallback.onSuccess(account);
        }else{
            mcallback.onFailed();
        }
    }
}

我們看到在mvc 架構(gòu)模式下我們將獲取數(shù)據(jù)的邏輯 抽離到mvcModel 中實現(xiàn) 我們就會簡化掉 activity中的代碼邏輯 ,但是缺點仍然存在惯驼,在MVC框架下蹲嚣,雖然將獲取數(shù)據(jù)與界面展示分割開來,但對于Controller層祟牲,仍然擁有很多權(quán)利隙畜,隨著功能的增多,其代碼量也將會大大增長说贝,不利于維護(hù)修改议惰。

  • MVP架構(gòu)模式

image.png

在mvp架構(gòu)中 我們把整個項目代碼的邏輯分為 model view Presenter model model 是我們獲取數(shù)據(jù)的具體方法 (mvpmodel )view 是我們的xml文件 原來在mvc 中的controller 處理繁雜的邏輯我們都放到 Presenter 中進(jìn)行

具體代碼(MVP)

package com.app.mvc_demo.mvp;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import com.app.mvc_demo.R;
import com.app.mvc_demo.bean.Account;


public class MvpActivity extends AppCompatActivity implements View.OnClickListener,IMVPView {
    private EditText ed_account;
    private TextView text_account;
    private Button getacount_btn;
    private  MVPPersenter mvpPersenter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_normal);
        initview();
        mvpPersenter=new MVPPersenter(this);
    }
    private void initview() {
        ed_account=findViewById(R.id.ed_account);
        text_account=findViewById(R.id.account_text);
        getacount_btn=findViewById(R.id.get_account_btn);
        getacount_btn.setOnClickListener(this);
    }
    public String  getUserInput(){
        return ed_account.getText().toString().trim();
    }
    public void  showSuccessPage(Account account){
        text_account.setText("用戶賬號:"+account.getName()+"用戶等級"+account.getLevel());
    }
    public void  showFailedPage(){
        text_account.setText("獲取用戶數(shù)據(jù)失敗");
    }
    @Override
    public void onClick(View v) {
        String userInput=getUserInput();
         mvpPersenter.getData(userInput);
    }
}

mvpmodel

package com.app.mvc_demo.mvp;
import com.app.mvc_demo.bean.Account;
import com.app.mvc_demo.callback.Mcallback;
import java.util.Random;

public class MVPModel {
    public   void  getAccountData(String accountName, Mcallback mcallback){
        Random random=new Random();
        boolean isSuccess=random.nextBoolean();
        if(isSuccess){
            Account account=new Account();
            account.setLevel(100);
            account.setName(accountName);
            mcallback.onSuccess(account);
        }else{
            mcallback.onFailed();
        }
    }
}

IMVPView

IMvpview
package com.app.mvc_demo.mvp;
import com.app.mvc_demo.bean.Account;
public interface IMVPView {
    String  getUserInput();
    void  showSuccessPage(Account account);
    void  showFailedPage();
}

MVPPersenter

package com.app.mvc_demo.mvp;
import com.app.mvc_demo.bean.Account;
import com.app.mvc_demo.callback.Mcallback;

public class MVPPersenter {
    private  IMVPView imvpView;
    private MVPModel mvpModel;

    public MVPPersenter(IMVPView imvpView) {
        this.imvpView = imvpView;
        mvpModel=new MVPModel();
    }
   public void   getData(String  accountName){
        mvpModel.getAccountData(accountName, new Mcallback() {
            @Override
            public void onSuccess(Account account) {
                imvpView.showSuccessPage(account);
            }
            @Override
            public void onFailed() {
                imvpView.showFailedPage();

            }
        });
   }
}

在mvp架構(gòu)中 我們發(fā)現(xiàn)我們會在activtiy 直接操作那些邏輯 而是在MVPPersenter 中持有mvpmodel的引用然后在 MVPPersenter 的構(gòu)造方法中傳入 IMVPView的對象 我們在mvpModel 的回調(diào)方法里面在用imvpView 講拿到的數(shù)據(jù)結(jié)果回調(diào)到activity 或者fragment 中,然后我們在activity 或者fragment中實現(xiàn)imvpvidew中方法即可獲取數(shù)據(jù)結(jié)果

在使用MVP框架時乡恕,View層與Model層不通信言询,都通過 Presenter層傳遞,并且Presenter層與具體的View是沒有直接關(guān)聯(lián)的傲宜,而是通過定義好的接口進(jìn)行交互运杭,這就可能會導(dǎo)致有大量的接口生成,代碼復(fù)雜繁瑣蛋哭,難維護(hù)县习。

  • 1 因此涮母,在使用MVP時最好按照一定規(guī)范去做:
  • 2 接口規(guī)范化(封裝父類接口以減少接口的使用量)
  • 3使用第三方插件自動生成MVP代碼
  • 4 對于一些簡單的界面谆趾,可以選擇不使用框架
  • 5根據(jù)項目復(fù)雜程度,部分模塊可以選擇不使用接口

MVVM架構(gòu)

image.png

在mvvm 架構(gòu)中 我們將整個項目分為 model view viewmodel model model 是我們獲取數(shù)據(jù)的具體方法 (mvpmodel ) view 是我們的xml文件 viewmodel 處理業(yè)務(wù)邏輯數(shù)據(jù)更新 MVVM框架實現(xiàn)了數(shù)據(jù)與視圖的綁定(DataBinding)叛本,當(dāng)數(shù)據(jù)變化時沪蓬,視圖會自動更新;反之来候,當(dāng)視圖變化時跷叉,數(shù)據(jù)會自動更新。

我們要使用mvvm 首先我們要學(xué)會使用 DataBinding

DataBinding 使用步驟

  • 1啟用DataBinding
  • 2修改布局文件為DataBinding布局
  • 3數(shù)據(jù)綁定

啟用DataBinding

image.png

在build.gradle里面添加依賴代碼配置開啟 databinding支持

    dataBinding {
        enabled = true
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

修改布局文件為DataBinding布局

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable
            name="viewModel"
            type="com.app.mvc_demo.mvvm.MVVMViewModel" />
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".normal.NormalActivity">
        <EditText
            android:id="@+id/ed_account"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="50dp"
            android:layout_marginLeft="40dp"
            android:layout_marginRight="40dp"
            android:hint="請輸入要查詢的賬號">

        </EditText>
        <Button
            android:id="@+id/get_account_btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="獲取賬號信息"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="80dp"
            android:onClick="@{viewModel.getData}"
            >
        </Button>
        <TextView
            android:id="@+id/account_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginTop="80dp"
            android:text="@{viewModel.result}"
            android:hint="賬號信息未獲取"
            >
        </TextView>
    </LinearLayout>
</layout>

我們在布局文件中鼠標(biāo)放在最外層布局 然后按住alt+enter 鍵就可以修改為databinding布局


image.png

MVVM框架使用步驟:

  • 1提供View、ViewModel以及Model三層
  • 2 將布局修改為DataBinding布局
  • 3View與ViewMedel之間通過DataBinding進(jìn)行通信
  • 4獲取數(shù)據(jù)并展示在界面上
    再更深層次學(xué)習(xí)云挟,可以使用LiveData+ViewModel

具體代碼實現(xiàn)(mvvm )

mvvmactivity

package com.app.mvc_demo.mvvm;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import com.app.mvc_demo.R;
import com.app.mvc_demo.databinding.ActivityMvvmBinding;
/***
 *
 * 創(chuàng)建人:xuqing
 * 類說明:mvvmactivity 
 *
 *
 */
public class MVVMActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMvvmBinding  binding= DataBindingUtil.setContentView(this, R.layout.activity_mvvm);
        MVVMViewModel mvvmViewModel=new MVVMViewModel(getApplication(),binding);
        binding.setViewModel(mvvmViewModel);
    }

}
修改布局setContentView 為
 ActivityMvvmBinding  binding= DataBindingUtil.setContentView(this, R.layout.activity_mvvm);
實例化mvvmmodel 并且set到databinding上面
MVVMViewModel mvvmViewModel=new MVVMViewModel(getApplication(),binding);
binding.setViewModel(mvvmViewModel);

MVVMModel

package com.app.mvc_demo.mvvm;
import com.app.mvc_demo.bean.Account;
import com.app.mvc_demo.callback.Mcallback;
import java.util.Random;

public class MVVMModel {

    public   void  getAccountData(String accountName, Mcallback mcallback){
        Random random=new Random();
        boolean isSuccess=random.nextBoolean();
        if(isSuccess){
            Account account=new Account();
            account.setLevel(100);
            account.setName(accountName);
            mcallback.onSuccess(account);
        }else{
            mcallback.onFailed();
        }
    }
}

xml 布局文件


<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable
            name="viewModel"
            type="com.app.mvc_demo.mvvm.MVVMViewModel" />

    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".normal.NormalActivity">


        <EditText
            android:id="@+id/ed_account"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="50dp"
            android:layout_marginLeft="40dp"
            android:layout_marginRight="40dp"
            android:hint="請輸入要查詢的賬號">

        </EditText>
        <Button
            android:id="@+id/get_account_btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="獲取賬號信息"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="80dp"
            android:onClick="@{viewModel.getData}"
            >

        </Button>
        <TextView
            android:id="@+id/account_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginTop="80dp"
            android:text="@{viewModel.result}"
            android:hint="賬號信息未獲取"
            >
        </TextView>
    </LinearLayout>
</layout>

MVVMViewModel

package com.app.mvc_demo.mvvm;
import android.app.Application;
import android.util.Log;
import android.view.View;
import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;
import com.app.mvc_demo.bean.Account;
import com.app.mvc_demo.callback.Mcallback;
import com.app.mvc_demo.databinding.ActivityMvvmBinding;

public class MVVMViewModel extends BaseObservable {
    private static final String TAG = "MVVMViewModel";
    private  MVVMModel mvvmModel;
    private ActivityMvvmBinding binding;
    private String  result;
    public  MVVMViewModel(Application application,ActivityMvvmBinding binding){
        mvvmModel=new MVVMModel();
        this.binding=binding;

    }
    public  void  getData(View view){
       String userInput = binding.edAccount.getText().toString();

        mvvmModel.getAccountData(userInput, new Mcallback() {
            @Override
            public void onSuccess(Account account) {
                String info=account.getName()+"+|"+account.getLevel();
                Log.e(TAG, "onSuccess: info "+info );
                setResult(info);
            }
            @Override
            public void onFailed() {
                setResult("獲取數(shù)據(jù)失敗");
            }
        });
    }
    @Bindable
    public String getResult() {
        return result;
    }
    public void setResult(String result) {
        this.result = result;
        notifyPropertyChanged(com.app.mvc_demo.BR.result);
    }

}

我們可以看到在使用了databinding布局之后 我們可以直接就在xml文件 view 調(diào)用viewmodel里面的方法 然后我們在viewmodel中持有mvvmmodel的引用 我們實現(xiàn)了mvvmmodel中方法 然后我們通過
notifyPropertyChanged 來刷新xml文件 view 中顯示效果.
使用了mvvm框架+上databinding可以·直接view調(diào)用viewmodel中的方法 大大簡化了我們activity的邏輯代碼 但是有些必須在activity中實現(xiàn)的代碼 例如申請權(quán)限之類我們可以通過 可以使用LiveData+ViewModel 去處理這個同學(xué)們可以自己去研究 我這邊就不張開講了

最后總結(jié)

在安卓開發(fā)中我們應(yīng)該都有接觸到這三方代碼架構(gòu)模式 個人覺得具體用那種代碼架構(gòu)看項目 個開發(fā)項目組 我是做游戲SDK開發(fā) 就一個 所以我個人在開發(fā)中 首選mvc 應(yīng)為mvvm要引入很多三方庫不友好 mvp要封裝很多接口 項目本身邏輯少 所以我也不去用 要是開發(fā)app同學(xué)可以優(yōu)先使用mvvm或者mvp 在代碼整個解耦方面和后期維護(hù) 要比mvc要友好很多,最后希望我的文章能幫助到各位解決問題 梆砸,以后我還會貢獻(xiàn)更多有用的代碼分享給大家。各位同學(xué)如果覺得文章還不錯 园欣,麻煩給關(guān)注和star帖世,小弟在這里謝過啦!

項目地址:

碼云 :https://gitee.com/qiuyu123/mvvm_demo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市沸枯,隨后出現(xiàn)的幾起案子日矫,更是在濱河造成了極大的恐慌,老刑警劉巖绑榴,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哪轿,死亡現(xiàn)場離奇詭異,居然都是意外死亡翔怎,警方通過查閱死者的電腦和手機(jī)窃诉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赤套,“玉大人褐奴,你說我怎么就攤上這事∮诒校” “怎么了敦冬?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長唯沮。 經(jīng)常有香客問我脖旱,道長,這世上最難降的妖魔是什么介蛉? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任萌庆,我火速辦了婚禮,結(jié)果婚禮上币旧,老公的妹妹穿的比我還像新娘践险。我一直安慰自己,他們只是感情好吹菱,可當(dāng)我...
    茶點故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布巍虫。 她就那樣靜靜地躺著,像睡著了一般鳍刷。 火紅的嫁衣襯著肌膚如雪占遥。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死宙橱,一個胖子當(dāng)著我的面吹牛搔啊,可吹牛的內(nèi)容都是我干的柬祠。 我是一名探鬼主播,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼负芋,長吁一口氣:“原來是場噩夢啊……” “哼瓶盛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起示罗,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤惩猫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蚜点,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體轧房,經(jīng)...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年绍绘,在試婚紗的時候發(fā)現(xiàn)自己被綠了奶镶。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,444評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡陪拘,死狀恐怖厂镇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情左刽,我是刑警寧澤捺信,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站欠痴,受9級特大地震影響迄靠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜喇辽,卻給世界環(huán)境...
    茶點故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一掌挚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧菩咨,春花似錦吠式、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至缨硝,卻和暖如春摩钙,著一層夾襖步出監(jiān)牢的瞬間罢低,已是汗流浹背查辩。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工胖笛, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人宜岛。 一個月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓长踊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親萍倡。 傳聞我的和親對象是個殘疾皇子身弊,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,455評論 2 359

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