Android Studio 2.3.3 圖解配置NDK開發環境以及Hello Word To jni~

LZ-Says:半夜睡覺滾了地上了,無奈之下醒來了,想想最近幾天因為一個括號導致JNI遲遲不能開展,心里面無奈又崩潰,索性直接起來整完得了~

前言

當前畢業的時候,感覺自己掌握了全世界,隨著參加工作的時間一天天的增長,突然覺得丫的,啥都不會啊,要學的東西還是太多太多了。不過近來被飛大姐洗腦成功,<font color=#FF0000>萬事不過幾行代碼而已,干它~

So 今天為大家帶來簡單的jni配置,使用,以及運行我們的第一個簡單小demo`

Hi Jni

總是再說jni,jni,那么jni到底是什么東西,我們一起來看看:

JNI是Java Native Interface的縮寫,它提供了若干的API實現了Java和其他語言的通信(主要是C&C++)。從Java1.1開始,JNI標準成為java平臺的一部分,它允許Java代碼和其他語言寫的代碼進行交互。

JNI標準至少要保證本地代碼能工作在任何Java 虛擬機環境。

下面為大家附上官方Android平臺架構圖:

<center>
這里寫圖片描述
這里寫圖片描述

可以看到Android上層的Application和ApplicationFramework都是使用Java編寫,底層包括系統和使用眾多的Libraries都是C/C++編寫的,所以上層Java要調用底層的C/C++函數庫必須通過Java的JNI來實現。

Jni使用場景

當你開始著手準備一個使用JNI的項目時,請確認是否還有替代方案。應用程序使用JNI會帶來一些副作用。下面給出幾個方案,可以避免使用JNI的時候,達到與本地代碼進行交互的效果:

1、JAVA程序和本地程序使用TCP/IP或者IPC進行交互。
2、當用JAVA程序連接本地數據庫時,使用JDBC提供的API。
3、JAVA程序可以使用分布式對象技術,如JAVA IDL API。

這些方案的共同點是,JAVA和C處于不同的線程,或者不同的機器上。這樣,當本地程序崩潰時,不會影響到JAVA程序。

下面這些場合中,同一進程內JNI的使用無法避免:

1、程序當中用到了JAVA API不提供的特殊系統環境才會有的特征。而跨進程操作又不現實。
2、你可能想訪問一些己有的本地庫,但又不想付出跨進程調用時的代價,如效率,內存,數據傳遞方面。
3、JAVA程序當中的一部分代碼對效率要求非常高,如算法計算,圖形渲染等。

<font color=#FF0000>總之,只有當你必須在同一進程中調用本地代碼時,再使用JNI。

Jni缺陷

一旦使用JNI,JAVA程序就喪失了JAVA平臺的兩個優點:

1、程序不再跨平臺。要想跨平臺,必須在不同的系統環境下重新編譯本地語言部分。
2、程序不再是絕對安全的,本地代碼的不當使用可能導致整個程序崩潰。一個通用規則是,你應該讓本地方法集中在少數幾個類當中。這樣就降低了JAVA和C之間的耦合性

Jni作用

JNI可以這樣與本地程序進行交互:

1、你可以使用JNI來實現“本地方法”(native methods),并在JAVA程序中調用它們。

2、JNI支持一個“調用接口”(invocation interface),它允許你把一個JVM嵌入到本地程序中。本地程序可以鏈接一個實現了JVM的本地庫,然后使用“調用接口”執行JAVA語言編寫的軟件模塊。

簡單了解以上內容后,我們開啟正題,在Android開發中,我們該怎么使用jni,或者說是在Android Studio中,我們該怎么使用jni呢?表急,往下瞅瞅~

話說,我們開發android應用程序基礎不就是下載官方相關的SDK,ADT啥啥啥的,同理jni也一樣。

現在為大家簡單介紹NDK~如下。

Hi NDK

Android NDK 是一套允許使用原生代碼語言(例如 C 和 C++)實現部分應用的工具集。在開發某些類型應用時,這有助于重復使用以這些語言編寫的代碼庫。

同理,官方也為我們提供了一個小例子,簡單走馬觀花看一下:

public class MyActivity extends Activity {
  /**
  * 使用 C/C++ 語言實現的原生方法
  */
  public native void computeFoo();
}

native方法,當年看到這個東西感覺好高大上,沒想到而今我也能玩玩,哈哈,GGG~

官方對于NDK是這樣說的:

NDK 不適用于大多數初學的 Android 編程者,對許多類型的 Android 應用沒什么價值。 因為它不可避免地會增加開發過程的復雜性,所以通常不值得使用。 但如果您需要執行以下操作,它可能很有用:

  • 從設備獲取卓越性能以用于計算密集型應用,例如游戲或物理模擬;

  • 重復使用您自己或其他開發者的 C 或 C++ 庫。

丫的,爺兒們好奇瞅瞅不行啊?

行~!

哈哈~

簡單有個印象后,我們開始配置相關內容,為什么要說這個配置呢,主要有以下幾個原因:

  • 雖說配置是傻瓜式無腦操作,但是對于LZ小白這樣的人來說,依然覺得是一件很高大上的事兒,何況,丫的,連配置都不會,還開發個卵子?

  • 凡事兒親歷親為,不經歷,怎能有成長?

配置之前,我們還需要了解我們需要配置or下載哪兒些工具,以及這些東西都是干嘛的,不然稀里糊涂的,糟心。

同理,我們也需要去簡單了解下使用NDK好處:

1、代碼的保護。由于apk的java層代碼很容易被反編譯,而C/C++庫反匯難度較大;
2、可以方便地使用現存的開源庫。大部分現存的開源庫都是用C/C++代碼編寫的;
3、提高程序的執行效率。將要求高性能的應用邏輯使用C開發,從而提高應用程序的執行效率;
4、便于移植。用C/C++寫得庫可以方便在其他的嵌入式平臺上再次使用。

NDK以及所需構建工具簡介

  • NDK:這個還需要再說嘛?

  • CMake:外部構建工具;

CMake是一個開源的跨平臺系列工具,旨在構建,測試和打包軟件。

CMake用于使用簡單的平臺和編譯器獨立的配置文件來控制軟件編譯過程,并生成可以在選擇的編譯環境中使用的本地makefile和工作空間。

CMack工具套件由Kitware創建,以響應對開源項目(如ITK和VTK)的強大的跨平臺構建環境的需求。

官方地址:https://cmake.org/ 有興趣可以簡單了解下~

  • LLDB:Android Studio 上面調試本地代碼的工具

LLDB是下一代高性能調試器。它被構建為一組可重用的組件,可以高度利用較大的LLVM項目中的現有庫,例如Clang表達式解析器和LLVM反匯編程序。

在Mac OS X中,LLDB是Xcode中的默認調試器,支持在桌面和iOS設備和模擬器上調試C,Objective-C和C ++。

LLDB項目中的所有代碼都可以使用標準的 LLVM許可證(一種開放源代碼“BSD風格”)許可證。

LLDB目前將調試信息轉換成clang類型,以便它可以利用clang編譯器基礎架構。這允許LLDB在表達式中支持最新的C,C++,Objective C和Objective C ++語言特性和運行時間,而無需重新實現任何此功能。在編寫表達式的函數,拆卸指令和提取指令詳細信息等時,還可以利用編譯器來處理所有ABI細節。

主要優點包括:

  1. C,C ++,Objective C的最新語言支持 ;
  2. 可以聲明局部變量和類型的多行表達式;
  3. 支持時使用JIT表達式;
  4. 當JIT不能使用時,評估表達式中間表示(IR)

官方地址:http://lldb.llvm.org/

以上簡單了解下就好了,至于為啥要這么搞,就是為了方便以后有需要直接翻出來看看~

NDK配置(包含構建工具,調試工具)

<font color=#FF0000>下載安裝NDK,有倆種方式,其實都一樣,只是一個需要手動下載,解壓,配置目錄,一個Android Studio自動完成以上操作。

配置NDK倆種方式

手動下載NDK

NDK下載地址如下:

https://developer.android.google.cn/ndk/downloads/index.html

大家可自行選擇相應版本進行下載。

下載完成后,解壓本地目錄,在Android Studio中配置路徑即可,如下圖所示:

<center>
這里寫圖片描述
這里寫圖片描述

強大的Android Studio走起~

1.點擊Project Structure,選擇 Download Android NDK;

<center>
這里寫圖片描述
這里寫圖片描述

2.耐心等待吧,LZ下載比較快,解壓比較慢~

<center>
這里寫圖片描述
這里寫圖片描述

3.下載解壓完成,自動錄入地址,省事兒哈~

<center>
這里寫圖片描述
這里寫圖片描述

到此,關于NDK下載安裝倆種方式圖解完畢~

我們看一下ndk目錄各個作用,簡單了解下。

  • docs: 幫助文檔
  •       build/tools:linux的批處理文件
    
  •       platforms:編譯c代碼需要使用的頭文件和類庫
    
  •       prebuilt:預編譯使用的二進制可執行文件
    
  •      sample:jni的使用例子
    
  •      source:ndk的源碼
    
  •      toolchains:工具鏈
    
  •     ndk-build.cmd:編譯打包c代碼的一個指令,需要配置系統環境變量
    

配置構建工具以及調試工具

1.如下圖所示,點擊SDK Manager,選擇下載安裝CMake以及LLDB;

<center>
這里寫圖片描述
這里寫圖片描述

簡單看一下版本信息,果斷OK~

<center>
這里寫圖片描述
這里寫圖片描述

沒啥可說的,等待,不過分分鐘搞定~

<center>
這里寫圖片描述
這里寫圖片描述

到此,基本配置已完成,但是如何查驗NDK是否成功安裝了呢?

配置下環境變量瞅瞅唄~

配置環境變量

復制NDK地址,按如下圖示進行操作即可。

<center>
這里寫圖片描述
這里寫圖片描述

<center>
這里寫圖片描述
這里寫圖片描述

cmd直接輸入ndk-build回車

<center>
這里寫圖片描述
這里寫圖片描述

在這里吐槽下LZ之前遇到的坑。

LZ目錄習慣命名為HLQWorkSofe(Android) or HLQWorkSofe(Java),在之前從來沒有出現過問題,但是在NDK時候,卻怎么也不行,最后無奈下只能把()去掉。

問題截圖如下:

<center>
這里寫圖片描述
這里寫圖片描述

<center>
這里寫圖片描述
這里寫圖片描述

<font color=#FF0000>搞得LZ都快放棄了,最后一試,好了,給我郁悶的,大家注意啊~

創建個項目玩一玩

創建項目時,記得勾選下面的Include C++ support哦~

導入C++支持庫,不然沒法玩哈~
<center>

這里寫圖片描述
這里寫圖片描述

一路next下之后,關于最后一步選擇時,在此作特殊說明下。

<center>
這里寫圖片描述
這里寫圖片描述
  • 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之后,我們稍等片刻~

查看默認生成例子

<center>
這里寫圖片描述
這里寫圖片描述

下面我們一起來探究下官方提供的例子,看看從例子中我們能得知什么對我們有用的信息。

從上圖可看到,和以前唯一不同的就是多出了cpp目錄以及External Build Files倆個目錄,那么這倆個都有什么用呢?一起來看看。

  • cpp 目錄存放所有 native code 的地方,包括源碼,頭文件,預編譯項目等。對于新項目,Android Studio 創建了一個 C++ 模板文件:native-lib.cpp,并且將該文件放到了你的 app 模塊的 src/main/cpp/ 目錄下。這份模板代碼提供了一個簡答的 C++ 函數:stringFromJNI(),該函數返回一個字符串:”Hello from C++”

  • External Build Files 目錄是存放 CMake 或 ndk-build 構建腳本的地方。有點類似于 build.gradle 文件告訴 Gradle 如何編譯你的 APP 一樣,CMake 和 ndk-build 也需要一個腳本來告知如何編譯你的 native library。對于一個新的項目,Android Studio 創建了一個 CMake 腳本:CMakeLists.txt,并且將其放到了你的 module 的根目錄下

首先還是來看看代碼吧,比較直觀也比較好理解。

1.查看activity代碼

package cn.hlq.hlqjnipro;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}

首先static靜態塊去加載so庫,本地編寫native方法,調用方法輸出內容。

2.查看native-lib.cpp代碼

#include <jni.h>
#include <string>

extern "C"
JNIEXPORT jstring JNICALL
Java_cn_hlq_hlqjnipro_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

簡單可以看到,首先定義hello變量,之后return。

3.查看CMakeLists.txt

簡單的翻譯了下,如有不正之處歡迎指出`

