Android JNI

JNI是 Java Native Interface 的縮寫盅视,顧名思義,翻譯為 Java 本地接口旦万,是 Java 與 C++/C 語言通信的橋梁闹击。當(dāng) Java 語言無法勝任時(shí),便通過 JNI 技術(shù)成艘,調(diào)用 C++/C 語言來處理赏半。

大致有三種情況需要使用 JNI 技術(shù),第一種:需要調(diào)用 UNIX 系統(tǒng)的某個(gè)功能淆两,而這個(gè)功能并非 Java 語言完成的断箫;第二種:需要使用早期用 C++/C 語言開發(fā)的一些功能;第三種:游戲秋冰、音視頻開發(fā)涉及的音視頻編解碼和圖像繪制需要更快的處理速度仲义。

Android 系統(tǒng)按語言來劃分的話,分為 Java 層和 Native 層剑勾。 Java 通過 JNI 技術(shù)可以訪問 Native 層級(jí)的代碼光坝, Native 層級(jí)的代碼自然也可以通過 JNI 技術(shù)訪問 Java層級(jí)的代碼。

我們學(xué)習(xí) JNI 技術(shù)的直接目的就是為了深入 Android Native 層甥材,理解 Native 層級(jí)的代碼盯另。

Java 訪問 Native

Java 要訪問 Native 的前提是把 Java 層的 Native 方法注冊(cè)到 Native 層,而注冊(cè)主要有兩種方式洲赵,一種是靜態(tài)注冊(cè)鸳惯,一種是動(dòng)態(tài)注冊(cè)商蕴。

靜態(tài)注冊(cè)

通過 Java 的 native 方法生成 Jni 方法有兩種方式,分別是自動(dòng)生成和手動(dòng)生成芝发。

  • 自動(dòng)生成
    靜態(tài)注冊(cè)在 AndroidStudio 中可以實(shí)現(xiàn) native 方法的自動(dòng)生成绪商,只需要?jiǎng)?chuàng)建 native project 或者引入 native library ,利用 cmake 進(jìn)行編譯即可辅鲸。
  • 手動(dòng)生成
    自動(dòng)生成的方式并不會(huì)將生成的 native 方法存放到一個(gè) .h 文件格郁,只會(huì)存放到 .cpp 文件中。項(xiàng)目中往往需要 .h 文件的存在独悴,這個(gè)時(shí)候選擇手動(dòng)生成例书。手動(dòng)生成通過 javac 和 javah 方法。以下面的內(nèi)容為示例:
public class HelloWorld {
    static {
        System.loadLibrary("hello_world");
    }

    public native void helloWorld();

    public static void main(String[] args) {
        new HelloWorld().helloWorld();
    }
}

因?yàn)槭钱?dāng)前路徑下執(zhí)行刻炒,所以源代碼不添加package决采。

javac HelloWorld.java
javah HelloWorld

分別會(huì)生成兩個(gè)文件,分別是 class 文件和 .h 文件坟奥,只看 .h 文件树瞭,內(nèi)容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h> // 注釋1
/* Header for class HelloWorld */

