React Native-原生模塊開發(fā)

前言:

所謂原生模塊開發(fā)藕赞,很重要的就是Android/iOS原生代碼和ReactNative代碼的交互缚忧,以及在ReactNative代碼中使用原生的組件哼凯。下面將從這兩個地方開始講解(示例以Android平臺為基礎(chǔ)):

先回顧一下RN組件的生命周期:

constructor():
在組件被加載前最先調(diào)用,第一個語句必須是super(props)。

componentWillMount():
在初始渲染(render函數(shù)被RN框架執(zhí)行之前)前被執(zhí)行耻涛。在函數(shù)中調(diào)用setState函數(shù)改變了某些狀態(tài)機(jī)變量的值废酷,RN框架不會立馬執(zhí)行渲染操作,而是等該函數(shù)執(zhí)行完之后執(zhí)行初始渲染抹缕。

componentDidMount():
執(zhí)行完初始渲染之后立馬被調(diào)用澈蟆。將從網(wǎng)絡(luò)側(cè)請求數(shù)據(jù)的代碼放在這里比較合適。

componentWillReceiveProps(nextProps):
RN初始化渲染執(zhí)行完成后歉嗓,當(dāng)RN組件收到新的props時丰介,這個函數(shù)被調(diào)用。函數(shù)接受的參數(shù)是一個object為新的props鉴分。

shouldComponentUpdate(nextProps, nextState):
RN初始化渲染執(zhí)行完成后哮幢,當(dāng)RN組件接收到新的state或者props時,該函數(shù)被調(diào)用志珍。接受兩個參數(shù):第一個是新的props橙垢,第二個是新的state。該函數(shù)返回一個布爾類型的值伦糯,表示RN框架針對此次改變是否重新渲染該組件柜某。

componentWillUpdate(nextProps, nextState):
RN組件在初始化渲染執(zhí)行完成之后,RN框架在重新渲染RN組件前會調(diào)用這個函數(shù)敛纲。

componentDidUpdate(prevProps, prevState):
RN組件在初始化渲染執(zhí)行完成之后喂击,RN框架在重新渲染RN組件完成后會調(diào)用這個函數(shù),傳入的兩個參數(shù)是渲染前的Props和state淤翔。

componentWillUnmount():
在RN組件被卸載前翰绊,這個函數(shù)被執(zhí)行。

一旁壮、原生代碼和RN代碼的交互:

1 RN側(cè)主動調(diào)用原生模塊方法:回調(diào)函數(shù)和Promise機(jī)制

首先在電腦中部署Android開發(fā)和RN開發(fā)所需要的環(huán)境:
打開Android端的初始化項目(名為MyRNProject):
工程中只有一個Activity监嗜,且繼承于ReactActivity,將該Activity設(shè)置為應(yīng)用的入口抡谐,getMainComponentName()返回服務(wù)器上對應(yīng)的組件名稱

package com.myrnproject;

import android.os.Bundle;

import com.facebook.react.ReactActivity;

public class MainActivity extends ReactActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
    /**
     * Returns the name of the main component registered from JavaScript.
     * This is used to schedule rendering of the component.
     */
    @Override
    protected String getMainComponentName() {
        return "MyRNProject";
    }
}

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.myrnproject"
    android:versionCode="1"
    android:versionName="1.0">

    <uses-sdk
        android:minSdkVersion="16"
        android:targetSdkVersion="22" />

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <application
        android:name=".MainApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
            android:label="@string/app_name"
            android:windowSoftInputMode="adjustResize">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
    </application>
</manifest>

MainApplication為工程的Application裁奇,同樣需要繼承ReactApplication,getJSMainModuleName()函數(shù)返回加載的第一個js文件名稱麦撵。將需要注冊的ReactPackage添加到這里protected List<ReactPackage> getPackages(){}刽肠,new MyRNPackage()為我們自己定義的ReactPackage,該類用來創(chuàng)建工程中的NativeModule以及ViewManager等類厦坛。NativeModule為原生為RN提供的方法五垮,ViewManager為供RN使用的原生組件。

package com.myrnproject;

import android.app.Application;

import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.soloader.SoLoader;

import java.util.Arrays;
import java.util.List;

public class MainApplication extends Application implements ReactApplication {

    private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
        @Override
        public boolean getUseDeveloperSupport() {
            return BuildConfig.DEBUG;
        }

        @Override
        protected List<ReactPackage> getPackages() {
            return Arrays.<ReactPackage>asList(
                    new MainReactPackage(),
                    new MyRNPackage()
      );
        }

        @Override
        protected String getJSMainModuleName() {
            return "index";
        }
    };

    @Override
    public ReactNativeHost getReactNativeHost() {
        return mReactNativeHost;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        SoLoader.init(this, /* native exopackage */ false);
    }
}

