3.3、Android Studio 添加 C 和 C++ 項目

文章摘要
1、 c、c++項目的創建
2、添加C、C++項目依賴庫
4、配置Gradle,編譯lib so庫


英文文獻

搭配使用 Android Studio 2.2 或更高版本Android Plugin for Gradle 版本 2.2.0 或更高版本時,您可以將 C 和 C++ 代碼編譯到 Gradle 與 APK 一起打包的原生庫中,將這類代碼添加到您的應用中。您的 Java 代碼隨后可以通過 Java 原生接口 (JNI) 調用您的原生庫中的函數。

Android Studio 用于構建原生庫的默認工具是 CMake。由于很多現有項目都使用構建工具包編譯其原生代碼,Android Studio 還支持 ndk-build

本頁面介紹的信息可以幫助您使用所需構建工具設置 Android Studio、創建或配置項目以支持 Android 上的原生代碼,以及構建和運行應用。

:如果您的現有項目使用已棄用的 ndkCompile 工具,則應先打開 build.properties 文件,并移除以下代碼行,然后再將 Gradle 關聯到您的原生庫

// Remove this line
android.useDeprecatedNdk = true

一、下載 NDK 和構建工具

要為您的應用編譯和調試原生代碼,您需要以下組件:

  • Android 原生開發工具包 (NDK):這套工具集允許您為 Android 使用 C 和 C++ 代碼,并提供眾多平臺庫,讓您可以管理原生 Activity 和訪問物理設備組件,例如傳感器和觸摸輸入。
  • CMake:一款外部構建工具,可與 Gradle 搭配使用來構建原生庫。如果您只計劃使用 ndk-build,則不需要此組件。
  • LLDB:一種調試程序,Android Studio 使用它來調試原生代碼

您可以使用 SDK 管理器安裝這些組件:

  • 1、在打開的項目中,從菜單欄選擇 Tools > Android > SDK Manager。

  • 2、點擊 SDK Tools 標簽。

  • 3、選中 LLDB、CMakeNDK 旁的復選框,如圖 1 所示。

  • 圖 1. 從 SDK 管理器中安裝 LLDB、CMake 和 NDK。
  • 4、點擊 Apply,然后在彈出式對話框中點擊 OK。

  • 5、安裝完成后,點擊 Finish,然后點擊 OK。

二、創建支持 C/C++ 的新項目

創建支持原生代碼的項目與創建任何其他 Android Studio 項目類似,不過前者還需要額外幾個步驟:

  • 1、在向導的 Configure your new project 部分,選中 Include C++ Support 復選框。

  • 2、點擊 Next。

  • 3、正常填寫所有其他字段并完成向導接下來的幾個部分。

  • 4、在向導的 Customize C++ Support 部分,您可以使用下列選項自定義項目:

    • C++ Standard:使用下拉列表選擇您希望使用哪種 C++ 標準。選擇 Toolchain Default 會使用默認的 CMake 設置。
    • Exceptions Support:如果您希望啟用對 C++ 異常處理的支持,請選中此復選框。如果啟用此復選框,Android Studio 會將 --fexceptions
      標志添加到模塊級 build.gradle 文件的 cppFlags中,Gradle 會將其傳遞到 CMake。
    • Runtime Type Information Support:如果您希望支持 RTTI,請選中此復選框。如果啟用此復選框,Android Studio 會將 -frtti標志添加到模塊級 build.gradle 文件的 cppFlags 中,Gradle 會將其傳遞到 CMake。
  • 5、點擊 Finish。

在 Android Studio 完成新項目的創建后,請從 IDE 左側打開 Project 窗格并選擇 Android 視圖。如圖 2 中所示,Android Studio 將添加 cppExternal Build Files 組:

  • 圖 2. 您的原生源文件和外部構建腳本的 Android 視圖組。

注:此視圖無法反映磁盤上的實際文件層次結構,而是將相似文件分到一組中,簡化項目導航。

三、構建和運行示例應用

點擊 Run后,Android Studio 將在您的 Android 設備或者模擬器上構建并啟動一個顯示文字“Hello from C++”的應用。下面的概覽介紹了構建和運行示例應用時會發生的事件:

  • 1、Gradle 調用您的外部構建腳本 CMakeLists.txt。
  • 2、CMake 按照構建腳本中的命令將 C++ 源文件 native-lib.cpp 編譯到共享的對象庫中,并命名為 libnative-lib.so,Gradle 隨后會將其打包到 APK 中。
  • 3、運行時,應用的 MainActivity 會使用 System.loadLibrary() 加載原生庫?,F在,應用可以使用庫的原生函數 stringFromJNI()。
  • 4、MainActivity.onCreate() 調用 stringFromJNI(),這將返回“Hello from C++”并使用這些文字更新 TextView

