cmake式jni开发

向您的项目添加 C 和 C++ 代码

本文内容

下载 NDK 和构建工具

创建支持 C/C++ 的新项目

构建和运行示例应用

向现有项目添加 C/C++ 代码

创建新的原生源文件

创建 CMake 构建脚本

将 Gradle 关联到您的原生库

搭配使用Android Studio 2.2 或更高版本Android Plugin for Gradle 版本 2.2.0 或更高版本时,您可以将 C 和 C++ 代码编译到 Gradle 与 APK 一起封装的原生库中,将这类代码添加到您的应用中。您的 Java 代码随后可以通过 Java 原生接口 (JNI) 调用您的原生库中的函数。如果您想要详细了解如何使用 JNI 框架,请阅读Android 的 JNI 提示

Android Studio 用于构建原生库的默认工具是 CMake。由于很多现有项目都使用构建工具包编译其原生代码,Android Studio 还支持ndk-build。如果您想要将现有的 ndk-build 库导入到您的 Android Studio 项目中,请参阅介绍如何配置 Gradle 以关联到您的原生库的部分。不过,如果您在创建新的原生库,则应使用 CMake。

本页面介绍的信息可以帮助您使用所需构建工具设置 Android Studio、创建或配置项目以支持 Android 上的原生代码,以及构建和运行应用。

注:如果您的现有项目使用已弃用的ndkCompile工具,则应先打开build.properties文件,并移除以下代码行,然后再将 Gradle 关联到您的原生库

// Remove this line

android.useDeprecatedNdk = true

实验性 Gradle 的用户注意事项:如果您是以下任意一种情况,请考虑迁移到插件版本 2.2.0 或更高版本并使用 CMake 或 ndk-build 构建原生库:您的原生项目已经使用 CMake 或者 ndk-build;但是您想要使用稳定版本的 Gradle 构建系统;或者您希望支持插件工具,例如CCache。否则,您可以继续使用实验性版本的 Gradle 和 Android 插件

下载 NDK 和构建工具

要为您的应用编译和调试原生代码,您需要以下组件:

Android 原生开发工具包 (NDK):这套工具集允许您为 Android 使用 C 和 C++ 代码,并提供众多平台库,让您可以管理原生 Activity 和访问物理设备组件,例如传感器和触摸输入。

CMake:一款外部构建工具,可与 Gradle 搭配使用来构建原生库。如果您只计划使用 ndk-build,则不需要此组件。

LLDB:一种调试程序,Android Studio 使用它来调试原生代码

您可以使用 SDK 管理器安装这些组件:

在打开的项目中,从菜单栏选择Tools > Android > SDK Manager。

点击SDK Tools标签。

选中LLDB、CMake和NDK旁的复选框,如图 1 所示。


图 1.从 SDK 管理器中安装 LLDB、CMake 和 NDK。

点击Apply,然后在弹出式对话框中点击OK。

安装完成后,点击Finish,然后点击OK。

创建支持 C/C++ 的新项目

创建支持原生代码的项目与创建任何其他 Android Studio 项目类似,不过前者还需要额外几个步骤:

在向导的Configure your new project部分,选中Include C++ Support复选框。

点击Next。

正常填写所有其他字段并完成向导接下来的几个部分。

在向导的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。

点击Finish。

在 Android Studio 完成新项目的创建后,请从 IDE 左侧打开Project窗格并选择Android视图。如图 2 中所示,Android Studio 将添加cpp和External Build Files组:


图 2.您的原生源文件和外部构建脚本的 Android 视图组。

注:此视图无法反映磁盘上的实际文件层次结构,而是将相似文件分到一组中,简化项目导航。

在cpp组中,您可以找到属于项目的所有原生源文件、标头和预构建库。对于新项目,Android Studio 会创建一个示例 C++ 源文件native-lib.cpp,并将其置于应用模块的src/main/cpp/目录中。本示例代码提供了一个简单的 C++ 函数stringFromJNI(),此函数可以返回字符串“Hello from C++”。要了解如何向项目添加其他源文件,请参阅介绍如何创建新的原生源文件的部分。

在External Build Files组中,您可以找到 CMake 或 ndk-build 的构建脚本。与build.gradle文件指示 Gradle 如何构建应用一样,CMake 和 ndk-build 需要一个构建脚本来了解如何构建您的原生库。对于新项目,Android Studio 会创建一个 CMake 构建脚本CMakeLists.txt,并将其置于模块的根目录中。要详细了解此构建脚本的内容,请参阅介绍如何创建 Cmake 构建脚本的部分。

构建和运行示例应用

点击Run

后,Android Studio 将在您的 Android 设备或者模拟器上构建并启动一个显示文字“Hello from C++”的应用。下面的概览介绍了构建和运行示例应用时会发生的事件:

Gradle 调用您的外部构建脚本CMakeLists.txt。

CMake 按照构建脚本中的命令将 C++ 源文件native-lib.cpp编译到共享的对象库中,并命名为libnative-lib.so,Gradle 随后会将其封装到 APK 中。

运行时,应用的MainActivity会使用System.loadLibrary()加载原生库。现在,应用可以使用库的原生函数stringFromJNI()。