# 使用Android Studio使用CMake的更多信息,閱讀文檔:https://d.android.com/studio/projects/add-native-code.html

# 設置CMake的最低版本構建本機所需庫
cmake_minimum_required(VERSION 3.4.1)

# 創建和名稱庫,使它是靜態的或共享,并提供了相對路徑的源代碼
# 可以定義多個圖書館,CMake將為你構建這些內容
# Gradle自動與你的APK包共享庫

add_library( # 設置庫名稱
             native-lib

             # 集庫作為一個共享庫
             SHARED

             # 提供了一個相對路徑你的源文件
             src/main/cpp/native-lib.cpp )

# 搜索指定預先構建的庫和存儲路徑變量。因為CMake包括系統庫搜索路徑中默認情況下,只需要指定想添加公共NDK庫的名稱,在CMake驗證庫之前存在完成構建

find_library( # 設置path變量的名稱
              log-lib

              # 在CMake定位前指定的NDK庫名稱
              log )

# 指定庫CMake應該鏈接到目標庫中,可以鏈接多個庫,比如定義庫,構建腳本,預先構建的第三方庫或者系統庫

target_link_libraries( # 指定目標庫
                       native-lib

                       # 目標庫到日志庫的鏈接 包含在NDK
                       ${log-lib} )

一開始還覺的差不多點呢,結果看到這里越來越蒙圈,還是先運行一下,看看結果吧。

