Android項目集成React Native實踐總結(jié)

【最新更新】關(guān)于協(xié)議胆筒, React 和 React Native 的開源license都已經(jīng)更換成了MIT license。

【2017-12-30 更新】:

最近把 react native 版本更新到0.51.0诈豌,react版本更新到16.0.0 之后仆救,再次嘗試,發(fā)現(xiàn)有了一些變化矫渔。

  • 首先彤蔽,js的入口文件變?yōu)榱酥挥幸粋€index.js,而不再是之前的 index.android.js 和
    index.ios.js庙洼。
  • 然后顿痪,對應(yīng)上面一條,在創(chuàng)建 ReactInstanceManager 的實例時也有所變化:
        mReactInstanceManager = ReactInstanceManager.builder()
                .setApplication(MyApplication.instance)
                .setBundleAssetName("index.android.bundle")
                //.setJSMainModuleName("index.android") // 變?yōu)橄旅嬉恍?                .setJSMainModulePath("index")
                .addPackage(new MainReactPackage())
                .setUseDeveloperSupport(Config.DEBUG)
                .setInitialLifecycleState(LifecycleState.RESUMED)
                .build();
  • 另外油够,還發(fā)現(xiàn)蚁袭,如果原來的 android 工程名不叫 ‘a(chǎn)ndroid’(一般都不叫吧),在集成 RN 后無需一定改成 ‘a(chǎn)ndroid', 比如本文示例里的工程名叫 ’code', 集成 RN 后石咬,依然叫 code 也可以揩悄,只需在同級目錄創(chuàng)建 package.json 和 項目的js文件等等,即可鬼悠,經(jīng)測試可以跑起來删性。(這一條的好處是,你的 git 代碼改動記錄會沒有那么嚇人)
  • 還有焕窝,使用了 Atom + nuclide蹬挺,并不好用,暫時還是不如 vscode 順手袜啃,慢慢熟悉吧汗侵,畢竟官方推薦的。

【原文】:

React Native 面世已經(jīng)挺長時間了群发,從去年開始接觸 RN晰韵,做了一款小 App,一次開發(fā)熟妓,支持 Android 和 iOS 兩個平臺雪猪,很方便。但是這其間起愈,尤其是剛開始只恨,也是經(jīng)歷了一個比較陡峭的學(xué)習(xí)曲線译仗,因為ES6Flexbox layout 等等這些都是從頭學(xué)起官觅,一些工具也是為了開發(fā)這個項目才開始接觸纵菌,比如微軟開源的 VS Code 這個編輯器(因為有很多的 plugin,也可以說是 IDE了) —— 開發(fā) RN 好像還沒有像 Eclipse 休涤、Android Studio 或是 Xcode 那樣方便的 IDE咱圆,VS Code 算是很不錯的一個了(官網(wǎng)鏈接);

但是在這些基礎(chǔ)知識基本上手之后功氨,就還算比較順利了序苏,React Native 現(xiàn)在網(wǎng)上也有不少的開源項目和第三方庫,別人造好的輪子已經(jīng)基本可以滿足幾乎所有簡單的需求捷凄,各種文章論壇也不少忱详,遇到問題比較好找答案。如果是有基礎(chǔ)的前端同學(xué)來學(xué) RN 應(yīng)該是一個非常順暢的過程跺涤。

以上說的這個項目是從頭開始就是選擇了純 RN 開發(fā)匈睁,坑還算不多,慢慢地也都填上了钦铁。最近開始嘗試往一個已有的 Android 項目里集成 RN软舌,按照官網(wǎng)以及網(wǎng)上找到的一些文章,還是遇到了一些坑牛曹,自己總結(jié)一下佛点,也供大家參考。

一. 本文示例所依賴的環(huán)境:

  • minSdkVersion:14黎比;為支持RN改成了16超营。
  • compileSdkVersion: 25
  • buildToolsVersion: "25.0.3"
  • targetSdkVersion: 25
  • React Native:0.45.1 (2017-12-30更新: 已升級至0.51.0, react 版本16.0.0)
  • Mac

