JNI & NDK開發(fā)總結(jié)

提綱.png

一.JNI與NDK的關系

1.什么是JNI杏瞻?

JNI(Java Native Interface癌压,Java本地接口)蛹批,用于打通Java層與Native層撰洗。 這不是Android系統(tǒng)所獨有的,而是Java所有腐芍。
??Java語言是跨平臺的語言差导,而這跨平臺的背后都是依靠Java虛擬機,虛擬機采用C/C++編寫猪勇,適配各個系統(tǒng)设褐,通過JNI為上層Java提供各種服務,保證跨平臺性。通俗地說助析,JNI是一種技術犀被,通過這種技術可以做到以下兩點: Java程序中的函數(shù)可以調(diào)用Native語言寫的函數(shù); Native程序中的函數(shù)可以調(diào)用Java層的函數(shù)外冀。

1.1.1 正常情況下的Android框架

最頂層是 Android的應用程序代碼, 是純Java代碼, 中間有一層的Framework框架層, 通過Framework進行系統(tǒng)調(diào)用底層的庫 和 linux 內(nèi)核;

正常情況下的Android框架.jpg
1.1.2.使用JNI時的Android框架

繞過Framework提供的調(diào)用底層的代碼, 直接調(diào)用自己寫的C++代碼,該代碼最終會編譯成為一個動態(tài)的“.so庫(第二張圖的Native Libs)”寡键,該動態(tài)庫可以通過NDK提供的函數(shù)等工具,調(diào)用底下的C層Native Lib(上圖第三層)

使用JNI時的Android框架.png

2.什么是NDK?

NDK(英語:native development kit,原生開發(fā)工具包),是一種基于原生程序接口的軟件開發(fā)工具雪隧。通過此工具開發(fā)的程序直接以本地語言運行西轩,而非虛擬機。因此只有java等基于虛擬機運行的語言的程序才會有原生開發(fā)工具包脑沿。
??上面我們說過遭商,JNI是Java的一種特性,因此即便沒有NDK捅伤,我們?nèi)稳豢梢杂肅艸來寫我們的應用,那么為什么還要NDK呢巫玻?因為在此之前丛忆,在Android SDK文檔里,找不到任何JNI方面的幫助仍秤。即使第三方應用開發(fā)者使用JNI完成了自己的C++動態(tài)鏈接庫開發(fā)熄诡,但是so如何和應用程序一起打包成apk并發(fā)布?這里面也存在技術障礙诗力。

  • 因此凰浮,NDK提供了一系列的工具,幫助開發(fā)者快速開發(fā)C(或C++)的動態(tài)庫苇本,并能自動將so和java應用一起打包成apk袜茧。這些工具對開發(fā)者的幫助是巨大的。
  • NDK集成了交叉編譯器瓣窄,并提供了相應的mk文件隔離CPU笛厦、平臺、ABI等差異俺夕,開發(fā)人員只需要簡單修改mk文件(指出“哪些文件需要編譯”裳凸、“編譯特性要求”等),就可以創(chuàng)建出so劝贸。

二.JNI函數(shù)的動態(tài)注冊方式

1.JNI_OnLoad

在JNI中有一組特殊的函數(shù):

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved);
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved);

這一組函數(shù)的作用就是負責Java方法和本地C函數(shù)的鏈接姨谷,其中JNI_OnLoad方法是在動態(tài)庫被加載時調(diào)用(調(diào)用“System.loadLibrary("so庫名字");的時候”),而JNI_OnUnload則是在本地庫被卸載時調(diào)用映九。所以這兩個函數(shù)就是一個本地庫最重要的兩個生命周期方法梦湘。

2.JNIEnv和JavaVM的區(qū)別

JNI_OnLoad方法在動態(tài)注冊時的全部代碼如下:

jint JNI_OnLoad(JavaVM* vm, void* resered){
    JNIEnv* env = NULL;
    if((*vm).GetEnv((void**) &env, JNI_VERSION_1_6)!=JNI_OK){
        return JNI_ERR;
    }

    if(!registerNatives(env)){
        return JNI_ERR;
    }
    return JNI_VERSION_1_6;
}

注意到其中兩個類:JavaVM 和 JNIEnv

2.2.1 JavaVM 和 JNIEnv