按照剛才的簡單分析,這個demo輸出的結果應該為:Hello from C++ 。一起來驗證一下唄~

<center>
這里寫圖片描述
這里寫圖片描述

嗯,確實,輸出了,有些似懂非懂的,繼續研究~

經過查詢,發現一個之前從未關注的內容,那就是從編譯到運行示例 APP 的流程到底是怎樣呢,如下:

  1. Gradle 調用外部構建腳本,也就是 CMakeLists.txt;
  2. CMake 會根據構建腳本的指令去編譯一個 C++ 源文件,也就是 native-lib.cpp,并將編譯后的產物扔進共享對象庫中,并將其命名為 libnative-lib.so,然后 Gradle 將其打包到 APK 中;
  3. 在運行期間,APP 的 MainActivity 會調用 System.loadLibrary() 方法,加載 native library。而這個庫的原生函數,stringFromJNI(),就可以為 APP 所用了;
  4. MainActivity.onCreate() 方法會調用 stringFromJNI(),然后返回 “Hello from C++”,并更新 TextView 的顯示;

注意:Instant Run 并不兼容使用了 native code 的項目。Android Studio 會自動禁止 Instant Run 功能。

如果你想驗證一下 Gradle 是否將 native library 打包進了 APK,你可以使用 APK Analyzer:

  • 選擇 Build > Analyze APK。

  • 從 app/build/outputs/apk/ 路徑中選擇 APK,并點擊 OK。

  • 如下圖,在 APK Analyzer 窗口中,選擇 lib/<ABI>/,你就可以看見 libnative-lib.so

