如何高效的使用N-API開發(fā)Native模塊婆殿?

簡介

N-API 是 Node.js Addon Programming Interface 的縮寫诈乒,是 Node.js 提供的一組 C++ API,封裝了V8 引擎的能力婆芦,用于編寫 Node.js 的 Native 擴展模塊怕磨。通過 N-API喂饥,開發(fā)者可以使用 C++ 編寫高性能的 Node.js 模塊,同時保持與 Node.js 的兼容性肠鲫。

Node.js 官網(wǎng)中已經(jīng)給出 N-API 接口基礎(chǔ)能力的介紹员帮,同時,方舟 ArkTS 運行時提供的 N-API 接口导饲,封裝了方舟引擎的能力捞高,在功能上與 Node.js 社區(qū)保持一致,這里不再贅述渣锦。

本文將結(jié)合應(yīng)用開發(fā)場景硝岗,分別從對象生命周期管理、跨語言調(diào)用開銷袋毙、異步操作和線程安全四個角度出發(fā)型檀,給出安全、高效的 N-API 開發(fā)指導(dǎo)娄猫。

對象生命周期管理

在進行 N-API 調(diào)用時贱除,引擎堆中對象的句柄 handle 會作為 napi_value 返回,對象的生命周期由這些句柄控制媳溺。對象的句柄會與一個 scope 保持一致月幌,默認情況下,對象當(dāng)前所在 native 方法是 handle 的 scope悬蔽。在應(yīng)用 native 模塊實際開發(fā)過程中扯躺,需要對象有比當(dāng)前所在 native 方法更短或更長的 scope。本文描述了管理對象生命周期的 N-API 接口蝎困,開發(fā)者通過這些接口可以合理的管理對象生命周期录语,滿足業(yè)務(wù)訴求。

縮短對象生命周期

合理使用 napi_open_handle_scope 和 napi_close_handle_scope 管理 napi_value 的生命周期禾乘,做到生命周期最小化澎埠,避免發(fā)生內(nèi)存泄漏問題。

例如始藕,考慮一個具有 for 循環(huán)的方法蒲稳,在該循環(huán)中遍歷獲取大型數(shù)組的元素,示例代碼如下:

for (int i = 0; i < 1000000; i++) {
 napi_value result;
 napi_status status = napi_get_element(env, object, i, &result);
 if (status != napi_ok) {
  break;
 }
 // do something with element
}

在 for 循環(huán)中會創(chuàng)建大量的 handle伍派,消耗大量資源江耀。為了減小內(nèi)存開銷,N-API 提供創(chuàng)建局部 scope 的能力诉植,在局部 scope 中間所創(chuàng)建 handle 的生命周期將與局部 scpoe 保持一致祥国。一旦不再需要這些 handle,就可以直接關(guān)閉局部 scope晾腔。

  • 打開和關(guān)閉 scope 的方法為 napi_open_handle_scope 和 napi_close_handle_scope舌稀;
  • N-API 中 scope 的層次結(jié)構(gòu)是一個嵌套的層次結(jié)構(gòu)啊犬,任何時候只有一個存活的 scope,所有新創(chuàng)建的 handle 都將在該 scope 處于存活狀態(tài)時與之關(guān)聯(lián)扩借;
  • scope 必須按打開的相反順序關(guān)閉椒惨,在 native 方法中創(chuàng)建的所有 scope 必須在該方法返回之前關(guān)閉。

例如潮罪,使用下面的方法,可以確保在循環(huán)中领斥,最多只有一個句柄是有效的:

// 在for循環(huán)中頻繁調(diào)用napi接口創(chuàng)建js對象時嫉到,要加handle_scope及時釋放不再使用的資源;
// 下面例子中月洛,每次循環(huán)結(jié)束局部變量res的生命周期已結(jié)束何恶,因此加scope及時釋放其持有的js對象,防止內(nèi)存泄漏嚼黔。
for (int i = 0; i < 1000000; i++) {
    napi_handle_scope scope;
    napi_status status = napi_open_handle_scope(env, &scope);
    if (status != napi_ok) {
        break;
    }
    napi_value result;
    status = napi_get_element(env, object, i, &result);
    if (status != napi_ok) {
        break;
    }
    // do something with element
    status = napi_close_handle_scope(env, scope);
    if (status != napi_ok) {
        break;
    }
}