二. 結(jié)合官網(wǎng)的教程文章對整個集成過程做一個大致的翻譯介紹,順便講一些遇到的坑:

  • 如果對 React Native 沒有了解阅虫,建議先把 Getting Started 看一遍演闭,對 RN 有個基本認(rèn)識,安裝好環(huán)境等等颓帝。

1. 前置條件:

(1). 設(shè)置目錄結(jié)構(gòu):

由于 RN 支持 Android 和 iOS 雙平臺米碰,所以,為了方便购城,最好在 android 項目的根目錄之上一層創(chuàng)建一個新文件夾(比如叫 “code”)吕座,再把原來的項目的根目錄改名為 android,再整個移入這個新文件夾 “code”瘪板。
(2017-12-30更新: 無需改名吴趴,詳見文章頂部更新說明)

官網(wǎng)之所以這么建議,是因為當(dāng)你從頭創(chuàng)建一個 RN 項目時侮攀,目錄結(jié)構(gòu)就是這樣的锣枝。

下面是我的項目集成 RN 前后的目錄結(jié)構(gòu)變化:

集成前:

before.png

集成后:

after.png
(2). 安裝 JavaScript 依賴:

在這個新文件夾 “code” 下創(chuàng)建 package.json 文件:

{
    "name": "MyReactNativeApp", // (2017-12-30 新增備注: 這個名字需要和后面提到的 ReactRootView.startReactApplication() 的第二個參數(shù)一致 )
    "version": "0.0.1",
    "private": true,
    "scripts": {
        "start": "node node_modules/react-native/local-cli/cli.js start"
    }
}

然后打開終端執(zhí)行一下命令安裝 react 和 react-native 的 package:

npm install --save react react-native

這個命令會在我們的 "code" 目錄下創(chuàng)建一個 /node_modules 文件夾厢拭,里面是所有需要的 JavaScript 依賴,可以打開查看一下撇叁,非常多供鸠。

2. 集成 React Native 的配置:

(1). 配置依賴:

在 app module 的 build.gradle 文件里 (在本文的例子里,即 code/android/app/build.gradle ) 加入 react-native 的依賴:

dependencies {
    ...
    compile "com.facebook.react:react-native:+" // From node_modules.
}

注:像別的依賴一樣陨闹,+號表示依賴最新版回季,也可以指定明確的版本號。

然后正林,在android根目錄的 build.gradle 文件里 (在本文的例子里,即 code/android/build.gradle ) 添加 React Native 的 Maven url 配置:

allprojects {
    repositories {
        ...
        maven {
            // 這里是指定所依賴的 React Native 是來自從 npm 安裝來的 /node_modules 目錄颤殴,
            // 因為 Maven 中央倉庫里的 React Native 可能不是最新的觅廓。
            url "$rootDir/node_modules/react-native/android"
        }
    }
    ...
}

注意: 這里可能有個坑,不能無腦跟隨官網(wǎng)教程涵但。由于 一個 RN 工程支持兩個平臺杈绸,而 $rootDir 指的只是 android 項目的根目錄而并非整個 RN 工程的根目錄(也就是 node_modules 所在的目錄),因為如前文所說矮瘟,官網(wǎng)教程建議把目錄結(jié)構(gòu)做一番調(diào)整瞳脓,android 項目目錄在整個RN項目根目錄的下一層(見上面兩張圖)。所以其實如果按照官網(wǎng)建議的調(diào)整完目錄結(jié)構(gòu)后澈侠,這里的 Maven url 應(yīng)該是:

url "$rootDir/../node_modules/react-native/android"

而不是

url "$rootDir/node_modules/react-native/android"

因為這個 maven url 配置錯誤劫侧,有可能遇到Crash:

Caused by: java.lang.IllegalAccessError: Method 'void android.support.v4.net.ConnectivityManagerCompat.<init>()' is inaccessible to class 'com.facebook.react.modules.netinfo.NetInfoModule' (declaration of 'com.facebook.react.modules.netinfo.NetInfoModule' appears in /data/app/[your-package-name]/base.apk:classes41.dex)

開始還懷疑是 Multidex 導(dǎo)致的問題,后來才發(fā)現(xiàn)是 Maven url 配置錯了導(dǎo)致需要依賴的 React Native 的版本不對所致哨啃∩斩埃可以在 Android Studio 里點開 External Libraries,查看 React Native 的版本是不是所需要依賴的版本拳球,如果不是审姓,多半是因為這個 maven url 配置的問題。