package com.myrnproject;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by tianxiying on 2017/12/6.
 */

public class MyRNPackage implements ReactPackage {
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> moudles = new ArrayList<>();
        moudles.add(new AddressModule(reactContext));   //加入開發(fā)接口
        moudles.add(new ToastModule(reactContext));
        moudles.add(new EventModule(reactContext));
        return moudles;
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        List<ViewManager> list = new ArrayList<ViewManager>();
        list.add(new MyTextViewManager());
        list.add(new MyCalendarViewManager());
        return list;
    }
}

接下來我要為RN提供一個安卓原生代碼的方法杜秸,用Toast舉例:我創(chuàng)建了一個ToastModule繼承于ReactContextBaseJavaModule,getName() 函數(shù)返回模塊名稱润绎,在RN中通過該名稱找到該模塊撬碟,在上面的MyRNPackage中我們已將其加入管理器中诞挨。
內(nèi)部代碼如下:

package com.myrnproject;

import android.os.Handler;
import android.widget.Toast;

import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;

import java.util.HashMap;
import java.util.Map;

/**
 * Created by tianxiying on 2017/12/14.
 */

public class ToastModule extends ReactContextBaseJavaModule {
    private ReactApplicationContext aContext;
    private Promise interfacePromise;
    private Handler handler;
    private static final String DURATION_SHORT_KEY = "SHORT";
    private static final String DURATION_LONG_KEY = "LONG";

    public ToastModule(ReactApplicationContext reactContext) {
        super(reactContext);
        aContext = reactContext;

    }

    @Override
    public Map<String, Object> getConstants() {
        final Map<String, Object> constants = new HashMap<>();
        constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT);
        constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG);
        return constants;
    }

    @Override
    public String getName() {
        return "ToastModule";
    }

    @ReactMethod
    public void showToast(String message, int duration, Callback callback) {
        try {
            Toast.makeText(aContext, message, duration).show();
            callback.invoke("success");
        } catch (Exception e) {
            callback.invoke("exception" + e.toString());
        }
    }

    @ReactMethod
    public void showToastLater(String message, int duration, final Callback callback) {
        try {
            Toast.makeText(aContext, message, duration).show();
            handler = new Handler();
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    doCallBack(callback);
                }
            }, 5000);
        } catch (Exception e) {
            callback.invoke("exception" + e.toString());
        }
    }

    public void doCallBack(Callback callback) {
        callback.invoke("原生5秒后回調(diào)CallBack");
    }
}

1.1 回調(diào)函數(shù):
RN調(diào)用原生方法,然后在原生側(cè)調(diào)用RN側(cè)提供的回調(diào)函數(shù)呢蛤,實現(xiàn)交互惶傻。
以ToastModule為例,加上@ReactMethod注解的方法為RN模塊可以直接調(diào)用方法:

@ReactMethod
    public void showToast(String message, int duration, Callback callback) {
        try {
            Toast.makeText(aContext, message, duration).show();
            callback.invoke("success");
        } catch (Exception e) {
            callback.invoke("exception" + e.toString());
        }
    }

該方法有三個參數(shù)其障,分別是信息字符串银室、顯示時長以及RN端傳入的回調(diào),getConstants()中約定了對應(yīng)的變量方便找RN端使用励翼。

在RN端的代碼如下:首先將ToastModule導(dǎo)入蜈敢,在showToast函數(shù)中直接調(diào)用ToastModule.showToast(),將需要參數(shù)包含回調(diào)函數(shù)傳入∑В回調(diào)成功將改變state中的變量值抓狭。

import React, {Component} from 'react';
import {
    View,
    Text,
    Image,
    StyleSheet,
    PixelRatio,
    TouchableOpacity,
    NativeModules,
    DeviceEventEmitter,
    Platform,
} from 'react-native';

let ToastModule = NativeModules.ToastModule;
...
<Text style={styles.text_title}>Toast(回調(diào)函數(shù))</Text>
                <TouchableOpacity onPress={(message) => this.showToast("hello world!")}>
                    <Text style={styles.text_item}>
                        {"回調(diào)結(jié)果:" + this.state.interfaceResult}
                    </Text>
                </TouchableOpacity>
...
showToast(message) {
        console.log("showToast!");
        if (Platform.OS === "android") {
            ToastModule.showToast(message,
                ToastModule.SHORT,
                (msg) => {
                    this.setState({
                        interfaceResult: msg
                    })
                });
        }
    }

下面是運行截圖:


image

image