存在一些場景细层,某些對象的生命周期需要大于對象本身所在區(qū)域的生命周期,例如嵌套循環(huán)場景唬涧。開發(fā)者可以通過 napi_open_escapable_handle_scope 與 napi_close_escapable_handle_scope 管理對象的生命周期疫赎,在此期間定義的對象的生命周期將與父作用域的生命周期保持一致。

延長對象生命周期

開發(fā)者可以通過創(chuàng)建 napi_ref 來延長 napi_value 對象的生命周期碎节,通過 napi_create_reference 創(chuàng)建的對象需要用戶手動調(diào)用 napi_delete_reference 釋放捧搞,否則可能造成內(nèi)存泄漏。

使用案例1:保存 napi_value

通過 napi_define_class 創(chuàng)建一個 constructor 并保存下來狮荔,后續(xù)可以通過保存的 constructor 調(diào)用 napi_new_instance 來創(chuàng)建實例胎撇。但是,如果 constructor 是以 napi_value 的形式保存下來殖氏,一旦超過了 native 方法的 scope晚树,這個 constructor 就會被析構(gòu),后續(xù)再使用就會造成野指針雅采。推薦寫法如下:

  • 1爵憎、開發(fā)者可以改用 napi_ref 的形式把 constructor 保存下來;
  • 2、由開發(fā)者自己管理 constructor 對象的生命周期总滩,不受 native 方法的 scope 限制纲堵。
// 1、開發(fā)者可以改用 napi_ref 的形式把 constructor 保存下來
static napi_value TestDefineClass(napi_env env,
                                  napi_callback_info info) {
  napi_status status;
  napi_value result, return_value;

  napi_property_descriptor property_descriptor = {
    "TestDefineClass",
    NULL,
    TestDefineClass,
    NULL,
    NULL,
    NULL,
    napi_enumerable | napi_static,
    NULL};

  NODE_API_CALL(env, napi_create_object(env, &return_value));

  status = napi_define_class(NULL,
                             "TrackedFunction",
                             NAPI_AUTO_LENGTH,
                             TestDefineClass,
                             NULL,
                             1,
                             &property_descriptor,
                             &result);
  SaveConstructor(env, result);
  ...
}
// 2闰渔、由開發(fā)者自己管理 constructor 對象的生命周期
napi_status SaveConstructor(napi_env env, napi_value constructor) {
    return napi_create_reference(env, constructor, 1, &g_constructor);
};

napi_status GetConstructor(napi_env env) {
    napi_value constructor;
    return napi_get_reference_value(env, g_constructor, &constructor);
};

使用案例2:napi_wrap

開發(fā)者使用 napi_wrap 接口席函,可以將 native 對象和 js 對象綁定,當(dāng) js 對象被 GC 回收時冈涧,需要通過回調(diào)函數(shù)對 native 對象的資源進行清理茂附。napi_wrap 接口本質(zhì)上也是創(chuàng)建了一個 napi_ref正蛙,開發(fā)者可以根據(jù)業(yè)務(wù)需要,選擇由系統(tǒng)來管理創(chuàng)建的 napi_ref营曼,或是自行釋放創(chuàng)建的 napi_ref乒验。

// 用法1:napi_wrap不需要接收創(chuàng)建的napi_ref,最后一個參數(shù)傳遞nullptr蒂阱,創(chuàng)建的napi_ref由系統(tǒng)管理锻全,不需要用戶手動釋放
napi_wrap(env, jsobject, nativeObject, cb, nullptr, nullptr);

// 用法2:napi_wrap需要接收創(chuàng)建的napi_ref,最后一個參數(shù)不為nullptr录煤,返回的napi_ref需要用戶手動釋放鳄厌,否則會內(nèi)存泄漏
napi_ref result;
napi_wrap(env, jsobject, nativeObject, cb, nullptr, &result);
// 當(dāng)jsobject和result后續(xù)不再使用時,及時調(diào)用napi_remove_wrap釋放result
napi_value result1;
napi_remove_wrap(env, jsobject, result1)

跨語言調(diào)用開銷

接口調(diào)用