Instant Run 與使用原生代碼的項目不兼容。Android Studio 會自動停用此功能。

如果您想要驗證 Gradle 是否已將原生庫打包到 APK 中,可以使用 APK 分析器

  • 1、選擇 Build > Analyze APK。

  • 2、從 app/build/outputs/apk/ 目錄中選擇 APK 并點擊 OK。

  • 3、如圖 3 中所示,您會在 APK 分析器窗口的 lib/<ABI>/ 下看到 libnative-lib.so。

  • 圖 3. 使用 APK 分析器定位原生庫。

提示:如果您想要試驗使用原生代碼的其他 Android 應用,請點擊 File > New > Import Sample 并從 Ndk 列表中選擇示例項目。

四、向現有項目添加 C/C++ 代碼

如果您希望向現有項目添加原生代碼,請執行以下步驟:

  • 1、[創建新的原生源文件]并將其添加到您的 Android Studio 項目中。如果您已經擁有原生代碼或想要導入預構建的原生庫,則可以跳過此步驟。

  • 2、[創建 CMake 構建腳本],將您的原生源代碼構建到庫中。如果導入和關聯預構建庫或平臺庫,您也需要此構建腳本。如果您的現有原生庫已經擁有 CMakeLists.txt 構建腳本或者使用 ndk-build 并包含 Android.mk 構建腳本,則可以跳過此步驟。

  • 3、提供一個指向您的 CMake 或 ndk-build 腳本文件的路徑,將 Gradle 關聯到您的原生庫。Gradle 使用構建腳本將源代碼導入您的 Android Studio 項目并將原生庫(SO 文件)打包到 APK 中。

配置完項目后,您可以使用 JNI 框架從 Java 代碼中訪問您的原生函數。要構建和運行應用,只需點擊 Run 。Gradle 會以依賴項的形式添加您的外部原生構建流程,用于編譯、構建原生庫并將其隨 APK 一起打包。

4.1、創建新的原生源文件

要在應用模塊的主源代碼集中創建一個包含新建原生源文件的 cpp/ 目錄,請按以下步驟操作:

  • 1、從 IDE 的左側打開 Project 窗格并從下拉菜單中選擇 Project 視圖。

  • 2、導航到 您的模塊 > src,右鍵點擊 main 目錄,然后選擇 New > Directory

  • 3、為目錄輸入一個名稱(例如 cpp)并點擊 OK。

  • 4、右鍵點擊您剛剛創建的目錄,然后選擇 New > C/C++ Source File。

  • 5、為您的源文件輸入一個名稱,例如 native-lib。

  • 6、從 Type 下拉菜單中,為您的源文件選擇文件擴展名,例如 .cpp。

    • 點擊 Edit File Types,您可以向下拉菜單中添加其他文件類型,例如 .cxx 或 .hxx。在彈出的 C/C++ 對話框中,從 Source ExtensionHeader Extension 下拉菜單中選擇另一個文件擴展名,然后點擊 OK
  • 7、如果您還希望創建一個標頭文件,請選中 Create an associated header 復選框。

  • 8、點擊 OK。

4.2、創建 CMake 構建腳本

如果您的原生源文件還沒有 CMake 構建腳本,則您需要自行創建一個并包含適當的 CMake 命令。CMake 構建腳本是一個純文本文件,您必須將其命名為 CMakeLists.txt。本部分介紹了您應包含到構建腳本中的一些基本命令,用于在創建原生庫時指示 CMake 應使用哪些源文件。

:如果您的項目使用 ndk-build,則不需要創建 CMake 構建腳本。提供一個指向您的 Android.mk
文件的路徑,[將 Gradle 關聯到您的原生庫](后邊會介紹關聯操作)。

要創建一個可以用作 CMake 構建腳本的純文本文件,請按以下步驟操作:

  • 1、從 IDE 的左側打開 Project 窗格并從下拉菜單中選擇 Project 視圖。
  • 2、右鍵點擊 您的模塊 的根目錄并選擇 New > File。