External Libraries.png
(2) 權(quán)限配置:

確保 app 的 AndroidManifest.xml 里申明了 Internet 權(quán)限:

<uses-permission android:name="android.permission.INTERNET" />

DevSettingsActivity是 React Native 用于開發(fā)調(diào)試的一個界面祝峻,發(fā)布 Release 版本的時候不需要魔吐,可以在 Release 版本去掉,但調(diào)試時一定需要的莱找,還可以用來從開發(fā)服務(wù)器 Reload JS 代碼酬姆,把它加進 AndroidManifest.xml 即可:

<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />

3. 代碼集成:

(1) JS 部分:

在工程根目錄(package.json 所在目錄)下創(chuàng)建 index.android.js 文件。這個文件就是 JavaScript 代碼所在宋距,或者說是 JavaScript 代碼的入口文件轴踱。(如果需要還可以在同目錄創(chuàng)建一個 index.ios.js 文件)
這里用官網(wǎng)的簡單 Hello World 示例:

'use strict';

import React from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  View
} from 'react-native';

class HelloWorld extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.hello}>Hello, World</Text>
      </View>
    )
  }
}
var styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
  },
  hello: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
});

// 注意:這個 "MyRnModule" 名字要和后面要講到的 Java 文件里的對應(yīng)。
AppRegistry.registerComponent('MyRnModule', () => HelloWorld);
(1) Java 部分:

在 Android 代碼目錄里創(chuàng)建一個新的 Activity 用于承載 React Native 的運行谚赎。網(wǎng)上很多教程說這個 Activity 需要繼承 ReactActivity淫僻,可能是在集成較舊版本的 RN 時需要這樣诱篷,現(xiàn)在已經(jīng)不需要,只需要直接繼承 Activity 或者 AppCompatActivity 即可雳灵,但是要實現(xiàn)一個 DefaultHardwareBackBtnHandler 接口棕所。

為了在開發(fā)過程中彈出出錯浮層,如果 targetSdkVersion 在23或以上悯辙,需要在進入這個 Activity 時判斷是否有相應(yīng)權(quán)限琳省,可以用 Settings.canDrawOverlays(context) 來判斷。

完整代碼:

package com.my-pkg-name.rn;

import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.support.v7.app.AppCompatActivity;
import android.view.KeyEvent;

import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactRootView;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.my-pkg-name.base.Config;
import com.my-pkg-name.ToastUtil;

public class MyReactActivity extends AppCompatActivity implements DefaultHardwareBackBtnHandler {

    private static final int OVERLAY_PERMISSION_REQ_CODE = 100;

    private ReactRootView mReactRootView;
    private ReactInstanceManager mReactInstanceManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mReactRootView = new ReactRootView(this);
        mReactInstanceManager = ReactInstanceManagerProvider.getReactInstanceManager();

        // 這里的 "MyRnModule" 名字要與前面 index.android.js 里 AppRegistry.registerComponent('MyRnModule', () => HelloWorld); 第一個參數(shù)一致躲撰。
        mReactRootView.startReactApplication(mReactInstanceManager, "MyRnModule", null);

        setContentView(mReactRootView);

        // 判斷權(quán)限用于顯示設(shè)置界面浮層
        if (Config.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (!Settings.canDrawOverlays(this)) {
                Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                        Uri.parse("package:" + getPackageName()));
                startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE);
            }
        }
    }

    @Override
    protected void onPause() {
        super.onPause();

        if (mReactInstanceManager != null) {
            mReactInstanceManager.onHostPause(this);
        }
    }

    @Override
    protected void onResume() {
        super.onResume();

        if (mReactInstanceManager != null) {
            mReactInstanceManager.onHostResume(this, this);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        if (mReactInstanceManager != null) {
            mReactInstanceManager.onHostDestroy();
        }
    }

    @Override
    public void onBackPressed() {
        if (mReactInstanceManager != null) {
            mReactInstanceManager.onBackPressed();
        } else {
            super.onBackPressed();
        }
    }

    @Override
    public void invokeDefaultOnBackPressed() {
        super.onBackPressed();
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == OVERLAY_PERMISSION_REQ_CODE) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                if (!Settings.canDrawOverlays(this)) {
                    // SYSTEM_ALERT_WINDOW permission not granted...
                }
            }
        }
    }

    // 在模擬器中調(diào)試時针贬,Ctrl + M 打開設(shè)置界面
    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        if (Config.DEBUG) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {
                ToastUtil.showLong(this, "未允許彈窗權(quán)限,無法打開設(shè)置彈窗拢蛋!");
            } else if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) {
                mReactInstanceManager.showDevOptionsDialog();
                return true;
            }
        }
        return super.onKeyUp(keyCode, event);
    }
}