跨語言調(diào)用是指在一個程序中使用多種編程語言編寫的代碼妈踊,并且這些代碼可以相互調(diào)用和交互了嚎,ArkTS 調(diào)用 C++ 就是一種跨語言調(diào)用的方式。使用 N-API 進行函數(shù)調(diào)用會引入一定的開銷廊营,因為需要進行上下文切換歪泳、參數(shù)傳遞、函數(shù)調(diào)用和返回值處理等露筒,這些過程都涉及到一些性能開銷呐伞。目前,通過 N-API 接口實現(xiàn) ArkTS 調(diào)用 C++ 的場景大致分為三類:ArkTS 直接調(diào)用 C++ 接口邀窃、ArkTS 監(jiān)聽 C++ 接口以及 ArkTS 接收 C++ 回調(diào)荸哟。頻繁的跨語言接口調(diào)用可能會影響業(yè)務(wù)性能,因此需要開發(fā)者合理的設(shè)計接口調(diào)用頻率瞬捕。

數(shù)值轉(zhuǎn)換

使用 N-API 進行 ArkTS 與 C++ 之間的數(shù)據(jù)轉(zhuǎn)換鞍历,有如下建議:

  • 減少數(shù)據(jù)轉(zhuǎn)換次數(shù):頻繁的數(shù)據(jù)轉(zhuǎn)換可能會導(dǎo)致性能下降,可以通過批量處理數(shù)據(jù)或者使用更高效的數(shù)據(jù)結(jié)構(gòu)來優(yōu)化性能肪虎;
  • 避免不必要的數(shù)據(jù)復(fù)制:在進行數(shù)據(jù)轉(zhuǎn)換時劣砍,可以使用 N-API 提供的接口來直接訪問原始數(shù)據(jù),而不是創(chuàng)建新的數(shù)據(jù)副本扇救;
  • 使用緩存:如果某些數(shù)據(jù)在多次轉(zhuǎn)換中都會被使用到刑枝,可以考慮使用緩存來避免重復(fù)的數(shù)據(jù)轉(zhuǎn)換。緩存可以減少不必要的計算迅腔,提高性能装畅。

異步操作

對于IO、CPU密集型任務(wù)需要異步處理沧烈, 否則會造成主線程的阻塞掠兄。N-API 支持異步能力,允許應(yīng)用程序在執(zhí)行某個耗時任務(wù)時不會被阻塞,而是繼續(xù)執(zhí)行其他任務(wù)蚂夕。當(dāng)異步操作完成時迅诬,應(yīng)用程序會收到通知,并可以處理異步操作的結(jié)果婿牍。

異步示例

開發(fā)者可以通過如下示例將耗時任務(wù)用異步方式實現(xiàn)侈贷,大概邏輯包括以下三步:

  • 用 napi_create_promise 接口創(chuàng)建 promise,將創(chuàng)建一個 deferred 對象并與 promise 一起返回等脂,deferred 對象會綁定到已創(chuàng)建的 promise俏蛮;
  • 執(zhí)行耗時任務(wù),并將執(zhí)行結(jié)果傳遞給 promise慎菲;
  • 使用 napi_resolve_deferred 或 napi_reject_deffered 接口來 resolve 或 reject 創(chuàng)建的 promise嫁蛇,并釋放 deferred 對象。
// 在executeCB露该、completeCB之間傳遞數(shù)據(jù)
struct AddonData {
    napi_async_work asyncWork = nullptr;
    napi_deferred deferred = nullptr;
    napi_ref callback = nullptr;

    double args[2] = {0};
    double result = 0;
};

// 2、執(zhí)行耗時任務(wù)第煮,并將執(zhí)行結(jié)果傳遞給 promise解幼;
static void addExecuteCB(napi_env env, void *data) {
    AddonData *addonData = (AddonData *)data;
    addonData->result = addonData->args[0] + addonData->args[1];
};

// 3、使用 napi_resolve_deferred 或 napi_reject_deffered 接口來 resolve 或 reject 創(chuàng)建的 promise包警,并釋放 deferred 對象;
static void addPromiseCompleteCB(napi_env env, napi_status status, void *data) {
    AddonData *addonData = (AddonData *)data;
    napi_value result = nullptr;
    napi_create_double(env, addonData->result, &result);
    napi_resolve_deferred(env, addonData->deferred, result);

    if (addonData->callback != nullptr) {
        napi_delete_reference(env, addonData->callback);
    }

    // 刪除異步 work
    napi_delete_async_work(env, addonData->asyncWork);
    delete addonData;
    addonData = nullptr;
};