注:您可以在所需的任意位置創建構建腳本。不過,在配置構建腳本時,原生源文件和庫的路徑將與構建腳本的位置相關。

  • 3、輸入“CMakeLists.txt”作為文件名并點擊 OK。

現在,您可以添加 CMake 命令,對您的構建腳本進行配置。要指示 CMake 從原生源代碼創建一個原生庫,請將 cmake_minimum_required()和 add_library()命令添加到您的構建腳本中:

# Sets the minimum version of CMake required to build your native library.
# This ensures that a certain set of CMake features is available to
# your build.

cmake_minimum_required(VERSION 3.4.1)

# Specifies a library name, specifies whether the library is STATIC or
# SHARED, and provides relative paths to the source code. You can
# define multiple libraries by adding multiple add.library() commands,
# and CMake builds them for you. When you build your app, Gradle
# automatically packages shared libraries with your APK.

add_library( # Specifies the name of the library.
             native-lib

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             src/main/cpp/native-lib.cpp )

使用 add_library() 向您的 CMake 構建腳本添加源文件或庫時,Android Studio 還會在您同步項目后在 Project 視圖下顯示關聯的標頭文件。不過,為了確保 CMake 可以在編譯時定位您的標頭文件,您需要將 include_directories() 命令添加到 CMake 構建腳本中并指定標頭的路徑:

add_library(...)

# Specifies a path to native header files.
include_directories(src/main/cpp/include/)

CMake 使用以下規范來為庫文件命名:lib庫名稱.so
例如,如果您在構建腳本中指定“native-lib”作為共享庫的名稱,CMake 將創建一個名稱為 libnative-lib.so 的文件。不過,在 Java 代碼中加載此庫時,請使用您在 CMake 構建腳本中指定的名稱:

static {
    System.loadLibrary(“native-lib”);
}

注:如果您在 CMake 構建腳本中重命名或移除某個庫,您需要先清理項目,Gradle 隨后才會應用更改或者從 APK 中移除舊版本的庫。要清理項目,請從菜單欄中選擇 Build > Clean Project。

Android Studio 會自動將源文件和標頭添加到 Project 窗格的 cpp 組中。使用多個 add_library() 命令,您可以為 CMake 定義要從其他源文件構建的更多庫。

4.3、添加 NDK API

Android NDK 提供了一套實用的原生 API 和庫。通過將 NDK 庫包含到項目的 CMakeLists.txt 腳本文件中,您可以使用這些 API 中的任意一種。

預構建的 NDK 庫已經存在于 Android 平臺上,因此,您無需再構建或將其打包到 APK 中。由于 NDK 庫已經是 CMake 搜索路徑的一部分,您甚至不需要在您的本地 NDK 安裝中指定庫的位置 - 只需要向 CMake 提供您希望使用的庫的名稱,并將其關聯到您自己的原生庫。

將 find_library()命令添加到您的 CMake 構建腳本中以定位 NDK 庫,并將其路徑存儲為一個變量。您可以使用此變量在構建腳本的其他部分引用 NDK 庫。以下示例可以定位 Android 特定的日志支持庫并將其路徑存儲在 log-lib 中:

find_library( # Defines the name of the path variable that stores the
              # location of the NDK library.
              log-lib

              # Specifies the name of the NDK library that
              # CMake needs to locate.
              log )

為了確保您的原生庫可以在 log 庫中調用函數,您需要使用 CMake 構建腳本中的 target_link_libraries() 命令關聯庫:

find_library(...)

# Links your native library against one or more other native libraries.
target_link_libraries( # Specifies the target library.
                       native-lib

                       # Links the log library to the target library.
                       ${log-lib} )

NDK 還以源代碼的形式包含一些庫,您在構建和關聯到您的原生庫時需要使用這些代碼。您可以使用 CMake 構建腳本中的 add_library() 命令,將源代碼編譯到原生庫中。要提供本地 NDK 庫的路徑,您可以使用 ANDROID_NDK 路徑變量,Android Studio 會自動為您定義此變量。

以下命令可以指示 CMake 構建 android_native_app_glue.c,后者會將 NativeActivity 生命周期事件和觸摸輸入置于靜態庫中并將靜態庫關聯到 native-lib:

add_library( app-glue
             STATIC
             ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c )

# You need to link static libraries against your shared native library.
target_link_libraries( native-lib app-glue ${log-lib} )
4.4、添加其他預構建庫

