React Native Android原生模塊開發(fā)實戰(zhàn)|教程|心得|如何創(chuàng)建React Native Android原生模塊

尊重版權(quán),未經(jīng)授權(quán)不得轉(zhuǎn)載
本文出自:賈鵬輝的技術(shù)博客(http://www.reibang.com/p/dd37ab5f9a09)

告訴大家一個好消息,為大家精心準(zhǔn)備的React Native視頻教程發(fā)布了狮鸭,大家現(xiàn)可以看視頻學(xué)React Native了合搅。

前言

一直想寫一下我在React Native原生模塊封裝方面的一些經(jīng)驗和心得,來分享給大家歧蕉,但實在抽不開身灾部,今天看了一下日歷發(fā)現(xiàn)馬上就春節(jié)了,所以就趕在春節(jié)之前將這篇博文寫好并發(fā)布(其實是兩篇:要看iOS篇的點這里《React Native iOS原生模塊開發(fā)》)惯退。

我平時在用React Native開發(fā)App時會用到一些原生模塊赌髓,比如:在做社會化分享、第三方登錄、掃描锁蠕、通信錄夷野,日歷等等,想必大家也是一樣匿沛。

關(guān)于在React Native中使用原生模塊扫责,在這里引用React Native官方文檔的一段話:

有時候App需要訪問平臺API,但在React Native可能還沒有相應(yīng)的模塊逃呼。或者你需要復(fù)用一些Java代碼者娱,而不想用JavaScript再重新實現(xiàn)一遍抡笼;又或者你需要實現(xiàn)某些高性能的、多線程的代碼黄鳍,譬如圖片處理推姻、數(shù)據(jù)庫、或者一些高級擴(kuò)展等等框沟。
我們把React Native設(shè)計為可以在其基礎(chǔ)上編寫真正的原生代碼藏古,并且可以訪問平臺所有的能力。這是一個相對高級的特性忍燥,我們并不期望它應(yīng)當(dāng)在日常開發(fā)的過程中經(jīng)常出現(xiàn)拧晕,但它確實必不可少,而且是存在的梅垄。如果React Native還不支持某個你需要的原生特性厂捞,你應(yīng)當(dāng)可以自己實現(xiàn)對該特性的封裝。

上面是我翻譯React Native官方文檔上的一段話队丝,大家如果想看英文版可以點這里:Native Modules
在這篇文章中呢靡馁,我會帶著大家來開發(fā)一個從相冊獲取照片并裁切照片的項目,并結(jié)合這個項目來具體講解一下如何一步步開發(fā)React Native Android原生模塊的机久。

[圖片上傳失敗...(image-f79608-1544634754617)]

提示:告訴大家一個好消息臭墨,React Native視頻教程發(fā)布了,大家現(xiàn)可以看視頻學(xué)React Native了膘盖。

首先胧弛,讓我們先看一下振劳,開發(fā)Android原生模塊的主要流程鸟蟹。

開發(fā)Android原生模塊的主要流程

在這里我把構(gòu)建React Native Android原生模塊的流程概括為以下三大步:

  1. 編寫原生模塊的相關(guān)Java代碼;
  2. 暴露接口與數(shù)據(jù)交互勒魔;
  3. 注冊與導(dǎo)出React Native原生模塊践图;

接下來讓我們一起來看一下每一步所需要做的一些事情掺冠。

原生模塊開發(fā)實戰(zhàn)

在這里我們就以開發(fā)一個從相冊獲取照片并裁切照片的實戰(zhàn)項目,來具體講解一下如何開發(fā)React Native Android原生模塊的。

編寫原生模塊的相關(guān)Java代碼

這一步我們需要用到AndroidStudio德崭。
首先我們用AndroidStudio打開React Native項目根目錄下的android目錄斥黑,如圖:

open-react-native-android-native-project

用AndroidStudio第一次打開這個Android項目的時候,AndroidStudio會下載一些此項目所需要的依賴眉厨,比如項目所依賴的Gradle版本等锌奴。這些依賴下載完成之后呢,AndroidStudio會對項目進(jìn)行初始化憾股,初始化成功之后在AndroidStudio的工具欄中可以看到一個名為“app”的一個可運行的模塊鹿蜀,如圖:

open-react-native-android-native-project-success

接下來呢,我們就可以編寫Java代碼了服球。

首先呢茴恰,我們先來實現(xiàn)一個Crop接口:

public interface Crop {
    /**
     * 選擇并裁切照片
     * @param outputX
     * @param outputY
     * @param promise
     */
    void selectWithCrop(int outputX,int outputY,Promise promise);
}

我們創(chuàng)建一個CropImpl.java,在這個類中呢斩熊,我們實現(xiàn)了從相冊選擇照片以及裁切照片的功能:

/**
 * React Native Android原生模塊開發(fā)
 * Author: CrazyCodeBoy
 * 技術(shù)博文:http://www.devio.org
 * GitHub:https://github.com/crazycodeboy
 * Email:crazycodeboy@gmail.com
 */

public class CropImpl implements ActivityEventListener,Crop{
    private final int RC_PICK=50081;
    private final int RC_CROP=50082;
    private final String CODE_ERROR_PICK="用戶取消";
    private final String CODE_ERROR_CROP="裁切失敗";

    private Promise pickPromise;
    private Uri outPutUri;
    private int aspectX;
    private int aspectY;
    private Activity activity;
    public static CropImpl of(Activity activity){
        return new CropImpl(activity);
    }

    private CropImpl(Activity activity) {
        this.activity = activity;
    }
    public void updateActivity(Activity activity){
        this.activity=activity;
    }
    @Override
    public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
        if(requestCode==RC_PICK){
            if (resultCode == Activity.RESULT_OK && data != null) {//從相冊選擇照片并裁剪
                outPutUri= Uri.fromFile(Utils.getPhotoCacheDir(System.currentTimeMillis()+".jpg"));
                onCrop(data.getData(),outPutUri);
            } else {
                pickPromise.reject(CODE_ERROR_PICK,"沒有獲取到結(jié)果");
            }
        }else if(requestCode==RC_CROP){
            if (resultCode == Activity.RESULT_OK) {
                pickPromise.resolve(outPutUri.getPath());
            }else {
                pickPromise.reject(CODE_ERROR_CROP,"裁剪失敗");
            }
        }
    }

    //...省略部分代碼
  
    private void onCrop(Uri targetUri,Uri outputUri){
        this.activity.startActivityForResult(IntentUtils.getCropIntentWith(targetUri,outputUri,aspectX,aspectY),RC_CROP);
    }
}