JavaVM代表java的虛擬機。在java里,每一個process可以產(chǎn)生多個java vm對象践叠,但是在android上言缤,每一個process只有一個Dalvik虛擬機對象,也就是在android進程中是通過有且只有一個虛擬器對象來服務所有java和c/c++代碼禁灼,這個對象是線程共享的管挟。
??JNIEnv(JNI Interface Pointer)是提供JNI Native函數(shù)的基礎環(huán)境,線程相關弄捕,不同線程的JNIEnv相互獨立僻孝。

JNIEnv示意圖.png

從上圖可知,JNIEnv實際上就是提供了一些JNI系統(tǒng)函數(shù)守谓。通過這些函數(shù)可以做到:

  • 調(diào)用Java的函數(shù)穿铆。
  • 操作jobject對象等很多事情。

如果說到這里還不明白的話斋荞,沒關系荞雏,我們之后會講JNIEnv這個指針的具體用法。

2.2.2 Java和Android中JavaVM對象有區(qū)別

在java里平酿,每一個process可以產(chǎn)生多個java vm對象凤优,但是在android上,每一個process只有一個Dalvik虛擬機對象蜈彼,也就是在android進程中是通過有且只有一個虛擬器對象來服務所有java和c/c++代碼筑辨。

Android的dex字節(jié)碼c/c++的.so同時運行Dalvik虛擬機之內(nèi),共同使用一個進程空間幸逆。之所以可以相互調(diào)用棍辕,也是因為有Dalvik虛擬機*,Dalvik虛擬機說白了也是一個Android定制版的JVM还绘,谷歌對他做了很多優(yōu)化和調(diào)整(比如將JVM的機制堆棧尋址改為了基于寄存器)楚昭,因此它也是用C實現(xiàn)的,它的底層調(diào)的是第一張圖的第三層(系統(tǒng) Libs)拍顷。

java代碼需要c/c++代碼時哪替,在Dalvik虛擬機加載進.so庫時,會先調(diào)用JNI_Onload()菇怀,此時就會 把JAVA VM對象的指針存儲于c層jni組件的全局環(huán)境中凭舶,在Java層調(diào)用C層的本地函數(shù)時,調(diào)用c本地函數(shù)的線程必然通過Dalvik虛擬機來調(diào)用c層的本地函數(shù)爱沟,此時帅霜,Dalvik虛擬機會為本地的C組件實例化一個JNIEnv指針,該指針指向Dalvik虛擬機的具體的函數(shù)列表

JNI的c組件調(diào)用Java層的方法或者屬性時呼伸,也需要通過JNIEnv指針來進行調(diào)用身冀。當本地c/c++想獲得當前線程所要使用的JNIEnv時钝尸,可以使用Dalvik虛擬機對象的JavaVM jvm->GetEnv()返回當前線程所在的JNIEnv*(上面代碼也展示了):