原生代碼調(diào)用了回調(diào)函數(shù)造烁,回調(diào)函數(shù)并不會馬上執(zhí)行否过,因為是混合開發(fā)的橋接方式本來就是異步的)
注意事項:需要注意的是,回調(diào)函數(shù)必須要RN側(cè)提供的情況下才能調(diào)用惭蟋,調(diào)用Callback.invoke()即可執(zhí)行回調(diào)方法苗桂,也可以將Callback引用暫時保存起來,不立即調(diào)用告组,但是該回調(diào)方法只能調(diào)用一次煤伟,多于一次將會如下錯誤:

image

1.2 Promise機(jī)制
建議使用,因為Promise在RN中使用非常廣泛惹谐,能力也非常強(qiáng)大持偏。
下面我在RN模塊中,調(diào)用原生代碼讀取手機(jī)通訊錄并將其顯示在界面上:

package com.myrnproject;

import android.app.Activity;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract;

import com.facebook.react.bridge.ActivityEventListener;
import com.facebook.react.bridge.BaseActivityEventListener;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;

import org.json.JSONObject;

import static android.app.Activity.RESULT_OK;

/**
 * Created by tianxiying on 2017/12/6.
 */

public class AddressModule extends ReactContextBaseJavaModule {
    private ReactApplicationContext aContext;
    private Promise interfacePromise;
    private final ActivityEventListener mActivityEventListener = new BaseActivityEventListener() {
        @Override
        public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
            if (requestCode != 1 || resultCode != RESULT_OK) return;
            Uri contactData = data.getData();
            Cursor cursor = activity.managedQuery(contactData, null, null, null, null);
            cursor.moveToFirst();
            String toRNMessage = getContactInfo(cursor);
            if (toRNMessage != null) {
                interfacePromise.resolve(toRNMessage);
            }
        }
    };

    private String getContactInfo(Cursor cursor) {
        try {
            String name = "";
            String phoneNumber = "";
            int idColumn = cursor.getColumnIndex(ContactsContract.Contacts._ID);
            String contactId = cursor.getString(idColumn);
            String queryString = ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=" + contactId;
            Uri aUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
            Cursor phone = aContext.getContentResolver().query(aUri, null, queryString,
                    null, null);
            String dn = ContactsContract.Contacts.DISPLAY_NAME;
            String pn = ContactsContract.CommonDataKinds.Phone.NUMBER;
            if (phone.moveToFirst()) {
                for (; !phone.isAfterLast(); phone.moveToNext()) {
                    dn = name = cursor.getString(cursor.getColumnIndex(dn));
                    phoneNumber = phone.getString(phone.getColumnIndex(pn));
                }
                phone.close();
            }
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("msgType","pickContactResult");
            jsonObject.put("displayName",name);
            jsonObject.put("peerNumber",phoneNumber);
            return jsonObject.toString();
        } catch (Exception e) {
            interfacePromise.reject("error while get contact", e);
        }
        return null;
    }

    public AddressModule(ReactApplicationContext reactContext) {
        super(reactContext);
        aContext = reactContext;
        reactContext.addActivityEventListener(mActivityEventListener);
    }

    @Override
    public String getName() {
        return "AddressModule";
    }

    @ReactMethod
    public void handleMessage(String aMessage, Promise aPromise) {
        interfacePromise = aPromise;
        Intent aIntent = new Intent(Intent.ACTION_PICK);
        aIntent.setType(ContactsContract.Contacts.CONTENT_TYPE);
        Bundle b = new Bundle();
        aContext.startActivityForResult(aIntent, 1, b);
    }
}

重點關(guān)注一下 public void handleMessage(String aMessage, Promise aPromise)函數(shù)氨肌,它有兩個參數(shù)鸿秆,分別是一個消息字符串和一個Promise引用,在該方法被調(diào)用時我們將Promise引用保存下來為interfacePromise怎囚,ActivityEventListener負(fù)責(zé)監(jiān)聽Activity的生命周期卿叽,我們已將其注冊,在通訊錄頁面返回的時候恳守,onActivityResult將被調(diào)用考婴,我們將用戶點擊的通訊錄好友信息獲取到保存為Json格式,使用interfacePromise.resolve(toRNMessage)將其返回到RN端催烘。

RN端代碼如下:在then函數(shù)中處理成功回調(diào)沥阱,在catch處理異常回調(diào)伊群。

import React, {Component} from 'react';
import {
    View,
    Text,
    Image,
    StyleSheet,
    PixelRatio,
    TouchableOpacity,
    NativeModules,
    DeviceEventEmitter,
    Platform,
} from 'react-native';

let AddressModule = NativeModules.AddressModule;
...
<Text style={styles.text_title}>讀取通訊錄(Promise機(jī)制)</Text>
                <TouchableOpacity onPress={() => this.userPressAddressBook()}>
                    <Text style={styles.text_item}>
                        {this.state.ContactName + ":" + this.state.PhoneNumber}
                    </Text>
                </TouchableOpacity>