#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" { // 2
#endif
/*
 * Class:     HelloWorld
 * Method:    helloWorld
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloWorld_helloWorld
  (JNIEnv *, jobject); // 3

#ifdef __cplusplus
}
#endif
#endif
  1. 注釋 1 處的 jni.h 位于 /usr/lib/jvm/java-8-openjdk-amd64/include 路徑下。(Ubuntu環(huán)境)
  2. 注釋 2 處的 extern "C" 的主要作用就是為了能夠正確實(shí)現(xiàn) C++ 代碼調(diào)用其他 C 語言代碼爱谁。加上 extern "C" 后晒喷,會(huì)指示編譯器這部分代碼按 C 語言的進(jìn)行編譯,而不是 C++ 的访敌。
  3. 注釋 3 處的方法為 Java 的 native 方法生成凉敲。
    JNIEXPORT 和 JNICALL 為兩個(gè)宏,用于設(shè)置函數(shù)可見性捐顷,以及調(diào)用棧約定,這里可以忽略這兩個(gè)宏雨效,void 為方法的返回值迅涮。
    方法以Java_開頭,接著是包名和類名徽龟,以_替換.叮姑,最后是方法名。
    Java 中的 native 方法并沒有參數(shù)据悔,在 Jni 方法中會(huì)增加兩個(gè)參數(shù):JNIEnv *指針類型和jobject類型的參數(shù)传透。
    JNIEnv 是 Natvie 世界中 Java 環(huán)境的代表,通過 JNIEnv * 指針就可以在 Native 世界中訪問 Java 世界的代碼進(jìn)行操作极颓,它只在創(chuàng)建它的線程中有效朱盐,不能跨線程傳遞。jobject 同樣也是 JNI 的數(shù)據(jù)類型菠隆,對(duì)應(yīng)于 Java 的 Object兵琳,指向 this 的指針狂秘,用于獲取類相關(guān)的信息(變量、方法等)躯肌。

以上只是生成了 .h 文件者春,還需要生成 .c 文件。

#include "HelloWorld.h"
#include <jni.h>
#include <stdio.h>

JNIEXPORT void JNICALL Java_HelloWorld_helloWorld (JNIEnv *env, jobject obj) {
  printf("HelloWorld JNI!\n");
}

編譯共享文件庫(kù):

g++ -shared -I/usr/lib/jvm/java-8-openjdk-amd64/include -I/usr/lib/jvm/java-8-openjdk-amd64/include/linux HelloWorld.c -o libhello_world.so

運(yùn)行 java 文件:

$java -Djava.library.path=. HelloWorld
HelloWorld JNI!

動(dòng)態(tài)注冊(cè)

以 framework services 模塊 services/java/com/android/server/SystemServer.java 中的 native 方法 startSensorService 調(diào)用流程舉例清女,講解如何進(jìn)行動(dòng)態(tài)注冊(cè)钱烟。

    /**
     * Start the sensor service. This is a blocking call and can take time.
     */
    private static native void startSensorService();
  1. 加載 libandroid_servers.so,然后調(diào)用 JNI_OnLoad 方法嫡丙。
    當(dāng)執(zhí)行 services/java/com/android/server/SystemServer.java 中的 System.loadLibrary("android_servers"); 時(shí)拴袭,會(huì)調(diào)用 JNI_OnLoad 方法,這個(gè)方法位于 services/core/jni/onload.cpp 中迄沫。
extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
    JNIEnv* env = NULL;
    jint result = -1;

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        ALOGE("GetEnv failed!");
        return result;
    }
    ALOG_ASSERT(env, "Could not retrieve the env!");

    ...
    register_android_server_SystemServer(env);
    ...
    return JNI_VERSION_1_4;
}
  1. 調(diào)用 register_android_server_SystemServer(env);
    這個(gè)方法位于 services/core/jni/com_android_server_SystemServer.cpp 中稻扬。
int register_android_server_SystemServer(JNIEnv* env)
{
    return jniRegisterNativeMethods(env, "com/android/server/SystemServer",
            gMethods, NELEM(gMethods));
}
  1. 調(diào)用 jniRegisterNativeMethods
    jniRegisterNativeMethods 位于 libnativehelper/JNIHelp.cpp 中。
inline int jniRegisterNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) {
    return jniRegisterNativeMethods(&env->functions, className, gMethods, numMethods);
}
MODULE_API int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
    const JNINativeMethod* gMethods, int numMethods)
{
    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);

    ALOGV("Registering %s's %d native methods...", className, numMethods);

    scoped_local_ref<jclass> c(env, findClass(env, className));
    ALOG_ALWAYS_FATAL_IF(c.get() == NULL,
                         "Native registration unable to find class '%s'; aborting...",
                         className);

    int result = e->RegisterNatives(c.get(), gMethods, numMethods);
    ALOG_ALWAYS_FATAL_IF(result < 0, "RegisterNatives failed for '%s'; aborting...",
                         className);

    return 0;
}

這里會(huì)調(diào)用 RegisterNatives 方法完成注冊(cè)羊瘩。流程介紹完了泰佳,我們重點(diǎn)看 jniRegisterNativeMethods 方法,它的參數(shù)分別是類名尘吗,方法數(shù)組逝她,方法個(gè)數(shù)。重點(diǎn)看方法數(shù)組 gMethods 睬捶。

  1. gMethods: Java native 方法 與 Jni 方法映射表