添加預構建庫與為 CMake 指定要構建的另一個原生庫類似。不過,由于庫已經預先構建,您需要使用 IMPORTED標志告知 CMake 您只希望將庫導入到項目中:

add_library( imported-lib
             SHARED
             IMPORTED )

然后,您需要使用 set_target_properties() 命令指定庫的路徑,如下所示。

某些庫為特定的 CPU 架構(或應用二進制接口 (ABI))提供了單獨的軟件包,并將其組織到單獨的目錄中。此方法既有助于庫充分利用特定的 CPU 架構,又能讓您僅使用所需的庫版本。要向 CMake 構建腳本中添加庫的多個 ABI 版本,而不必為庫的每個版本編寫多個命令,您可以使用 ANDROID_ABI
路徑變量。此變量使用 NDK 支持的一組默認 ABI,或者您手動配置 Gradle 而讓其使用的一組經過篩選的 ABI。例如:

add_library(...)
set_target_properties( # Specifies the target library.
          imported-lib

          # Specifies the parameter you want to define.
          PROPERTIES IMPORTED_LOCATION

          # Provides the path to the library you want to import.
          imported-lib/src/${ANDROID_ABI}/libimported-lib.so )

為了確保 CMake 可以在編譯時定位您的標頭文件,您需要使用 include_directories() 命令,并包含標頭文件的路徑:

include_directories( imported-lib/include/ )

注:如果您希望打包一個并不是構建時依賴項的預構建庫(例如在添加屬于 imported-lib 依賴項的預構建庫時),則不需要執行以下說明來關聯庫。

要將預構建庫關聯到您自己的原生庫,請將其添加到 CMake 構建腳本的 target_link_libraries() 命令中:

target_link_libraries( native-lib imported-lib app-glue ${log-lib} )

五、將 Gradle 關聯到您的原生庫

要將 Gradle 關聯到您的原生庫,您需要提供一個指向 CMake 或 ndk-build 腳本文件的路徑。在您構建應用時,Gradle 會以依賴項的形式運行 CMake 或 ndk-build,并將共享的庫打包到您的 APK 中。Gradle 還使用構建腳本來了解要將哪些文件添加到您的 Android Studio 項目中,以便您可以從 Project 窗口訪問這些文件。如果您的原生源文件沒有構建腳本,則需要先創建 CMake 構建腳本,然后再繼續。

將 Gradle 關聯到原生項目后,Android Studio 會更新 Project 窗格以在 cpp 組中顯示您的源文件和原生庫,在 External Build Files 組中顯示您的外部構建腳本。

:更改 Gradle 配置時,請確保通過點擊工具欄中的 Sync Project應用更改。此外,如果在將 CMake 或 ndk-build 腳本文件關聯到 Gradle 后再對其進行更改,您應當從菜單欄中選擇 Build > Refresh Linked C++ Projects,將 Android Studio 與您的更改同步。

5.1、使用 Android Studio UI

您可以使用 Android Studio UI 將 Gradle 關聯到外部 CMake 或 ndk-build 項目:

  • 1、從 IDE 左側打開 Project 窗格并選擇 Android 視圖。

  • 2、右鍵點擊您想要關聯到原生庫的模塊(例如 app 模塊),并從菜單中選擇 Link C++ Project with Gradle。您應看到一個如圖 4 所示的對話框。

  • 3、從下拉菜單中,選擇 CMakendk-build

    • 1、如果您選擇 CMake,請使用 Project Path 旁的字段為您的外部 CMake 項目指定 CMakeLists.txt 腳本文件。
    • 2、如果您選擇 ndk-build,請使用 Project Path 旁的字段為您的外部 ndk-build 項目指定 Android.mk 腳本文件。如果 Application.mk 文件與您的 Android.mk 文件位于相同目錄下,Android Studio 也會包含此文件。
  • 圖 4. 使用 Android Studio 對話框關聯外部 C++ 項目。
  • 4、點擊 OK。

5.2、手動配置 Gradle

要手動配置 Gradle 以關聯到您的原生庫,您需要將 externalNativeBuild {} 塊添加到模塊級 build.gradle 文件中,并使用 cmake {} 或 ndkBuild {} 對其進行配置:

android {
  ...
  defaultConfig {...}
  buildTypes {...}

  // Encapsulates your external native build configurations.
  externalNativeBuild {

    // Encapsulates your CMake build configurations.
    cmake {

      // Provides a relative path to your CMake build script.
      path "CMakeLists.txt"
    }
  }
}