...
userPressAddressBook() {
        console.log("clickUserPressAddressBook考杉!");
        AddressModule.handleMessage("testMessage").then(
            (result) => {
                console.log(result);
                let aObj = JSON.parse(result);
                this.setState({
                    PhoneNumber: aObj.peerNumber,
                    ContactName: aObj.displayName,
                })
            }
        ).catch(
            (error) => {
                console.log(error);
            }
        );
    }

運行圖如下:


image

image

image

Promise對比回調(diào)機(jī)制不需要設(shè)置和實現(xiàn)事件的監(jiān)聽函數(shù)策精,通過Promise機(jī)制讓異步處理便于書寫、閱讀與理解崇棠。

2 原生代碼主動調(diào)用RN代碼:通過SendEvent——發(fā)送消息的方式咽袜。

首先,我定義了一個EventModule用來主動發(fā)送消息:

package com.myrnproject;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;

import javax.annotation.Nullable;

/**
 * Created by tianxiying on 2017/12/14.
 */

public class EventModule extends ReactContextBaseJavaModule {
    private ReactApplicationContext aContext;

    public EventModule(ReactApplicationContext reactContext) {
        super(reactContext);
        aContext = reactContext;
    }

    @Override
    public String getName() {
        return "EventModule";
    }

    public void sendEvent(ReactContext reactContext, String eventName, @Nullable WritableMap params) {
        reactContext
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(eventName, params);
    }

    @ReactMethod
    public void testSendEvent() {
        WritableMap parms = Arguments.createMap();
        sendEvent(aContext,"testEvent",parms);
    }
}

重點關(guān)注sendEvent方法枕稀,通過reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, params)询刹,其中eventName代表消息名,params代表參數(shù)萎坷。
為了方便測試凹联,我寫了一個testSendEvent方法,由RN界面來觸發(fā)sendEvent方法食铐。

RN中代碼如下:在componentDidMount進(jìn)行了監(jiān)聽事件的注冊匕垫。

import React, {Component} from 'react';
import {
    View,
    Text,
    Image,
    StyleSheet,
    PixelRatio,
    TouchableOpacity,
    NativeModules,
    DeviceEventEmitter,
    Platform,
} from 'react-native';

let EventModule = NativeModules.EventModule;
...
componentDidMount() {
        DeviceEventEmitter.addListener('testEvent', (message) => {
            // handle event.
            console.log("testEvent!" + message);
            this.setReceivedResult();
        });
    }
...
<Text style={styles.text_title}>原生調(diào)RN(sendEvent)</Text>
                <TouchableOpacity onPress={() => this.testSendEvent()}>
                    <Text style={styles.text_item}>
                        {"消息結(jié)果:" + this.state.sendEventResult}
                    </Text>
                </TouchableOpacity>
...
 testSendEvent() {
        console.log("testSendEvent虐呻!");
        EventModule.testSendEvent();
    }
...
 setReceivedResult() {
        console.log("set received象泵!");
        this.setState({
            sendEventResult: 'received sendevent'
        });
    }

運行截圖如下:


image

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市斟叼,隨后出現(xiàn)的幾起案子偶惠,更是在濱河造成了極大的恐慌,老刑警劉巖朗涩,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件忽孽,死亡現(xiàn)場離奇詭異,居然都是意外死亡谢床,警方通過查閱死者的電腦和手機(jī)兄一,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來识腿,“玉大人出革,你說我怎么就攤上這事《伤希” “怎么了骂束?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長成箫。 經(jīng)常有香客問我展箱,道長,這世上最難降的妖魔是什么蹬昌? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任混驰,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘账胧。我一直安慰自己竞慢,他們只是感情好先紫,可當(dāng)我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布治泥。 她就那樣靜靜地躺著,像睡著了一般遮精。 火紅的嫁衣襯著肌膚如雪居夹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天本冲,我揣著相機(jī)與錄音准脂,去河邊找鬼。 笑死檬洞,一個胖子當(dāng)著我的面吹牛狸膏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播添怔,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼湾戳,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了广料?” 一聲冷哼從身側(cè)響起砾脑,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎艾杏,沒想到半個月后韧衣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡购桑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年畅铭,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片勃蜘。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡硕噩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出元旬,到底是詐尸還是另有隱情榴徐,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布匀归,位于F島的核電站坑资,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏穆端。R本人自食惡果不足惜袱贮,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望体啰。 院中可真熱鬧攒巍,春花似錦嗽仪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至兢孝,卻和暖如春窿凤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背跨蟹。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工雳殊, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人窗轩。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓夯秃,卻偏偏與公主長得像,于是被迫代替她去往敵國和親痢艺。 傳聞我的和親對象是個殘疾皇子仓洼,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,086評論 2 355

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