/*
 * JNI registration.
 */
static const JNINativeMethod gMethods[] = {
    /* name, signature, funcPtr */
    { "startSensorService", "()V", (void*) android_server_SystemServer_startSensorService },
    ...
};

SystemServer.java 中的 startSensorService 映射到了 services/core/jni/com_android_server_SystemServer.cpp 中的 android_server_SystemServer_startSensorService 方法上潮酒。這樣每次在 SystemServer.java 中添加一個(gè) native 方法假夺,就只需在 gMethods 里面映射一個(gè) Jni 方法即可。

  1. JNINativeMethod 類型
typedef struct {
    const char* name;
    const char* signature;
    void*       fnPtr;
} JNINativeMethod;

gMethods 數(shù)組的每個(gè)元素的類型是 JNINativeMethod 。它是一個(gè)結(jié)構(gòu)體污抬,由三個(gè)元素組成,分別是 Java native 方法名包归,Java native 方法的簽名信息痢艺,Jni 方法的指針。

總結(jié):

  • 如果在 framework 或者 services 模塊的某個(gè)類中添加 native 方法座韵,只需要在相應(yīng)的 gMethods 數(shù)組中建立與 Jni 方法的映射關(guān)系即可险绘。
  • 如果在新文件中添加native方法,那就在 JNI_OnLoad 方法中誉碴,將相應(yīng)的 cpp 類進(jìn)行注冊(cè)即可宦棺。

數(shù)據(jù)類型

Java 的數(shù)據(jù)類型分為基本數(shù)據(jù)類型和引用數(shù)據(jù)類型,JNI 層同樣分為了這兩種類型黔帕。Java 的數(shù)據(jù)類型到了 JNI 層需要轉(zhuǎn)換為 JNI 層的數(shù)據(jù)類型代咸。

基本數(shù)據(jù)類型的轉(zhuǎn)換

基本數(shù)據(jù)類型的轉(zhuǎn)換

引用數(shù)據(jù)類型的轉(zhuǎn)換

引用數(shù)據(jù)類型的轉(zhuǎn)換

數(shù)組的 JNI 層數(shù)據(jù)需要以 Array 結(jié)尾,簽名格式的開頭都會(huì)有 [ 成黄。需要注image.png意有些數(shù)據(jù)類型的簽名以 “;” 結(jié)尾侣背,引用數(shù)據(jù)類型還具有繼承關(guān)系白华,如下圖所示:


引用數(shù)據(jù)類型的繼承關(guān)系

改寫之前的 HelloWorld.java ,給 helloWorld 方法添加參數(shù)贩耐,Java 數(shù)據(jù)類型分別是 Object 和 String 弧腥。

public native void helloWorld(Object object,String str);
javac HelloWorld.java
javah HelloWorld

生成新的 JNI 方法

/*
 * Class:     HelloWorld
 * Method:    helloWorld
 * Signature: (Ljava/lang/Object;Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_HelloWorld_helloWorld
  (JNIEnv *, jobject, jobject, jstring);

對(duì)比發(fā)現(xiàn),Object 數(shù)據(jù)類型轉(zhuǎn)換為了 jobject潮太,String 數(shù)據(jù)類型轉(zhuǎn)換為了 jstring管搪。

方法簽名

由于 Java 是有重載方法的,可以定義方法名相同铡买,但參數(shù)不同的方法更鲁,正因?yàn)槿绱耍?JNI 中僅僅通過方法名是無法定位 Java 中對(duì)應(yīng)的具體方法的奇钞,JNI 為了解決這一問題就將參數(shù)類型和返回值類型組合在一起作為方法簽名澡为。通過方法簽名和方法名就可以找到對(duì)應(yīng)的 Java 方法。

static const JNINativeMethod gBinderProxyMethods[] = {
     /* name, signature, funcPtr */
    {"pingBinder",          "()Z", (void*)android_os_BinderProxy_pingBinder},
    {"isBinderAlive",       "()Z", (void*)android_os_BinderProxy_isBinderAlive},
    {"getInterfaceDescriptor", "()Ljava/lang/String;", (void*)android_os_BinderProxy_getInterfaceDescriptor},
    {"transactNative",      "(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z", (void*)android_os_BinderProxy_transact},
    {"linkToDeath",         "(Landroid/os/IBinder$DeathRecipient;I)V", (void*)android_os_BinderProxy_linkToDeath},
    {"unlinkToDeath",       "(Landroid/os/IBinder$DeathRecipient;I)Z", (void*)android_os_BinderProxy_unlinkToDeath},
    {"getNativeFinalizer",  "()J", (void*)android_os_BinderProxy_getNativeFinalizer},
};
typedef struct {
    const char* name;
    const char* signature;
    void*       fnPtr;
} JNINativeMethod;