:如果您想要將 Gradle 關聯到現有 ndk-build 項目,請使用 ndkBuild {} 塊而不是 cmake {},并提供 Android.mk 文件的相對路徑。如果 Application.mk 文件與您的 Android.mk 文件位于相同目錄下,Gradle 也會包含此文件。

5.2.1、指定可選配置

您可以在模塊級 build.gradle 文件的 defaultConfig {} 塊中配置另一個 externalNativeBuild {} 塊,為 CMake 或 ndk-build 指定可選參數和標志。與 defaultConfig {} 塊中的其他屬性類似,您也可以在構建配置中為每個產品風味重寫這些屬性。

例如,如果您的 CMake 或 ndk-build 項目定義多個原生庫,您可以使用 targets 屬性僅為給定產品風味構建和打包這些庫中的一部分。以下代碼示例說明了您可以配置的部分屬性:

android {
  ...
  defaultConfig {
    ...
    // This block is different from the one you use to link Gradle
    // to your CMake or ndk-build script.
    externalNativeBuild {

      // For ndk-build, instead use ndkBuild {}
      cmake {

        // Passes optional arguments to CMake.
        arguments "-DANDROID_ARM_NEON=TRUE", "-DANDROID_TOOLCHAIN=clang"

        // Sets optional flags for the C compiler.
        cFlags "-D_EXAMPLE_C_FLAG1", "-D_EXAMPLE_C_FLAG2"

        // Sets a flag to enable format macro constants for the C++ compiler.
        cppFlags "-D__STDC_FORMAT_MACROS"
      }
    }
  }

  buildTypes {...}

  productFlavors {
    ...
    demo {
      ...
      externalNativeBuild {
        cmake {
          ...
          // Specifies which native libraries to build and package for this
          // product flavor. If you don't configure this property, Gradle
          // builds and packages all shared object libraries that you define
          // in your CMake or ndk-build project.
          targets "native-lib-demo"
        }
      }
    }

    paid {
      ...
      externalNativeBuild {
        cmake {
          ...
          targets "native-lib-paid"
        }
      }
    }
  }

  // Use this block to link Gradle to your CMake or ndk-build script.
  externalNativeBuild {
    cmake {...}
    // or ndkBuild {...}
  }
}
5.2.2、指定 ABI

默認情況下,Gradle 會針對 NDK 支持的 ABI 將您的原生庫構建到單獨的 .so
文件中,并將其全部打包到您的 APK 中。如果您希望 Gradle 僅構建和打包原生庫的特定 ABI 配置,您可以在模塊級 build.gradle
文件中使用 ndk.abiFilters 標志指定這些配置,如下所示:

android {
  ...
  defaultConfig {
    ...
    externalNativeBuild {
      cmake {...}
      // or ndkBuild {...}
    }

    ndk {
      // Specifies the ABI configurations of your native
      // libraries Gradle should build and package with your APK.
      abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a',
                   'arm64-v8a'
    }
  }
  buildTypes {...}
  externalNativeBuild {...}
}

在大多數情況下,您只需要在 ndk {} 塊中指定 abiFilters(如上所示),因為它會指示 Gradle 構建和打包原生庫的這些版本。不過,如果您希望控制 Gradle 應當構建的配置,并獨立于您希望其打包到 APK 中的配置,請在 defaultConfig.externalNativeBuild.cmake {} 塊(或 defaultConfig.externalNativeBuild.ndkBuild {} 塊中)配置另一個 abiFilters 標志。Gradle 會構建這些 ABI 配置,不過僅會打包您在 defaultConfig.ndk{} 塊中指定的配置。

為了進一步降低 APK 的大小,請考慮配置 ABI APK 拆分,而不是創建一個包含原生庫所有版本的大型 APK,Gradle 會為您想要支持的每個 ABI 創建單獨的 APK,并且僅打包每個 ABI 需要的文件。如果您配置 ABI 拆分,但沒有像上面的代碼示例一樣指定 abiFilters 標志,Gradle 會構建原生庫的所有受支持 ABI 版本,不過僅會打包您在 ABI 拆分配置中指定的版本。為了避免構建您不想要的原生庫版本,請為 abiFilters 標志和 ABI 拆分配置提供相同的 ABI 列表。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,818評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,185評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,656評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,647評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,446評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,951評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,041評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,189評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,718評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,800評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,419評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,420評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,755評論 2 371

推薦閱讀更多精彩內容