// 1撵摆、用 napi_create_promise 接口創(chuàng)建 promise,將創(chuàng)建一個 deferred 對象并與 promise 一起返回害晦,deferred
// 對象會綁定到已創(chuàng)建的 promise特铝;
static napi_value addPromise(napi_env env, napi_callback_info info) {
    size_t argc = 2;
    napi_value args[2];
    napi_value thisArg = nullptr;
    napi_get_cb_info(env, info, &argc, args, &thisArg, nullptr);

    napi_valuetype valuetype0;
    napi_typeof(env, args[0], &valuetype0);
    napi_valuetype valuetype1;
    napi_typeof(env, args[1], &valuetype1);
    if (valuetype0 != napi_number || valuetype1 != napi_number) {
        napi_throw_type_error(env, nullptr, "Wrong arguments. 2 numbers expected.");
        return NULL;
    }

    napi_value promise = nullptr;
    napi_deferred deferred = nullptr;
    napi_create_promise(env, &deferred, &promise);

    // 異步工作項上下文用戶數(shù)據(jù),傳遞到異步工作項的execute壹瘟、complete之間傳遞數(shù)據(jù)
    auto addonData = new AddonData{
        .asyncWork = nullptr,
        .deferred = deferred,
    };

    napi_get_value_double(env, args[0], &addonData->args[0]);
    napi_get_value_double(env, args[1], &addonData->args[1]);

    // 創(chuàng)建async work鲫剿,創(chuàng)建成功后通過最后一個參數(shù)(addonData->asyncWork)返回async work的handle
    napi_value resourceName = nullptr;
    napi_create_string_utf8(env, "addAsyncCallback", NAPI_AUTO_LENGTH, &resourceName);
    napi_create_async_work(env, nullptr, resourceName, addExecuteCB, addPromiseCompleteCB, (void *)addonData,
                           &addonData->asyncWork);

    // 將剛創(chuàng)建的async work加到隊列,由底層去調(diào)度執(zhí)行
    napi_queue_async_work(env, addonData->asyncWork);

    return promise;
}

在異步操作完成后稻轨,回調(diào)函數(shù)將被調(diào)用灵莲,并將結(jié)果傳遞給 Promise 對象。在 JavaScript 中殴俱,可以使用 Promise 對象的 then() 方法來處理異步操作的結(jié)果政冻。

import hilog from '@ohos.hilog';
import testNapi from 'libentry.so'

@Entry
@Component
struct TestAdd {
  build() {
    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
      Text("hello world")
        .onClick(() => {
          let num1 = 2;
          let num2 = 3;
          testNapi.addPromise(num1, num2).then((result) => {
            hilog.info(0x0000, 'testTag', '%{public}d', result);
          })
        })
    }
    .width('100%')
    .height('100%')
  }
}

指定異步任務(wù)調(diào)度優(yōu)先級

Function Flow 編程模型(Function Flow Runtime,F(xiàn)FRT)是一種基于任務(wù)和數(shù)據(jù)驅(qū)動的并發(fā)編程模型线欲,允許開發(fā)者通過任務(wù)及其依賴關(guān)系描述的方式進行應(yīng)用開發(fā)明场。方舟 ArkTS 運行時提供了擴展 qos 信息的接口,支持傳入 qos李丰,并調(diào)用 FFRT苦锨,根據(jù)系統(tǒng)資源使用情況降低功耗、提升性能。

  • 接口示例:napi_status napi_queue_async_work_with_qos(napi_env env, napi_async_work work, napi_qos_t qos)()

    • [in] env:調(diào)用API的環(huán)境逆屡;
    • [in] napi_async_work: 異步任務(wù)圾旨;
    • [in] napi_qos_t: qos 等級;
  • qos 等級定義:

typedef enum {
    napi_qos_background = 0,
    napi_qos_utility = 1,
    napi_qos_default = 2,
    napi_qos_user_initiated = 3,
} napi_qos_t;
  • N-API 層封裝了對外的接口魏蔗,對接 libuv 層 uv_queue_work_with_qos(uv_loop_t* loop, uv_work_t* req, uv_work_cb work_cb, uv_after_work_cb after_work_cb, uv_qos_t qos) 函數(shù)砍的。

  • 相較于已有接口 napi_queue_async_work,增加了 qos 等級莺治,用于控制任務(wù)調(diào)度的優(yōu)先級廓鞠。使用示例:

static void PromiseOnExec(napi_env env, void *data) { 
    OH_LOG_INFO(LOG_APP, "PromiseOnExec"); 
}

static void PromiseOnComplete(napi_env env, napi_status status, void *data) {
    int number = *((int *)data);
    OH_LOG_INFO(LOG_APP, "PromiseOnComplete number = %{public}d", number);
}