上文有講景埃,JNINativeMethod 類型的第二個(gè)參數(shù)即為方法簽名媒至,如 gBinderProxyMethods 數(shù)組所示,方法簽名的格式為:

(參數(shù)簽名格式...) 返回值簽名格式

以 Java transactNative 方法舉例:

    public native boolean transactNative(int code, Parcel data, Parcel reply,
            int flags) throws RemoteException;
{"transactNative",      "(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z", (void*)android_os_BinderProxy_transact},

Native 訪問 Java

JNIEnv 解析

Natvie 訪問 Java 通過 JNIEnv 谷徙, JNIEnv 是 Native 世界中 Java 環(huán)境的代表拒啰,通過 JNIEnv * 指針就可以在 Native 世界中訪問 Java 世界的代碼進(jìn)行操作,它只在創(chuàng)建它的線程中有效完慧,不能跨線程傳遞谋旦,因此不同線程的 JNIEnv 是彼此獨(dú)立的。
JNIEnv 的主要?jiǎng)幼饔袃牲c(diǎn):

  1. 調(diào)用 Java 的方法屈尼。
  2. 操作 Java (操作 Java 中的變量和對(duì)象)
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif

JNIEnv 定義在 libnativehelper/include_jni/jni.h 文件中册着。
使用 宏 __cplusplus 來區(qū)分 C 和 C++ 兩種代碼。在 C 中脾歧,JNIEnv 的類型是 JNINativeInterface* 甲捏,在 C++ 中的類型是 _JNIEnv 。我們這里重點(diǎn)看 C++ 中的類型涨椒,也就是 _JNIEnv 摊鸡。它也在 libnativehelper/include_jni/jni.h 中定義绽媒。

/*
486 * C++ object wrapper.
487 *
488 * This is usually overlaid on a C struct whose first element is a
489 * JNINativeInterface*.  We rely somewhat on compiler behavior.
490 */
491struct _JNIEnv {
492    /* do not rename this; it does not seem to be entirely opaque */
493    const struct JNINativeInterface* functions;
494
495#if defined(__cplusplus)
...
504    jclass FindClass(const char* name)
505    { return functions->FindClass(this, name); }
...
591    jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
592    { return functions->GetMethodID(this, clazz, name, sig); }
...
694    jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
695    { return functions->GetFieldID(this, clazz, name, sig); }
...
1032};

_JNIEnv 如上所示是一個(gè)結(jié)構(gòu)體蚕冬,其內(nèi)部有一個(gè) JNINativeInterface* 類型的靜態(tài)常量 functions ∈窃可見 C++ 的 JNIEnv 是對(duì) C 的 JNIEnv 的一次封裝囤热。通過 functions 可以調(diào)用很多函數(shù)。這里說下常用的三個(gè)函數(shù):

  1. FindClass: 用來找到 Java 中指定的名稱的類获三。
  2. GetMethodID: 用來得到 Java 中的方法旁蔼。
  3. GetFieldID: 用來得到 Java 中的成員變量

以上三個(gè)函數(shù)都調(diào)用 functions 锨苏,它是 JNINativeInterface* 類型的變量,來看下 JNINativeInterface* 類型:

struct JNINativeInterface {

    jclass      (*FindClass)(JNIEnv*, const char*);
    jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
    jfieldID    (*GetFieldID)(JNIEnv*, jclass, const char*, const char*);
    ...
}