<center>
這里寫圖片描述
這里寫圖片描述

轉自:http://blog.csdn.net/wl9739/article/details/52607010

仿造demo來一下,首先布局中新增一個TextView,activity中編寫一個native方法,調用native方法并輸出結果,如下:

        TextView tv1 = (TextView) findViewById(R.id.sample_text_1);
        tv1.setText(hlqFromJNI());
    public native String hlqFromJNI();

在cpp中這么玩下:

#include <jni.h>
#include <string>

extern "C" {
JNIEXPORT jstring JNICALL
Java_cn_hlq_hlqjnipro_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

JNIEXPORT jstring JNICALL
Java_cn_hlq_hlqjnipro_MainActivity_hlqFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "真惡心啊 Fuck";
    return env->NewStringUTF(hello.c_str());
}

}

大家有沒有注意到Java_cn_hlq_hlqjnipro_MainActivity_hlqFromJNI這個名稱,方法名必須為Java_包名全路徑_方法名

運行結果如下:

<center>
這里寫圖片描述
這里寫圖片描述

一般情況下,Gradle 會將你的本地庫構建成 .so 文件,然后將其打包到你的 APK 中。如果你想 Gradle 構建并打包某個特定的 ABI 。你可以在你的 module 層級的 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 {...}
}

提供給別人使用