static napi_value Test(napi_env env, napi_callback_info info) {
    napi_value resourceName = nullptr;
    napi_create_string_utf8(env, "TestExample", NAPI_AUTO_LENGTH, &resourceName);
    napi_async_work async_work;
    int *data = new int(10);
    napi_create_async_work(env, nullptr, resourceName, PromiseOnExec, PromiseOnComplete, data, &async_work);
    napi_queue_async_work_with_qos(env, async_work, napi_qos_default);
    return nullptr;
}

線程安全

如果應(yīng)用需要進行大量的計算或者 IO 操作,使用并發(fā)機制可以充分利用多核 CPU 的優(yōu)勢谣旁,提高應(yīng)用的處理效率床佳。例如,圖像處理榄审、視頻編碼砌们、數(shù)據(jù)分析等應(yīng)用可以使用并發(fā)機制來提高處理速度。

雖然 N-API 本身不支持多線程并發(fā)操作搁进,但是可以在多線程環(huán)境下進行一些數(shù)據(jù)交互浪感,且需要格外注意線程安全眯漩。在多線程環(huán)境下擎值,開發(fā)者可以使用 napi_create_threadsafe_function 函數(shù)創(chuàng)建一個線程安全函數(shù),然后在任意線程中調(diào)用舰始。應(yīng)用場景:當(dāng) native 側(cè)有其他線程莱革,并且需要根據(jù)這些線程的完成結(jié)果調(diào)用 JavaScript 函數(shù)時峻堰,這些線程必須與 native 側(cè)的主線程進行通信,才能在主線程中調(diào)用 JavaScript 函數(shù)盅视。線程安全函數(shù)便提供了一種簡化方法捐名,避免了線程間通訊,同時可以回到主線程調(diào)用 JavaScript 函數(shù)左冬。

使用方法

ArkTS 側(cè)傳入回調(diào)函數(shù)

struct Index {
  @State message: string = 'Hello World'

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            testNapi.threadSafeTest((value) => {
              hilog.info(0x0000, 'testTag', 'js callback value = ' + value);
            })
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

native 側(cè)主線程中創(chuàng)建線程安全函數(shù)

static void CallJs(napi_env env, napi_value js_cb, void *context, void *data) {

    std::thread::id this_id = std::this_thread::get_id();
    OH_LOG_INFO(LOG_APP, "thread CallJs %{public}d.\n", this_id);
    napi_status status;

    status = napi_get_reference_value(env, cbObj, &js_cb);

    napi_valuetype valueType = napi_undefined;
    napi_typeof(env, js_cb, &valueType);
    OH_LOG_INFO(LOG_APP, "CallJs js_cb is napi_function: %{public}d", valueType == napi_function);

    OH_LOG_INFO(LOG_APP, "CallJs 0");
    if (env != NULL) {
        napi_value undefined, js_the_prime;
        status = napi_create_int32(env, 666, &js_the_prime);
        OH_LOG_INFO(LOG_APP, "CallJs 1: %{public}d", status == napi_ok);
        status = napi_get_undefined(env, &undefined);
        OH_LOG_INFO(LOG_APP, "CallJs 2: %{public}d", status == napi_ok);

        napi_value ret;

        status = napi_call_function(env, undefined, js_cb, 1, &js_the_prime, &ret);
        OH_LOG_INFO(LOG_APP, "CallJs 3: %{public}d", status == napi_ok);
    }
}

napi_threadsafe_function tsfn;

static napi_value ThreadSafeTest(napi_env env, napi_callback_info info) {
    size_t argc = 1;
    napi_value js_cb, work_name;
    napi_status status;

    status = napi_get_cb_info(env, info, &argc, &js_cb, NULL, NULL);
    OH_LOG_INFO(LOG_APP, "ThreadSafeTest 0: %{public}d", status == napi_ok);

    status = napi_create_reference(env, js_cb, 1, &cbObj);
    OH_LOG_INFO(LOG_APP, "napi_create_reference of js_cb to cbObj: %{public}d", status == napi_ok);

    status =
        napi_create_string_utf8(env, "Node-API Thread-safe Call from Async Work Item", NAPI_AUTO_LENGTH, &work_name);
    OH_LOG_INFO(LOG_APP, "ThreadSafeTest 1: %{public}d", status == napi_ok);

    std::thread::id this_id = std::this_thread::get_id();
    OH_LOG_INFO(LOG_APP, "thread ThreadSafeTest %{public}d.\n", this_id);

    napi_valuetype valueType = napi_undefined;
    napi_typeof(env, js_cb, &valueType);
    OH_LOG_INFO(LOG_APP, "ThreadSafeTest js_cb is napi_function: %{public}d", valueType == napi_function);

    status = napi_create_threadsafe_function(env, js_cb, NULL, work_name, 0, 1, NULL, NULL, NULL, CallJs, &tsfn);
    OH_LOG_INFO(LOG_APP, "ThreadSafeTest 2: %{public}d", status == napi_ok);
}

其他線程中調(diào)用線程安全函數(shù)

std::thread t([]() {
    std::thread::id this_id = std::this_thread::get_id();
    OH_LOG_INFO(LOG_APP, "thread0 %{public}d.\n", this_id);
    napi_status status;
    status = napi_acquire_threadsafe_function(tsfn);
    OH_LOG_INFO(LOG_APP, "thread1 : %{public}d", status == napi_ok);
    status = napi_call_threadsafe_function(tsfn, NULL, napi_tsfn_blocking);
    OH_LOG_INFO(LOG_APP, "thread2 : %{public}d", status == napi_ok);
});
t.detach();

線程函數(shù)使用注意事項

在多線程環(huán)境下桐筏,需要避免使用共享的數(shù)據(jù)結(jié)構(gòu)和全局變量,以免競爭和沖突拇砰。同時梅忌,需要確保線程之間的同步和互斥,以避免數(shù)據(jù)不一致的情況發(fā)生除破。除此之外牧氮,仍需注意:

  • 對線程安全函數(shù)的調(diào)用是異步進行的,對 JavaScript 回調(diào)的調(diào)用將被放置在任務(wù)隊列中瑰枫;
  • 創(chuàng)建 napi_threadsafe_function 時踱葛,可以提供 napi_finalize 回調(diào)丹莲。當(dāng)線程安全函數(shù)即將被銷毀時,將在主線程上調(diào)用此 napi_finalize 回調(diào)尸诽;
  • 在調(diào)用 napi_create_threadsafe_function 時給定了上下文甥材,可以從任何調(diào)用 napi_get_threadafe_function_context 的線程中獲取。

寫在最后

如果你覺得這篇內(nèi)容對你還蠻有幫助性含,我想邀請你幫我三個小忙

  • 點贊洲赵,轉(zhuǎn)發(fā),有你們的 『點贊和評論』商蕴,才是我創(chuàng)造的動力叠萍。
  • 關(guān)注小編,同時可以期待后續(xù)文章ing??绪商,不定期分享原創(chuàng)知識苛谷。
  • 想要獲取更多完整鴻蒙最新學(xué)習(xí)知識點,請移步前往小編:https://gitee.com/MNxiaona/733GH/blob/master/jianshu
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末格郁,一起剝皮案震驚了整個濱河市腹殿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌例书,老刑警劉巖赫蛇,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異雾叭,居然都是意外死亡,警方通過查閱死者的電腦和手機落蝙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進店門织狐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人筏勒,你說我怎么就攤上這事移迫。” “怎么了管行?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵厨埋,是天一觀的道長。 經(jīng)常有香客問我捐顷,道長荡陷,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任迅涮,我火速辦了婚禮废赞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘叮姑。我一直安慰自己唉地,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著耘沼,像睡著了一般极颓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上群嗤,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天菠隆,我揣著相機與錄音,去河邊找鬼骚烧。 笑死浸赫,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的赃绊。 我是一名探鬼主播既峡,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼碧查!你這毒婦竟也來了运敢?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤忠售,失蹤者是張志新(化名)和其女友劉穎传惠,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體稻扬,經(jīng)...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡卦方,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了泰佳。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盼砍。...
    茶點故事閱讀 40,742評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖逝她,靈堂內(nèi)的尸體忽然破棺而出浇坐,到底是詐尸還是另有隱情,我是刑警寧澤黔宛,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布近刘,位于F島的核電站,受9級特大地震影響臀晃,放射性物質(zhì)發(fā)生泄漏觉渴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一积仗、第九天 我趴在偏房一處隱蔽的房頂上張望疆拘。 院中可真熱鬧,春花似錦寂曹、人聲如沸哎迄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽漱挚。三九已至翔烁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間旨涝,已是汗流浹背蹬屹。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留白华,地道東北人慨默。 一個月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像弧腥,于是被迫代替她去往敵國和親厦取。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,747評論 2 361

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