JNINativeInterface 結(jié)構(gòu)中定義了很多和 JNIEnv* 相關(guān)聯(lián)的函數(shù)指針棺聊,至于函數(shù)指針是何時(shí)被賦值的伞租,我們就不再關(guān)注了,感興趣的可以查詢下相關(guān)資料限佩。

示例

#include "HelloWorld.h"
#include <jni.h>
#include <stdio.h>

JNIEXPORT void JNICALL Java_HelloWorld_helloWorld (JNIEnv* env, jobject obj,jobject obj1,jstring jstr) {
  jclass clazz;
  clazz = env->FindClass("HelloWorld"); //1. 獲取類
  //clazz = env->GetObjectClass(obj); //2. 獲取類 obj代表調(diào)用當(dāng)前jni方法的java對(duì)象
  if (clazz == NULL) {
    return;
  }
  jmethodID methodID = env->GetMethodID(clazz,"helloWorldFromJava","(Ljava/lang/String;)V");
  if (methodID == NULL) {
    return;
  }
  jfieldID fieldID = env->GetFieldID(clazz,"mField","Ljava/lang/String;"); //獲取變量
  jstring fieldName = static_cast<jstring>(env->GetObjectField(obj,fieldID)); //獲取變量的值
  env->CallVoidMethod(obj, methodID,fieldName); //把成員變量的值作為參數(shù)傳遞給方法
}

public class HelloWorld {

    private String mField = "HelloWorld";

    static {
        System.loadLibrary("hello_world");
    }

    public native void helloWorld(Object object,String str);

    public static void main(String[] args) {
        new HelloWorld().helloWorld("HelloWorldObj","HelloWorldStr");
    }
    
    private void helloWorldFromJava(String str) {
        System.out.println(str);;
    }

}
javac HelloWorld.java
javah HelloWorld
g++ -shared -I/usr/lib/jvm/java-8-openjdk-amd64/include -I/usr/lib/jvm/java-8-openjdk-amd64/include/linux HelloWorld.c -o libhello_world.so
java -Djava.library.path=. HelloWorld
  1. Jni 中通過 FindClass 獲取 Java HelloWorld 類對(duì)象
  2. Jni 中通過 GetMethodID 獲取 Java HelloWorld 類方法 helloWorldFromJava
  3. Jni 中通過 GetFieldID GetObjectField 獲取 Java HelloWorld 類變量 mField 的值
  4. Jni 中通過 CallVoidMethod 調(diào)用 helloWorldFromJava 方法葵诈,并傳入 mField 的值

本地引用

JNIEnv 提供的函數(shù)所返回的引用基本上都是本地引用,上一小節(jié)示例代碼中的 clazz 和 methodID祟同、fieldID 都屬于本地引用作喘。本地引用有如下特點(diǎn):

  1. 當(dāng) Native 函數(shù)返回時(shí),這個(gè)本地引用就會(huì)被自動(dòng)釋放晕城。
  2. 只在創(chuàng)建它的線程中有效泞坦,不能跨線程使用。
  3. 局部應(yīng)用是 JVM 負(fù)責(zé)的引用類型砖顷,受 JVM 管理贰锁。
    可以使用 DeleteLocalRef 等函數(shù)來手動(dòng)刪除本地引用,這些函數(shù)的使用場(chǎng)景主要是在 Native 函數(shù)返回前占用了大量?jī)?nèi)存择吊,需要立即刪除本地引用李根。
  env->DeleteLocalRef(clazz);
  env->DeleteLocalRef(fieldName);

其他的不作演示了

jclass jcls;//需要DeleteLocalRef
jobject jcls;//需要DeleteLocalRef
jstring jcls;//需要ReleaseStringUTFChars DeleteLocalRef
jarray jcls;//需要DeleteLocalRef
jmethodID jfieldID//不需要DeleteLocalRef

全局引用

全局引用和本地引用幾乎是相反的。它主要的特點(diǎn)有:

  1. 在 native 函數(shù)返回時(shí)不會(huì)被自動(dòng)釋放几睛,需要手動(dòng)進(jìn)行釋放房轿,并且不會(huì)被 GC 回收。
  2. 全局引用是可以跨線程使用的所森。
  3. 全局引用不受到 JVM 管理囱持。