查看視頻教程

關(guān)于Android拍照往枣、從相冊或文件中選擇照片,裁剪以及壓縮照片等更高級的功能實現(xiàn)粉渠,大家可以參考開源項目TakePhoto

實現(xiàn)了從相冊選擇照片以及裁切照片的功能之后呢分冈,接下來我們需要將public void selectWithCrop(int aspectX, int aspectY, Promise promise)暴露給React Native,以供js調(diào)用霸株。

暴露接口與數(shù)據(jù)交互

接下了我們就向React Native暴露接口以及做一些數(shù)據(jù)交互部分的操作雕沉。為了暴露接口以及進(jìn)行數(shù)據(jù)交互我們需要借助React Native的ReactContextBaseJavaModule類,在這里我們創(chuàng)建一個ImageCropModule.java類讓它繼承自ReactContextBaseJavaModule淳衙。

創(chuàng)建一個ReactContextBaseJavaModule

/**
 * React Native Android原生模塊開發(fā)
 * Author: CrazyCodeBoy
 * 技術(shù)博文:http://www.devio.org
 * GitHub:https://github.com/crazycodeboy
 * Email:crazycodeboy@gmail.com
 */

public class ImageCropModule extends ReactContextBaseJavaModule implements Crop{
    private CropImpl cropImpl;
    public ImageCropModule(ReactApplicationContext reactContext) {
        super(reactContext);
    }

    @Override
    public String getName() {
        return "ImageCrop";
    }
  
    //...省略部分代碼
  
    @Override @ReactMethod
    public void selectWithCrop(int aspectX, int aspectY, Promise promise) {
        getCrop().selectWithCrop(aspectX,aspectY,promise);
    }
    private CropImpl getCrop(){
        if(cropImpl==null){
            cropImpl=CropImpl.of(getCurrentActivity());
            getReactApplicationContext().addActivityEventListener(cropImpl);
        }else {
            cropImpl.updateActivity(getCurrentActivity());
        }
        return cropImpl;
    }
}

查看視頻教程

ImageCropModule.java類中蘑秽,我們重寫了public String getName()方法,來暴露我們原生模塊的名字箫攀。并在public void selectWithCrop(int aspectX, int aspectY, Promise promise)上添加了@ReactMethod注解來暴露接口肠牲,這樣以來我們就可以在js文件中通過ImageCrop.selectWithCrop來調(diào)用我們所暴露給React Native的接口了。