在 Manifest 里注冊新 Activity桦他,注意要用 Theme.AppCompat.Light.NoActionBar 這個主題:

<activity
  android:name=".MyReactActivity"
  android:label="@string/app_name"
  android:theme="@style/Theme.AppCompat.Light.NoActionBar">
</activity>

ReactInstanceManagerProvider 是一個 提供 ReactInstanceManager 單例實例的工廠類。官網(wǎng)建議對 ReactInstanceManager 使用單例實例谆棱。

ReactInstanceManagerProvider.java:

package com.my-pkg-name.rn;

//import com.facebook.react.LifecycleState;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.shell.MainReactPackage;
import com.my-pkg-name.MyApplication;
import com.my-pkg-name.base.Config;

public class ReactInstanceManagerProvider {

    private ReactInstanceManager mReactInstanceManager;

    private ReactInstanceManagerProvider() {
        mReactInstanceManager = ReactInstanceManager.builder()
                .setApplication(MyApplication.instance)
                .setBundleAssetName("index.android.bundle")
                .setJSMainModuleName("index.android")
                .addPackage(new MainReactPackage())
                .setUseDeveloperSupport(Config.DEBUG)
                .setInitialLifecycleState(LifecycleState.RESUMED)
                .build();
    }

    private static ReactInstanceManagerProvider getInstance() {
        return Holder.sInstance;
    }

    private static class Holder {
        private static ReactInstanceManagerProvider sInstance = new ReactInstanceManagerProvider();
    }

    private ReactInstanceManager getReactInstanceManagerInstance() {
        return mReactInstanceManager;
    }

    public static ReactInstanceManager getReactInstanceManager() {
        return getInstance().getReactInstanceManagerInstance();
    }
}

最后快压,需要在合適的地方啟動這個新的 Activity:

startActivity(new Intent(getContext(), MyReactActivity.class));

至此,代碼部分已經(jīng)準(zhǔn)備妥當(dāng)了垃瞧,接下來要讓整個項目跑起來蔫劣。

4. Get it Running!

首先,要啟動開發(fā)服務(wù)器个从,只需在工程根目錄(package.json 所在目錄)運行命令:
npm start脉幢,然后正常在Android Studio 里面 點擊 Run App 即可。

  • 如果是真機調(diào)試信姓,要在連上手機后鸵隧,新啟一個命令行終端,執(zhí)行 adb reverse tcp:8081 tcp:8081

  • 真機調(diào)試首次加載可能會報錯:
    java.lang.RuntimeException: Unable to load script from assets 'index.android.bundle'. Make sure your bundle is packaged correctly or you're running a packager server.
    這是因為還沒有在手機上設(shè)置 server 和 port意推,搖一搖啟動設(shè)置頁面豆瘫,點擊 DevSettings -> Debug server host & port for device -> 輸入 [本機ip]:8081,本機 ip 可用 ifconfig 命令查看菊值。輸入完后返回外驱,再搖一搖然后點擊 Reload 即可。

  • 如果遇到這個錯:undefined is not an object (evaluating 'ReactInternals.ReactCurrentOwner')
    出現(xiàn)這個錯是因為 react 版本不對腻窒,react-native 0.45.1依賴 react 16.0.0-alpha昵宇,可到 /node_modules 目錄下查看 react 版本是否正確,如果不對儿子,執(zhí)行 npm install --save react@16.0.0-alpha.12 即可瓦哎。

  • 以上是針對開發(fā)調(diào)試,如果要發(fā)布 release 版,先執(zhí)行
    react-native bundle --platform android --dev false --entry-file index.android.js --bundle-output android/com/your-company-name/app-package-name/src/main/assets/index.android.bundle --assets-dest android/com/your-company-name/app-package-name/src/main/res/
    再照常
    ./gradlew assembleRelease
    即可蒋譬。