ReBuild Project之后,將生成好的so庫提供有需要的人即可,不過要記得調用的時候包名要一致,不然會報一個找不到so的問題。

LZ簡單寫了一個測試demo,如下圖:

<center>
這里寫圖片描述
這里寫圖片描述

運行結果如下:

<center>
這里寫圖片描述
這里寫圖片描述

比較惡心的就是包名這塊,等以后再看看有沒有好的解決辦法吧。

拓展APP構建流程

查看官方文檔中,突然發現有關構建流程內容,一起了解下,如下:

構建流程涉及許多將您的項目轉換成 Android 應用軟件包 (APK)的工具和流程。構建流程非常靈活,因此了解它的一些底層工作原理會很有幫助。

典型 Android 應用模塊的構建流程圖如下:

<center>
這里寫圖片描述
這里寫圖片描述

典型 Android 應用模塊的構建流程通常依循下列步驟:

1.編譯器將您的源代碼轉換成 DEX(Dalvik Executable) 文件(其中包括運行在 Android 設備上的字節碼),將所有其他內容轉換成已編譯資源;

2.APK 打包器將 DEX 文件和已編譯資源合并成單個 APK。不過,必須先簽署 APK,才能將應用安裝并部署到 Android 設備上;

3.APK 打包器使用調試或發布密鑰庫簽署您的 APK:

a. 如果您構建的是調試版本的應用(即專用于測試和分析的應用),打包器會使用調試密鑰庫簽署您的應用。Android Studio 自動使用調試密鑰庫配置新項目;

b. 如果您構建的是打算向外發布的發布版本應用,打包器會使用發布密鑰庫簽署您的應用

4.在生成最終 APK 之前,打包器會使用 zipalign 工具對應用進行優化,減少其在設備上運行時的內存占用

構建流程結束時,您將獲得可用來進行部署、測試的調試 APK,或者可用來發布給外部用戶的發布 APK。

參考文獻

  1. http://baike.baidu.com/link?url=QrA-HKewzfPKP5UejCXH8JhE4yQs5MhwG6EBYI8imO9k8zYIlM0h2DYffNSqdK8dG6LZLfT0dK5ocK2NEsOUJq
  2. http://blog.csdn.net/tongseng/article/details/53005123
  3. http://www.cnblogs.com/bastard/archive/2012/05/19/2508913.html
  4. http://blog.csdn.net/baidu_26352053/article/details/53931045
  5. https://developer.android.google.cn/ndk/guides/concepts.html
  6. https://developer.android.google.cn/studio/build/index.html;
  7. http://blog.csdn.net/wl9739/article/details/52607010

結束語

這篇文章寫的夠揪心,個人感覺亂糟糟的,實際學習的時候也是亂糟糟的,幾乎都是靠著各種搜索,況且還有一些不靠譜的博文誤導或者說是版本有些老。

望觀看者海涵`有問題及時溝通。

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

推薦閱讀更多精彩內容