接下來呢靴跛,我們來看一下原生模塊和js模塊是如何進(jìn)行數(shù)據(jù)交互的缀雳?

原生模塊和JS進(jìn)行數(shù)據(jù)交互

在我們要實現(xiàn)的從相冊選擇照片并裁切的項目中,js模塊需要告訴原生模塊照片裁切的比例梢睛,等照片裁切完成后肥印,原生模塊需要對js模塊進(jìn)行回調(diào)來告訴js模塊照片裁切的結(jié)果,在這里我們需要將照片裁切后生成的圖片的路徑告訴js模塊绝葡。

提示:在所有的情況下js和原生模塊之前進(jìn)行通信都是在異步的情況下進(jìn)行的深碱。

接下來我們就來看下一JS是如何向原生模塊傳遞數(shù)據(jù)的?

JS向原生模塊傳遞數(shù)據(jù):

為了實現(xiàn)JS向原生模塊進(jìn)行傳遞數(shù)據(jù)藏畅,我們可以直接通過調(diào)用原生模塊所暴露出來的接口敷硅,來為接口方法設(shè)置參數(shù)。這樣以來我們就可以將數(shù)據(jù)通過接口參數(shù)傳遞到原生模塊中,如:

  /**
     * 選擇并裁切照片
     * @param outputX
     * @param outputY
     * @param promise
     */
    void selectWithCrop(int outputX,int outputY,Promise promise);

通過上述代碼我們可以看出绞蹦,js模塊可以通過selectWithCrop方法來告訴原生模塊要裁切照片的寬高比力奋,最后一個參數(shù)是一個Promise,照片裁剪完成之后呢幽七,原生模塊可以通過Promise來對js模塊進(jìn)行回調(diào)景殷,來告訴裁切結(jié)果。

既然是js和Java進(jìn)行數(shù)據(jù)傳遞澡屡,那么他們兩者之間是如何進(jìn)行類型轉(zhuǎn)換的呢:
在上述例子中我們通過@ReactMethod注解來暴露接口猿挚,被 @ReactMethod標(biāo)注的方法支持如下幾種數(shù)據(jù)類型。

@ReactMethod標(biāo)注的方法支持如下幾種數(shù)據(jù)類型的參數(shù):

Boolean -> Bool
Integer -> Number
Double -> Number
Float -> Number
String -> String
Callback -> function
ReadableMap -> Object
ReadableArray -> Array

原生模塊向JS傳遞數(shù)據(jù):

原生模塊向JS傳遞數(shù)據(jù)我們可以借助Callbacks與Promises挪蹭,接下來就講一下如何通過他們兩個進(jìn)行數(shù)據(jù)傳遞的亭饵。

Callbacks

原生模塊支持一個特殊類型的參數(shù)-Callbacks,我們可以通過它來對js進(jìn)行回調(diào)梁厉,以告訴js調(diào)用原生模塊方法的結(jié)果。
將我們selectWithCrop的參數(shù)改為Callbacks之后:

@Override
public void selectWithCrop(int aspectX, int aspectY, Callback errorCallback,Callback successCallback) {
    this.errorCallback=errorCallback;
    this.successCallback=successCallback;
    this.aspectX=aspectX;
    this.aspectY=aspectY;
    this.activity.startActivityForResult(IntentUtils.getPickIntentWithGallery(),RC_PICK);
}

在回調(diào)的時候踏兜,我們就可以這樣寫:

if (resultCode == Activity.RESULT_OK) {
    successCallback.invoke(outPutUri.getPath());
}else {
    errorCallback.invoke(CODE_ERROR_CROP,"裁剪失敗");
}

在上述代碼中我們通過Callbackinvoke方法來對js進(jìn)行對調(diào)词顾,下面我們來看一下Callback.java的源碼:

public interface Callback {
  /**
   * Schedule javascript function execution represented by this {@link Callback} instance
   *
   * @param args arguments passed to javascript callback method via bridge
   */
  public void invoke(Object... args);
}

Callback.java的源碼中我們可以看出,它是一個只有一個public void invoke(Object... args)方法的接口碱妆,invoke方法接受一個可變參數(shù)肉盹,所以我們可以向js傳遞多個參數(shù)。

接下來呢疹尾,我們在js中就可以這樣來調(diào)用我們所暴露的接口:

ImageCrop.selectWithCrop(parseInt(x),parseInt(y),(error)=>{
    console.log(error);
},(result)=>{
    console.log(result);
})