if((*vm).GetEnv((void**) &env, JNI_VERSION_1_6)!=JNI_OK){

其中JNI_VERSION_1_6為JNI版本,這個值可以通過jint GetVersion(JNIEnv *env);來獲取當前的JNI版本搂根,返回值是宏定義的常量珍促,我們可以使用獲取到的值與下列宏進行匹配來知道當前的版本:

#define JNI_VERSION_1_1 0x00010001
#define JNI_VERSION_1_2 0x00010002
#define JNI_VERSION_1_4 0x00010004
#define JNI_VERSION_1_6 0x00010006

3.動態(tài)注冊

2.3.1 JNINativeMethod

首先我們來說說JNINativeMethod,就是我們動態(tài)注冊時建立的函數(shù)映射表剩愧,將Java代碼中的native函數(shù)名和native函數(shù)對應起來:

static JNINativeMethod methods[] = {
        {
                "nativeBaseDataType",  //Java代碼中的native函數(shù)的名字
                "(SIJFDCZ)V",   //方法的簽名信息,主要是參數(shù)+返回值猪叙,V表示返回類型為void
                (void*)baseDataType //函數(shù)指針,指向一個native中定義的C++函數(shù)
        }
}

上面建立了兩個函數(shù)對應關系仁卷,也就是JNINativeMethod數(shù)組中的兩個結(jié)構(gòu)體穴翩,每個結(jié)構(gòu)體有三個成員,第一個為Java代碼中的native函數(shù)的名字锦积,第三個為native中對應被調(diào)用的函數(shù)名字芒帕,我們來看看這兩個函數(shù):

java代碼中:
public static native void nativeBaseDataType(
                short s, int i, long l, float f, double d, char c, boolean b );
C++代碼中:
void baseDataType(JNIEnv* jniEnv,jobject jobj,jshort s,
                    jint i, jlong l, jfloat f, jdouble d, jchar c, jboolean b){
    ELOG("LearnJNI.cpp --> baseDataType:%d,%f,%c,%d",i,d,c,b);
}

這個方法展示了Java層向C++層傳遞基本類型數(shù)據(jù)》峤椋可以看到JNINativeMethod數(shù)組中結(jié)構(gòu)體的第一個元素和第三個元素就是這兩個地方方法的名字(其中第三個元素前面必須加上(void*))

我們再來看看結(jié)構(gòu)體中第二個元素背蟆,(SIJFDCZ)V,這就是Java代碼中對應的JNI函數(shù)的簽名哮幢,也就是參數(shù)類型+返回值類型,只是這個類型有一定的對應關系:

因此public static native void nativeBaseDataType(short s, int i, long l, float f, double d, char c, boolean b );對應(SIJFDCZ)V淆储;public static native String nativeReturnString();對應()Ljava/lang/String;
??同時應當注意,jni函數(shù)簽名中家浇。參數(shù)類型之間不用“;”隔開(除了String類型的標識為“ Ljava/lang/String; ”自帶“;”),直接連在一起就可以了碴裙,同時數(shù)組標識是在元素類型前面加“[”钢悲,如int[]標識為“[I”.

2.3.2 RegisterNatives

接著2中的那段代碼,看看“registerNatives()”:

static int registerNatives(JNIEnv *env) {
    const char *className = "com/example/dell/growup/LearnJNI"; //指定注冊的類
    return registerNativeMethods(env, className, methods, sizeof(methods) / sizeof(methods[0]));
}
static int registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *gMethods,
                                 int numMethods) {
    jclass clazz;
    clazz = (*env).FindClass(className);
    if (clazz == NULL) {
        return JNI_FALSE;
    }

    if ((*env).RegisterNatives(clazz, gMethods, numMethods) < 0) {
        return JNI_FALSE;
    }
    return JNI_TRUE;
}

可以看到舔株,這里JNIEnv env排上用場了莺琳,首先className中我們指定了注冊native函數(shù)的包名+類名,接著我們調(diào)用JNIEnv的FindClass*方法clazz = (*env).FindClass(className);來獲取注冊native函數(shù)的類载慈,之后通過JNIEnv調(diào)用JNI函數(shù) (*env).RegisterNatives(clazz, gMethods, numMethods) 來注冊上面JNINativeMethod中定義的對應函數(shù)惭等。

當Java層通過System.loadLibrary加載完JNI動態(tài)庫后,緊接著會查找該庫中一個叫JNI_OnLoad的函數(shù)办铡,如果有辞做,就調(diào)用它,而動態(tài)注冊的工作就是在這里完成的寡具。
??所以秤茅,如果想使用動態(tài)注冊方法,就必須要實現(xiàn)JNI_OnLoad函數(shù)童叠,在這個函數(shù)中會完成動態(tài)注冊的工作框喳。靜態(tài)注冊則沒有這個要求,一般我們可以自己實現(xiàn)這個JNI_OnLoad函數(shù),并在其中做一些初始化工作(即使是靜態(tài)注冊)五垮。

我們再貼一遍JNI_OnLoad()函數(shù)代碼:

//該函數(shù)的第一個參數(shù)類型為JavaVM,是虛擬機在JNI層的代表乍惊,每個Java進程只有一個
jint JNI_OnLoad(JavaVM* vm, void* resered){
    JNIEnv* env = NULL;
    if((*vm).GetEnv((void**) &env, JNI_VERSION_1_6)!=JNI_OK){
        return JNI_ERR;
    }

    if(!registerNatives(env)){
        return JNI_ERR;
    }
    return JNI_VERSION_1_6;
}

三.JNIEnv的各種操作

1.基本類型數(shù)據(jù)處理

其實基本類型處理上面我們已經(jīng)展示過了,這里再貼一遍代碼:

java代碼中:
public static native void nativeBaseDataType(
                                short s, int i, long l, float f, double d, char c, boolean b);
C++代碼中:
void baseDataType(JNIEnv* jniEnv,jobject jobj,
                            jshort s,jint i, jlong l, jfloat f, jdouble d, jchar c, jboolean b){
    ELOG("LearnJNI.cpp --> baseDataType:%d,%f,%c,%d",i,d,c,b);
}

可以看到放仗,java中的基本數(shù)據(jù)類型润绎,在native中基本上就是在前面加了個“j”,實際上也就是在“jni.h”(NDK提供的jni開發(fā)類)中給java中的基本類型用結(jié)構(gòu)體包裝類一下:

typedef uint8_t  jboolean; /* unsigned 8 bits */
typedef int8_t   jbyte;    /* signed 8 bits */
typedef uint16_t jchar;    /* unsigned 16 bits */
typedef int16_t  jshort;   /* signed 16 bits */
typedef int32_t  jint;     /* signed 32 bits */
typedef int64_t  jlong;    /* signed 64 bits */
typedef float    jfloat;   /* 32-bit IEEE 754 */
typedef double   jdouble;  /* 64-bit IEEE 754 */

從Java層往C++層傳遞基本類型數(shù)據(jù)是很容易的,只要上面函數(shù)的映射關系建立好了匙监,就跟普通函數(shù)傳參一樣凡橱。

2.String字符串處理

這里我們展示一個從java向C++傳基本類型、String亭姥、數(shù)組稼钩,并從C++中像向Java中傳遞String的例子:

Java代碼中:
void returnString(){
        int i =3;
        char[] c = {'J','N','I'};
        String s = "learn";

        String string = nativeReturnJavaString(i,s,c);
        Log.e("returnString",string);
    }
public static native String nativeReturnJavaString(int i, String s, char[] c);
C++代碼:
static JNINativeMethod methods[] = {
    {
                "nativeReturnJavaString",
                "(ILjava/lang/String;[C)Ljava/lang/String;",  //注意多種數(shù)據(jù)類型簽名時中間沒有分號或逗號
                (void*)returnJavaString
        }
}

jstring returnJavaString(JNIEnv *jniEnv, jobject jobj, jint i, jstring j_str, jcharArray j_char){
    const char* c_str = NULL;
    jchar* j_charArray = NULL;
    jint arr_len;
    jint str_len;
    char buff[120] = {0};
    jboolean isCopy;

    arr_len = (*jniEnv).GetArrayLength(j_char);// 獲取char數(shù)組長度
    str_len = (*jniEnv).GetStringLength(j_str);// 獲取String長度

    j_charArray = (jchar*)malloc(sizeof(jchar)* arr_len);   // 根據(jù)數(shù)組長度和數(shù)組元素的數(shù)據(jù)類型申請存放java數(shù)組元素的緩沖區(qū)
    memset(j_charArray, 0,sizeof(jchar)* arr_len ); // 初始化緩沖區(qū)
    (*jniEnv).GetCharArrayRegion(j_char, 0, arr_len, j_charArray);  // 拷貝Java數(shù)組中的所有元素到緩沖區(qū)中

    c_str = (*jniEnv).GetStringUTFChars(j_str, &isCopy);
    sprintf(buff, "%s", c_str);   //sprintf(s, "%d", 123);  //把整數(shù)123打印成一個字符串保存在s中
    for(int j=0; j<i; j++){
        buff[str_len+j] = (char) j_charArray[j];
        //ELOG("LearnJNI.cpp --> returnJavaString:%c",buff[str_len+j]);
    }

    free(j_charArray);  // 釋放存儲數(shù)組元素的緩沖區(qū)
    (*jniEnv).ReleaseStringUTFChars(j_str,c_str);

    return jniEnv->NewStringUTF(buff);
}

我們知道,Java中String是一個對象达罗,他申明的對象是存放在JVM內(nèi)部數(shù)據(jù)結(jié)構(gòu)中的(堆坝撑,棧,靜態(tài)區(qū))粮揉。 JNI把Java中的所有對象當作一個C指針(對象的引用)傳遞到本地方法中巡李,這個指針指向JVM中的內(nèi)部數(shù)據(jù)結(jié)構(gòu)(即這個對象存放的地址),而內(nèi)部的數(shù)據(jù)結(jié)構(gòu)在內(nèi)存中的存儲方式是不可見的扶认。只能從JNIEnv指針指向的函數(shù)表中選擇合適的JNI函數(shù)來操作JVM中的數(shù)據(jù)結(jié)構(gòu)侨拦。

訪問java.lang.String對應的JNI類型jstring時,沒有像訪問基本數(shù)據(jù)類型一樣直接使用辐宾,因為它在Java是一個引用類型狱从,所以在本地代碼中只能通過GetStringUTFChars這樣的JNI函數(shù)來訪問字符串的內(nèi)容。

3.2.1 const char* GetStringUTFChars(env, j_str, &isCopy)
  env:JNIEnv函數(shù)表指針
  j_str:jstring類型(Java傳遞給本地代碼的字符串指針)
  isCopy:取值JNI_TRUE和JNI_FALSE叠纹,如果值為JNI_TRUE季研,表示返回JVM內(nèi)部源字符串的一份拷貝,并為新產(chǎn)生的字符串分配內(nèi)存空間誉察。如果值為JNI_FALSE与涡,表示返回JVM內(nèi)部源字符串的指針,意味著可以通過指針修改源字符串的內(nèi)容持偏,不推薦這么做驼卖,因為這樣做就打破了Java字符串不能修改的規(guī)定。但我們在開發(fā)當中鸿秆,并不關心這個值是多少款慨,通常情況下這個參數(shù)填NULL即可。

因為Java默認使用Unicode編碼谬莹,而C/C++默認使用UTF編碼檩奠,所以在本地代碼中操作字符串的時候桩了,必須使用合適的JNI函數(shù)把jstring轉(zhuǎn)換成C風格的字符串。JNI支持字符串在Unicode和UTF-8兩種編碼之間轉(zhuǎn)換埠戳,GetStringUTFChars可以把一個jstring指針(指向JVM內(nèi)部的Unicode字符序列)轉(zhuǎn)換成一個UTF-8格式的C字符串井誉。

3.2.2 jstring NewStringUTF(const char* bytes)

通過調(diào)用NewStringUTF函數(shù),會構(gòu)建一個新的java.lang.String字符串對象整胃。這個新創(chuàng)建的字符串會自動轉(zhuǎn)換成Java支持的Unicode編碼颗圣。

3.2.3 void ReleaseStringUTFChars(jstring string, const char* utf)

在調(diào)用GetStringUTFChars函數(shù)從JVM內(nèi)部獲取一個字符串之后,JVM內(nèi)部會分配一塊新的內(nèi)存屁使,用于存儲源字符串的拷貝在岂,以便本地代碼訪問和修改。即然有內(nèi)存分配蛮寂,用完之后馬上釋放.通過調(diào)用ReleaseStringUTFChars函數(shù)通知JVM這塊內(nèi)存已經(jīng)不使用了蔽午,你可以清除了。注意:這兩個函數(shù)是配對使用的酬蹋,用了GetXXX就必須調(diào)用ReleaseXXX及老,而且這兩個函數(shù)的命名也有規(guī)律,除了前面的Get和Release之外范抓,后面的都一樣骄恶。

3.訪問數(shù)組

??訪問數(shù)組的例子上面已經(jīng)展示過了,這里主要說明一下幾個訪問數(shù)組相關的函數(shù)的用法匕垫。

3.3.1 jsize GetArrayLength(jarray array)

??獲取數(shù)組的長度僧鲁,返回值為jint類型,我們獲取數(shù)組的類型之后就可以調(diào)用malloc函數(shù)動態(tài)的分配內(nèi)存:

jint arr_len;
arr_len = (*jniEnv).GetArrayLength(j_char);

jchar* j_charArray = NULL;
j_charArray = (jchar*)malloc(sizeof(jchar)* arr_len);
memset(j_charArray, 0,sizeof(jchar)* arr_len );

??我們需要一個jchar* 類型的指針指向我們剛分配的內(nèi)存(的首地址)象泵,以便之后調(diào)用 memset函數(shù)的時候找到這塊內(nèi)存并將其中的字節(jié)初始化為0(申請的內(nèi)存中可能有之前殘余的值)寞秃,我們來看看這個memset函數(shù):

memset() 函數(shù)用來將指定內(nèi)存的前n個字節(jié)設置為特定的值(經(jīng)常用于初始化剛剛申請的內(nèi)存),其原型為:
    void * memset( void * ptr, int value, size_t num );

參數(shù)說明:
ptr 為要操作的內(nèi)存的指針单芜。
value 為要設置的值。你既可以向 value 傳遞 int 類型的值犁柜,也可以傳遞 char 類型的值洲鸠,
                int 和 char 可以根據(jù) ASCII 碼相互轉(zhuǎn)換。
num 為 ptr 的前 num 個字節(jié)馋缅,size_t 就是unsigned int扒腕。
3.3.2 void GetCharArrayRegion(jcharArray array, jsize start, jsize len,jchar* buf)
...
j_charArray = (jchar*)malloc(sizeof(jchar)* arr_len);
memset(j_charArray, 0,sizeof(jchar)* arr_len ); // 初始化緩沖區(qū)
(*jniEnv).GetCharArrayRegion(j_char, 0, arr_len, j_charArray);  // 拷貝Java數(shù)組中的所有元素到緩沖區(qū)中

c_str = (*jniEnv).GetStringUTFChars(j_str, &isCopy);
sprintf(buff, "%s", c_str);     //sprintf(s, "%d", 123);  //把整數(shù)123打印成一個字符串保存在s中

??GetCharArrayRegion將Java數(shù)組j_char,拷貝到我們剛剛申請的內(nèi)存j_charArray中萤悴,之后我們調(diào)用c_str = (*jniEnv).GetStringUTFChars(j_str, &isCopy);將java層傳來的 j_str(String類型)轉(zhuǎn)換成一個C風格字符串(c_str)瘾腰,然后sprintf(buff, "%s", c_str);是將作為C風格的字符串儲存在buff數(shù)組中。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末覆履,一起剝皮案震驚了整個濱河市蹋盆,隨后出現(xiàn)的幾起案子费薄,更是在濱河造成了極大的恐慌,老刑警劉巖栖雾,帶你破解...
    沈念sama閱讀 221,331評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件楞抡,死亡現(xiàn)場離奇詭異,居然都是意外死亡析藕,警方通過查閱死者的電腦和手機召廷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,372評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來账胧,“玉大人竞慢,你說我怎么就攤上這事≈文啵” “怎么了筹煮?”我有些...
    開封第一講書人閱讀 167,755評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長车摄。 經(jīng)常有香客問我寺谤,道長,這世上最難降的妖魔是什么吮播? 我笑而不...
    開封第一講書人閱讀 59,528評論 1 296
  • 正文 為了忘掉前任变屁,我火速辦了婚禮,結(jié)果婚禮上意狠,老公的妹妹穿的比我還像新娘粟关。我一直安慰自己,他們只是感情好环戈,可當我...
    茶點故事閱讀 68,526評論 6 397
  • 文/花漫 我一把揭開白布闷板。 她就那樣靜靜地躺著,像睡著了一般院塞。 火紅的嫁衣襯著肌膚如雪渴析。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,166評論 1 308
  • 那天涤躲,我揣著相機與錄音旷祸,去河邊找鬼。 笑死汹族,一個胖子當著我的面吹牛萧求,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播顶瞒,決...
    沈念sama閱讀 40,768評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼夸政,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了榴徐?” 一聲冷哼從身側(cè)響起守问,我...
    開封第一講書人閱讀 39,664評論 0 276
  • 序言:老撾萬榮一對情侶失蹤匀归,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后酪碘,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體朋譬,經(jīng)...
    沈念sama閱讀 46,205評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,290評論 3 340
  • 正文 我和宋清朗相戀三年兴垦,在試婚紗的時候發(fā)現(xiàn)自己被綠了徙赢。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,435評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡探越,死狀恐怖狡赐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情钦幔,我是刑警寧澤枕屉,帶...
    沈念sama閱讀 36,126評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站鲤氢,受9級特大地震影響搀擂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜卷玉,卻給世界環(huán)境...
    茶點故事閱讀 41,804評論 3 333
  • 文/蒙蒙 一哨颂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧相种,春花似錦威恼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,276評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至衬潦,卻和暖如春斤蔓,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背镀岛。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工弦牡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人哎媚。 一個月前我還...
    沈念sama閱讀 48,818評論 3 376
  • 正文 我出身青樓喇伯,卻偏偏與公主長得像喊儡,于是被迫代替她去往敵國和親拨与。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,442評論 2 359

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