5. 關(guān)于 minSdkVersion

由于 React Native 只支持 API Level 16 及以上割岛, 所以如果你的固有項目是支持更低的 API Level 的話,就需要考慮一下犯助,是不是針對不同系統(tǒng)版本做不同的方案癣漆,比如只在 API 16 及以上的設(shè)備上用 RN 方案,較舊的機型仍然用原生開發(fā)(但是這樣做引入 RN 的意義就大打折扣了)剂买;API 16 以下即 Android 4.0.x 及以下惠爽,這樣的舊機型現(xiàn)在幾乎已經(jīng)沒有了,我們的數(shù)據(jù)庫中這部分用戶只有不到 100 個瞬哼,而且大概率隨著時間會慢慢地減少婚肆,因此可以考慮分系統(tǒng)版本打包,讓這部分舊機型用戶可以使用APP坐慰,但不能使用 RN 部分新功能了旬痹〗龃叮總的來說需要綜合舊機型用戶量灸拍、活躍度峡捡、產(chǎn)品業(yè)務(wù)需求等綜合考慮了。

6. 寫在最后

從我個人用 React Native 開發(fā) APP 的體驗來看把跨,React Native 適合 C/S 結(jié)構(gòu)、業(yè)務(wù)型的 APP 或其中的模塊沼死,對于偏重底層技術(shù)的比如工具類 APP (或者模塊)着逐,我還沒有使用 RN 嘗試過,不過想必顯然是不太適合的意蛀∷时穑總的來說,一個對于底層技術(shù)依賴不多县钥,業(yè)務(wù)型秀姐,尤其是業(yè)務(wù)變動頻繁的應(yīng)用或模塊適合 RN 開發(fā),而且一次開發(fā)若贮,基本可以完全重用于兩個平臺省有,重要的是可以熱更新來應(yīng)對業(yè)務(wù)邏輯更新頻繁、更新要求快谴麦、迅速修復(fù)線上 bug 等需求場景蠢沿,目前看,RN 的熱更新并沒有被 Apple 封殺匾效。

建議符合上述描述的應(yīng)用類型的嘗試 React Native舷蟀,畢竟,從官網(wǎng) Showcase 列出的名單來看,已經(jīng)有不少重量級選手入坑了野宜。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末扫步,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子速缨,更是在濱河造成了極大的恐慌锌妻,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件旬牲,死亡現(xiàn)場離奇詭異仿粹,居然都是意外死亡,警方通過查閱死者的電腦和手機原茅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門吭历,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人擂橘,你說我怎么就攤上這事晌区。” “怎么了通贞?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵朗若,是天一觀的道長。 經(jīng)常有香客問我昌罩,道長哭懈,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任茎用,我火速辦了婚禮遣总,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘轨功。我一直安慰自己旭斥,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布古涧。 她就那樣靜靜地躺著垂券,像睡著了一般。 火紅的嫁衣襯著肌膚如雪羡滑。 梳的紋絲不亂的頭發(fā)上圆米,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天,我揣著相機與錄音啄栓,去河邊找鬼娄帖。 笑死,一個胖子當(dāng)著我的面吹牛昙楚,可吹牛的內(nèi)容都是我干的近速。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼削葱!你這毒婦竟也來了奖亚?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤析砸,失蹤者是張志新(化名)和其女友劉穎昔字,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體首繁,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡作郭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了弦疮。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片夹攒。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖胁塞,靈堂內(nèi)的尸體忽然破棺而出咏尝,到底是詐尸還是另有隱情,我是刑警寧澤啸罢,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布编检,位于F島的核電站,受9級特大地震影響扰才,放射性物質(zhì)發(fā)生泄漏蒙谓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一训桶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧酣倾,春花似錦舵揭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至映之,卻和暖如春拦焚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背杠输。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工赎败, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蠢甲。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓僵刮,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子搞糕,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,925評論 2 344

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