提示:另外要告訴大家的是上忍,無論是Callback還是我接下來要講的Promise,我們只能調(diào)用一次纳本,也就是"you call me once,I can only call you once"窍蓝。

Promises

除了上文所講的Callback之外React Native還為了我們提供了另外一種回調(diào)js的方式叫-Promise。如果我們暴露的接口方法的最后一個參數(shù)是Promise時繁成,如:

@Override @ReactMethod
public void selectWithCrop(int aspectX, int aspectY, Promise promise) {
    getCrop().selectWithCrop(aspectX,aspectY,promise);
}

那么當(dāng)js調(diào)用它的時候?qū)祷匾粋€Promsie:

ImageCrop.selectWithCrop(parseInt(x),parseInt(y)).then(result=> {
    this.setState({
        result: result
    })
}).catch(e=> {
    this.setState({
        result: e
    })
});

另外吓笙,我們也可以使用ES2016的 async/await語法,來簡化我們的代碼:

async onSelectCrop() {
    var result=await ImageCrop.selectWithCrop(parseInt(x),parseInt(y));
}

這樣以來代碼就簡化了很多巾腕。

因為面睛,基于回調(diào)的數(shù)據(jù)傳遞無論是Callback還是Promise,都只能調(diào)用一次尊搬。但叁鉴,在實際項目開發(fā)中我們有時會向js多次傳遞數(shù)據(jù),比如二維碼掃描原生模塊佛寿,針對這種多次數(shù)據(jù)傳遞的情況我們該怎么實現(xiàn)呢幌墓?

接下來我就為大家介紹一種原生模塊可以向js多次傳遞數(shù)據(jù)的方式:

向js發(fā)送事件

在原生模塊中我們可以向js發(fā)送多次事件,即使原生模塊沒有被直接的調(diào)用。為了向js傳遞事件我們需要用到RCTDeviceEventEmitter克锣,它是原生模塊和js之間的一個事件發(fā)射器茵肃。

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

在上述方法中我們可以向js模塊發(fā)送任意次數(shù)的事件,其中eventName是我們要發(fā)送事件的事件名袭祟,params是此次事件所攜帶的數(shù)據(jù)验残,接下來呢我們就可以在js模塊中監(jiān)聽這個事件了:

componentDidMount() {
    //注冊掃描監(jiān)聽
    DeviceEventEmitter.addListener('onScanningResult',this.onScanningResult);
}
onScanningResult = (e)=> {
    this.setState({
        scanningResult: e.result,
    });
}

另外,不要忘記在組件被卸載的時候移除監(jiān)聽:

componentWillUnmount(){
    DeviceEventEmitter.removeListener('onScanningResult',this.onScanningResult);//移除掃描監(jiān)聽
}

到現(xiàn)在呢巾乳,暴露接口以及數(shù)據(jù)傳遞已經(jīng)進(jìn)行完了您没,接下來呢,我們就需要注冊與導(dǎo)出React Native原生模塊了胆绊。

注冊與導(dǎo)出React Native原生模塊

為了向React Native注冊我們剛才創(chuàng)建的原生模塊氨鹏,我們需要實現(xiàn)ReactPackageReactPackage主要為注冊原生模塊所存在压状,只有已經(jīng)向React Native注冊的模塊才能在js模塊使用仆抵。

/**
 * React Native Android原生模塊開發(fā)
 * Author: CrazyCodeBoy
 * 技術(shù)博文:http://www.devio.org
 * GitHub:https://github.com/crazycodeboy
 * Email:crazycodeboy@gmail.com
 */
public class ImageCropReactPackage implements ReactPackage {
    @Override
    public List<Class<? extends JavaScriptModule>> createJSModules() {
        return Collections.emptyList();
    }
    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }
    @Override
    public List<NativeModule> createNativeModules(
            ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new ImageCropModule(reactContext));
        return modules;
    }
}

查看視頻教程

在上述代碼中,我們實現(xiàn)一個ReactPackage种冬,接下來呢镣丑,我們還需要在android/app/src/main/java/com/your-app-name/MainApplication.java中注冊我們的ImageCropReactPackage

@Override
protected List<ReactPackage> getPackages() {
    return Arrays.<ReactPackage>asList(
            new MainReactPackage(),
            new ImageCropReactPackage()//在這里將我們剛才創(chuàng)建的ImageCropReactPackage添加進(jìn)來
    );
}

原生模塊注冊完成之后呢,我們接下來就需要為我們的原生模塊導(dǎo)出一個js模塊娱两,以方便我們使用它莺匠。