MainActivity.onCreate()调用stringFromJNI(),这将返回“Hello from C++”并使用这些文字更新TextView

注:Instant Run与使用原生代码的项目不兼容。Android Studio 会自动停用此功能。

如果您想要验证 Gradle 是否已将原生库封装到 APK 中,可以使用APK 分析器

选择Build > Analyze APK。

从app/build/outputs/apk/目录中选择 APK 并点击OK。

如图 3 中所示,您会在 APK 分析器窗口的lib//下看到libnative-lib.so。


图 3.使用 APK 分析器定位原生库。

提示:如果您想要试验使用原生代码的其他 Android 应用,请点击File > New > Import Sample并从Ndk列表中选择示例项目。

向现有项目添加 C/C++ 代码

如果您希望向现有项目添加原生代码,请执行以下步骤:

创建新的原生源文件并将其添加到您的 Android Studio 项目中。

如果您已经拥有原生代码或想要导入预构建的原生库,则可以跳过此步骤。

创建 CMake 构建脚本,将您的原生源代码构建到库中。如果导入和关联预构建库或平台库,您也需要此构建脚本。

如果您的现有原生库已经拥有CMakeLists.txt构建脚本或者使用 ndk-build 并包含Android.mk构建脚本,则可以跳过此步骤。

提供一个指向您的 CMake 或 ndk-build 脚本文件的路径,将 Gradle 关联到您的原生库。Gradle 使用构建脚本将源代码导入您的 Android Studio 项目并将原生库(SO 文件)封装到 APK 中。

配置完项目后,您可以使用JNI 框架从 Java 代码中访问您的原生函数。要构建和运行应用,只需点击Run

。Gradle 会以依赖项的形式添加您的外部原生构建流程,用于编译、构建原生库并将其随 APK 一起封装。

创建新的原生源文件

要在应用模块的主源代码集中创建一个包含新建原生源文件的cpp/目录,请按以下步骤操作:

从 IDE 的左侧打开Project窗格并从下拉菜单中选择Project视图。

导航到您的模块> src,右键点击main目录,然后选择New > Directory。

为目录输入一个名称(例如cpp)并点击OK。

右键点击您刚刚创建的目录,然后选择New > C/C++ Source File。

为您的源文件输入一个名称,例如native-lib。

从Type下拉菜单中,为您的源文件选择文件扩展名,例如.cpp。

点击Edit File Types

,您可以向下拉菜单中添加其他文件类型,例如.cxx或.hxx。在弹出的C/C++对话框中,从Source Extension和Header Extension下拉菜单中选择另一个文件扩展名,然后点击OK。

如果您还希望创建一个标头文件,请选中Create an associated header复选框。

点击OK。

创建 CMake 构建脚本

如果您的原生源文件还没有 CMake 构建脚本,则您需要自行创建一个并包含适当的 CMake 命令。CMake 构建脚本是一个纯文本文件,您必须将其命名为CMakeLists.txt。本部分介绍了您应包含到构建脚本中的一些基本命令,用于在创建原生库时指示 CMake 应使用哪些源文件。

注:如果您的项目使用 ndk-build,则不需要创建 CMake 构建脚本。提供一个指向您的Android.mk文件的路径,将 Gradle 关联到您的原生库

要创建一个可以用作 CMake 构建脚本的纯文本文件,请按以下步骤操作:

从 IDE 的左侧打开Project窗格并从下拉菜单中选择Project视图。

右键点击您的模块的根目录并选择New > File。

注:您可以在所需的任意位置创建构建脚本。不过,在配置构建脚本时,原生源文件和库的路径将与构建脚本的位置相关。

输入“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 定义要从其他源文件构建的更多库。

添加 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} )

添加其他预构建库

添加预构建库与为 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 会自动将导入的库封装到 APK 中。您可以使用APK 分析器验证 Gradle 将哪些库封装到您的 APK 中。如需了解有关 CMake 命令的详细信息,请参阅CMake 文档

将 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 与您的更改同步。

使用 Android Studio UI

您可以使用 Android Studio UI 将 Gradle 关联到外部 CMake 或 ndk-build 项目:

从 IDE 左侧打开Project窗格并选择Android视图。

右键点击您想要关联到原生库的模块(例如app模块),并从菜单中选择Link C++ Project with Gradle。您应看到一个如图 4 所示的对话框。

从下拉菜单中,选择CMake或ndk-build。

如果您选择CMake,请使用Project Path旁的字段为您的外部 CMake 项目指定CMakeLists.txt脚本文件。

如果您选择ndk-build,请使用Project Path旁的字段为您的外部 ndk-build 项目指定Android.mk脚本文件。如果Application.mk文件与您的Android.mk文件位于相同目录下,Android Studio 也会包含此文件。


图 4.使用 Android Studio 对话框关联外部 C++ 项目。

点击OK。

手动配置 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 也会包含此文件。

指定可选配置

您可以在模块级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 {...}

    }

}

要详细了解配置产品风味和构建变体,请参阅配置构建变体。如需了解您可以使用arguments属性为 CMake 配置的变量列表,请参阅使用 CMake 变量

指定 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阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容