JNIEnv 的 NewGlobalRef 函數(shù)用來創(chuàng)建全局引用,用 DeleteGlobalRef 函數(shù)刪除全局引用焕济。

  mGlobalClazz = static_cast<jclass>(env->NewGlobalRef(clazz)) ; //創(chuàng)建全局引用
  if (mGlobalClazz == NULL) {
    return;
  }
  env->DeleteGlobalRef(mGlobalClazz); // 釋放全局引用

弱全局引用

弱全局引用是一種特殊的全局引用纷妆,它可以被 GC 回收,弱全局引用被 GC 回收后會(huì)指向NULL晴弃。JNIEnv 的 NewWeakGlobalRef 用來創(chuàng)建一個(gè)弱全局引用掩幢,用 DeleteWeakGlobalRef 來釋放全局引用。

mClass = (jclass)env->NewWeakGlobalRef(clazz);
env->DeleteWeakGlobalRef(mClass);

由于可能被 GC 回收上鞠,因此在使用它之前先判斷它是否被回收了际邻,利用函數(shù) IsSameObject進(jìn)行判斷。

  if (env->IsSameObject(mGlobalClazz, NULL)){
      return;
  }

疑問

在編譯全局引用的時(shí)候芍阎,報(bào)錯(cuò)了世曾。

$g++ -shared -I/usr/lib/jvm/java-8-openjdk-amd64/include -I/usr/lib/jvm/java-8-openjdk-amd64/include/linux HelloWorld.c -o libhello_world.so
/usr/bin/ld: /tmp/cctLHX7X.o: relocation R_X86_64_PC32 against symbol `mGlobalClazz' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: 最后的鏈結(jié)失敗: bad value
collect2: error: ld returned 1 exit status

當(dāng)我為全局引用聲明為局部變量并賦值之后,再編譯就報(bào)錯(cuò)了谴咸,百度了下轮听,說加上編譯選項(xiàng) -fPIC 骗露,但依然不知道為什么會(huì)報(bào)錯(cuò)。如果誰明白血巍,可以留言萧锉,感謝。

參考

  1. http://liuwangshu.cn/framework/jni/2-signature-jnienv.html
  2. https://furzoom.blog.csdn.net/article/details/113730538
  3. https://blog.csdn.net/weixin_35671110/article/details/117596800
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末述寡,一起剝皮案震驚了整個(gè)濱河市驹暑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌辨赐,老刑警劉巖优俘,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異掀序,居然都是意外死亡帆焕,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門不恭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來叶雹,“玉大人,你說我怎么就攤上這事换吧≌刍蓿” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵沾瓦,是天一觀的道長(zhǎng)满着。 經(jīng)常有香客問我,道長(zhǎng)贯莺,這世上最難降的妖魔是什么风喇? 我笑而不...
    開封第一講書人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮缕探,結(jié)果婚禮上魂莫,老公的妹妹穿的比我還像新娘。我一直安慰自己爹耗,他們只是感情好耙考,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著潭兽,像睡著了一般倦始。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上讼溺,一...
    開封第一講書人閱讀 52,441評(píng)論 1 310
  • 那天楣号,我揣著相機(jī)與錄音最易,去河邊找鬼怒坯。 笑死炫狱,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的剔猿。 我是一名探鬼主播视译,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼归敬!你這毒婦竟也來了酷含?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤汪茧,失蹤者是張志新(化名)和其女友劉穎椅亚,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體舱污,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡呀舔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了扩灯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片媚赖。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖珠插,靈堂內(nèi)的尸體忽然破棺而出惧磺,到底是詐尸還是另有隱情,我是刑警寧澤捻撑,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布磨隘,位于F島的核電站,受9級(jí)特大地震影響顾患,放射性物質(zhì)發(fā)生泄漏琳拭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一描验、第九天 我趴在偏房一處隱蔽的房頂上張望白嘁。 院中可真熱鬧,春花似錦膘流、人聲如沸絮缅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)耕魄。三九已至,卻和暖如春彭谁,著一層夾襖步出監(jiān)牢的瞬間吸奴,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留则奥,地道東北人考润。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像读处,于是被迫代替她去往敵國(guó)和親糊治。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

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