我們創(chuàng)建一個ImageCrop.js文件,然后添加如下代碼:

import { NativeModules } from 'react-native';
export default NativeModules.ImageCrop;

這樣以來呢十兢,我們就可以在其他地方通過下面方式來使用我們所導(dǎo)出的這個模塊了:

import ImageCrop from './ImageCrop' //導(dǎo)入ImageCrop.js
//...省略部分代碼

    onSelectCrop() {
        let x=this.aspectX?this.aspectX:ASPECT_X;
        let y=this.aspectY?this.aspectY:ASPECT_Y;
        ImageCrop.selectWithCrop(parseInt(x),parseInt(y)).then(result=> {
            this.setState({
                result: result
            })
        }).catch(e=> {
            this.setState({
                result: e
            })
        });
    }
//...省略部分代碼
}

查看視頻教程

現(xiàn)在呢趣竣,我們這個原生模塊就開發(fā)好了,而且我們也使用了我們的這個原生模塊旱物。關(guān)于Android拍照遥缕、從相冊或文件中選擇照片,裁剪以及壓縮照片等更高級的功能實現(xiàn)异袄,大家也可以參考開源項目TakePhoto

關(guān)于線程

在React Native中通砍,JS模塊運行在一個獨立的線程中。在我們?yōu)镽eact Native開發(fā)原生模塊的時候烤蜕,如果有耗時的操作比如:文件讀寫封孙、網(wǎng)絡(luò)操作等,我們需要新開辟一個線程讽营,不然的話虎忌,這些耗時的操作會阻塞JS線程。在Android中我們可以借助AsyncTask來實現(xiàn)多線程橱鹏。另外膜蠢,如果原生模塊中需要更新UI堪藐,我們需要獲取主線程,然后在主線程中更新UI挑围,如:

        activity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (!activity.isFinishing()) {

                    mSplashDialog = new Dialog(activity,fullScreen? R.style.SplashScreen_Fullscreen:R.style.SplashScreen_SplashTheme);
                    mSplashDialog.setContentView(R.layout.launch_screen);
                    mSplashDialog.setCancelable(false);

                    if (!mSplashDialog.isShowing()) {
                        mSplashDialog.show();
                    }
                }
            }
        });

可參考:SplashScreen.java

告訴大家一個好消息礁竞,為大家精心準(zhǔn)備的React Native視頻教程發(fā)布了,大家現(xiàn)可以看視頻學(xué)React Native了杉辙。

如果模捂,大家在開發(fā)原生模塊中遇到問題可以在本文的下方進(jìn)行留言,我看到了后會及時回復(fù)的哦蜘矢。
另外也可以關(guān)注我的新浪微博狂男,或者關(guān)注我的Github來獲取更多有關(guān)React Native開發(fā)的技術(shù)干貨

推薦學(xué)習(xí):視頻教程《最新版React Native+Redux打造高質(zhì)量上線App》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末品腹,一起剝皮案震驚了整個濱河市岖食,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌舞吭,老刑警劉巖泡垃,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異羡鸥,居然都是意外死亡兔毙,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門兄春,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人锡溯,你說我怎么就攤上這事赶舆。” “怎么了祭饭?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵芜茵,是天一觀的道長。 經(jīng)常有香客問我倡蝙,道長九串,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任寺鸥,我火速辦了婚禮猪钮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘胆建。我一直安慰自己烤低,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布笆载。 她就那樣靜靜地躺著扑馁,像睡著了一般涯呻。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上腻要,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天复罐,我揣著相機(jī)與錄音,去河邊找鬼雄家。 笑死效诅,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的咳短。 我是一名探鬼主播填帽,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼咙好!你這毒婦竟也來了篡腌?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤勾效,失蹤者是張志新(化名)和其女友劉穎嘹悼,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體层宫,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡杨伙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了萌腿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片限匣。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖毁菱,靈堂內(nèi)的尸體忽然破棺而出米死,到底是詐尸還是另有隱情,我是刑警寧澤贮庞,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布峦筒,位于F島的核電站,受9級特大地震影響窗慎,放射性物質(zhì)發(fā)生泄漏物喷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一遮斥、第九天 我趴在偏房一處隱蔽的房頂上張望峦失。 院中可真熱鬧,春花似錦伏伐、人聲如沸宠进。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽材蹬。三九已至实幕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間堤器,已是汗流浹背昆庇。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留闸溃,地道東北人整吆。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像辉川,于是被迫代替她去往敵國和親表蝙。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354

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