【轉】網易云音樂 RN 新架構升級實踐

本文作者:王永亮

本文介紹了從 RN 新架構源碼實現(xiàn)角度出發(fā)涩金,介紹了如何升級適配,以及網易云音樂在升級適配時遇到的問題及解決方案暇仲。

一步做、背景

網易云音樂從 ReactNative 0.33 版本開始接入,在 2019 年時開始把 RN 作為主要跨端方案進行建設奈附,并從 0.33 升級到了 0.60全度,升級 0.60 時還只有十幾個頁面使用 RN 開發(fā),時至 2022 年底已經有 100+ 業(yè)務模塊使用 RN 開發(fā)斥滤,對應 100+ RN 項目将鸵,P0 級別項目占比超過四分之一勉盅。云音樂 RN 已經擁有完善的組件庫和自定義協(xié)議庫,并且建設了從開發(fā)腳手架咨堤、一站式開發(fā)平臺菇篡、分發(fā)服務、端側定制容器以及監(jiān)控體系等一系列的配套設施一喘。

雖然使用 RN 開發(fā)的頁面越來越多驱还,但受限于 JS 的解釋執(zhí)行速度以及 RN 的多線程通信架構等問題,RN 頁面在啟動性能和某些交互體驗上和純原生仍有一些差距凸克,特別是核心場景议蟆,業(yè)務對頁面打開效率和使用體驗特別的敏感,這也導致跨端使用場景進一步擴大受阻萎战。為了突破性能瓶頸咐容,我們在橫向對比了業(yè)界各主流 App 上使用的動態(tài)化跨端方案,同時考慮基于當前 RN 版本進行改造和引擎替換等方案蚂维,最終結合云音樂自身的特點和已有基建情況我們選選擇了 ReactNative 新架構升級方案戳粒。本文主要介紹網易云音樂升級 RN 新架構遇到的一些問題和解決方案,希望給想要升級的同學提供一些可參考的信息虫啥。

二蔚约、項目思路和方案

新架構調研

升級前我們首先使用 Demo 對新老版本從性能、Bundle 包大小涂籽、客戶端包大小苹祟、內存占用等多角度進行了詳細的數(shù)據對比,測試機型為 iOS iPhone 6 iOS 12.5评雌、iPhone 12 iOS 16.1树枫,Android 小米8 SE 和紅米 Note 9 Pro,測試環(huán)境為 RN 0.60 版本 + JavaScriptCore 引擎和 RN 0.70 版本 + Hermes + 字節(jié)碼預編譯景东,詳細對比信息如下:

性能對比

經過對比砂轻,使用新架構 + Hermes 引擎 + 預編譯后 Android 小米8 SE 首幀提升 71.5%,LCP 提升 40.1%斤吐;紅米 Note 9 pro 首幀提升 77.3%舔清,LCP 提升 41.9%;iPhone 6 首幀耗時提升 63%曲初,iPhone 12 提升 42%;LCP iPhone 6 提升 48.5%杯聚,iPhone 12 提升 18.3%臼婆,詳細數(shù)據如下表:

首幀時間:

版本 iPhone 6 iPhone 12 小米 8 SE 紅米 Note 9 pro
RN 0.60 1563.66ms 189.66ms 987.2ms 743.4ms
RN 0.70 578.66ms 110ms 281ms 168.4ms

LCP 時間:

版本 iPhone 6 iPhone 12 小米 8 SE 紅米 Note 9 pro
RN 0.60 2482.2ms 886.5ms 1720ms 1358.6ms
RN 0.70 1276.6ms 724.25ms 1030.2ms 788.4ms
離線包大小影響

Hermes 引擎的一大優(yōu)勢是預編譯和字節(jié)碼執(zhí)行能力,但是將 JS 文本編譯成字節(jié)碼是有額外成本的幌绍,編譯后相比編譯前 demo 的 bundle 包的大小壓縮前增加 18.1%颁褂,ZIP 壓縮后相比于非字節(jié)碼 ZIP 后增加了 57.6%故响,根據我們后續(xù)實際打字節(jié)碼包的經驗,JS Bundle 在字節(jié)碼預編譯后 ZIP 包會有 40% ~ 100% 不等的增加颁独,在網絡狀態(tài)差的情況對離線包的到達會有一定的影響彩届,需要采取一些優(yōu)化措施, demo 詳細數(shù)據如下:

是否壓縮 bundle大小 bundle(bytecode)大小
ZIP前 2.7M 3.3M
ZIP后 623kb 1.4M
客戶端包大小影響

iOS新版本不引入 hermes.framework 時 IPA 包大小為 1.1MB誓酒,引入后為 3.1MB樟蠕,增加了 2MB 包大小。
Android新版本依賴大小 6.12M靠柑,老版本 6.14M寨辩,影響較小

內存占用影響

使用 Demo 驗證在內存(包含 App 本身的內存使用)使用上,RN 0.70 也比 RN 0.60 也有明顯優(yōu)化歼冰,iOS 新版本相比老版本內存占用減少了 50% 左右靡狞,Android 小米8 SE 內存占用減少 33.2%,紅米 Note 9 Pro 內存占用減少31%隔嫡,具體數(shù)據如下:

  • iOS
版本 iPhone 6 iPhone 12
RN 0.60 42.2MB 47.4MB
RN 0.70 21.4MB 25.7MB
  • Android
版本 小米 8 SE 紅米 Note 9 Pro
RN 0.60 208.4MB 223.1MB
RN 0.70 139MB 153.9MB
其他影響

我們對通信耗時甸怕、長列表場景幀率和頁面交互能力等場景也進行了 demo 驗證,使用 TurboModule腮恩、FabricComponent 后通信性能有了 50% 以上的提升梢杭,長列表場景幀率無明顯變化,頁面交互能力和 Native 基本持平庆揪。

綜上調研結果式曲,RN 0.70 新架構 + Hermes 引擎 + 字節(jié)碼預編譯開啟在各個方面上表現(xiàn)都要優(yōu)于云音樂之前使用的 RN 0.60 + JavaScriptCore 引擎。

新架構適配

RN 新架構的核心主要有三方面的優(yōu)化 —— Fabric缸榛、TurboModule 和 Hermes吝羞,分別對應組件渲染、信息通信和執(zhí)行引擎内颗,三項優(yōu)化都可以獨立開啟和關閉钧排,接入復雜度上 Hermes 接入適配成本相對最低;Fabric 和 TurboModule 都需要進行代碼改造適配后才能啟用均澳,TurboModule 開啟后 NativeModule 仍然可以使用恨溜,改造成本適中,F(xiàn)abric 的開啟最為復雜找前,由于 Fabric 開啟后只支持渲染 FabricComponent糟袁,所以需要將原來的 NativeComponent 全部改造為 FabricComponent 才能使用,F(xiàn)abric 在三者中適配成本最高躺盛。

這里從 Android 端的角度介紹下新架構的基本原理和適配:

Hermes 升級適配

Hermes 在 0.70 版本時開始被作為雙端默認的 JavaScript 引擎项戴,Hermes 引擎最大的優(yōu)勢是支持預編譯能力,預編譯將原本在端上解釋執(zhí)行時進行的抽象語法樹解析槽惫、詞法解析周叮、以及各種編譯優(yōu)化放到了打包時辩撑,直接輸出執(zhí)行效率更高的字節(jié)碼,具體原理可以參考官方圖:


Hermes 支持執(zhí)行純文本 JS Bundle 和 JS Bundle 預編譯后的字節(jié)碼文件(HBC 文件)仿耽,純文本執(zhí)行性能相比于其他引擎性能降低明顯合冀,但是執(zhí)行預編譯后的二進制文件時性能可以說有了質的提升,尤其是在 Android 系統(tǒng)上项贺,比較直觀的體現(xiàn)是 JS Bundle 預編譯成字節(jié)碼后頁面首屏渲染速度的顯著提升君躺。
但是也帶來了一些副作用,首先是 JS Bundle 預編譯為二進制后體積增加 50% 以上敬扛,另外一個問題是 JS Bundle 預編譯為字節(jié)碼后使用 bsdiff 打出的差量包的大小相比于原來純文本的 diff 包增加了 80% 以上晰洒,從幾 kb、幾十 kb 增加到了上百 kb啥箭,在一些弱網等場景包大小的增大可能會直接帶來離線包下載失敗率的提升谍珊。對于 diff 包增大的問題經過排查我們發(fā)現(xiàn)在打字節(jié)碼包時增加 -base-bytecode 指令可以降低 diff 包的大小,指令如下:

hermes -emit-binary -out bundle.hbc -base-bytecode bundle.hbc

原理可以參考 hermes 的 issue急侥,這里不得不吐槽下 Hermes 官方文檔實在是內容太少了砌滞,沒有對這方面內容的說明。

對于打字節(jié)碼后包增大的問題坏怪,雖然對大部分場景用戶都可以通過差量包進行升級贝润,但是對于新用戶和剛剛升級到 RN 0.70 的用戶還是需要全量拉取的,為了解決這個問題我們對字節(jié)碼離線包進行了剪裁和引入了新的壓縮算法铝宵。

使用 Hermes 引擎后 JS 代碼打包時會經過混淆打掘、壓縮和預編譯等步驟,在之前文本打包的基礎上鹏秋,字節(jié)碼預編譯后會生成 HBC SourceMap 來關聯(lián)字節(jié)碼和 JS SourceMap尊蚁,HBC SourceMap 大小在非 ZIP 情況下可以占到 HBC 包大小的 30% 左右,HBC SourceMap 主要作用是字節(jié)碼執(zhí)行出現(xiàn)異常時將字節(jié)碼堆棧還原為純文本堆棧侣夷,在運行時不需要使用横朋,所以我們在打包時把 HBC SourceMap 從 HBC 包中移除并上傳到了云存儲,在異常監(jiān)控平臺解析堆棧使用時直接從云存儲獲取百拓,通過 HBC 包的剪裁壓縮后包大小可以縮小 10% ~ 20%琴锭。

另外經過調研和對比還引入了 XZ 壓縮算法,XZ 壓縮算法有更高的壓縮比衙传,相比于 gzip 壓縮比提升 10% 以上决帖,但是壓縮時間和解壓縮時間都增加了幾十倍,壓縮由于發(fā)生在打包時時長增加可以忽略不計蓖捶,在中低端手機上測試解壓縮時間從原來的 0.0x 毫秒上升到了幾毫秒古瓤,時間增加完全可以接受。

經過兩項優(yōu)化后離線包整體大小縮小 30% 以上。

TurboModule 升級適配

TurboModule 提供了 JS 同步調用客戶端代碼的能力落君,原理上是以 C++ 代碼作為橋梁實現(xiàn)不同語言間的通信,通過 JSI 和 JNI 實現(xiàn)跨語言的通信亭引,代碼中利用 JSI 能力在 C++ 代碼中向 JSRuntime 注入了 “__turboModuleProxy”绎速,通過 ”__turboModuleProxy“ JS 可以直接調用到 C++奕锌,C++ 則通過框架初始化時使用 JNI 注入的 TurboModuleManager Java 對象的引用獲取 TurboModule Java 層實現(xiàn)本谜,最后通過 Java 層獲取到 C++ 層方法映射完成 TurboModule 的獲取。

TurboModule 需要通過 Codegen 來生成讶踪,具體方法可以參考官方文檔购公,使用 Codegen 生成的 TurboModule 包含 Java 代碼和 C++ 代碼兩部分萌京,C++ 代碼中維護了當前 TurboModule JS 到 C++ 方法的映射,以及對實際實現(xiàn) TurboModule 的 Java 對象的引用宏浩,最終調用 Java 層 TurboModule 方法時則通過 JavaTurboModule 的 invokeJavaMethod 統(tǒng)一中轉到 Java 層知残,這里需要注意的是如果 TurboModule 中定義的方法如果返回值是 void 類型,則會自動轉為異步調用方式比庄,相關代碼如下:

case VoidKind: {
  TMPL::asyncMethodCallArgConversionEnd(moduleName, methodName);
  TMPL::asyncMethodCallDispatch(moduleName, methodName);

  nativeInvoker_->invokeAsync(
      // 具體方法實現(xiàn)
      );

  TMPL::asyncMethodCallEnd(moduleName, methodName);
  return jsi::Value::undefined();
}

改造為 TurboModule 后求妹,如果需要使用同步方法,則函數(shù)定義的返回值也需要改為非 Void佳窑。在 RN 新架構中雖然新增了 TurboModule制恍,但是之前的 NativeModule 也還是可以使用的,并且新增的 TurboModule 也是向前兼容的神凑,所以云音樂的做法是先將頻繁使用的 NativeModule 改造為 TurboModule净神,降低改造成本和前端適配的成本。

Fabric 升級適配

Fabric 對渲染系統(tǒng)進行了重構溉委,重構后渲染系統(tǒng)分為渲染鹃唯、提交、掛載三個階段薛躬,渲染階段主要是運行 JS 渲染邏輯俯渤,為每個通過 React Fiber 框架計算生成的 Element 節(jié)點創(chuàng)建對應的 C++ 影子樹節(jié)點(shadowNode),提交階段使用 Yoga 引擎對前一階段生成的影子樹(ShadowTree)進行布局計算型宝,掛載階段在客戶端 UI 線程中將計算好的布局信息和來自于 JS 的樣式信息解析為客戶端的視圖樹八匠。新的渲染系統(tǒng)將影子樹邏輯和相對應的 Yoga 布局計算直接放在了 C++ 中,優(yōu)化掉了原來 Java 代碼中的影子樹和不必要的 YogaJNI 調用趴酣,提升了數(shù)據傳輸?shù)男世媸鳎w架構如下圖:

image.png

Fabric 的適配成本相對來說還是比較高的,和 TurboModule 不同岖寞,由于代碼中 UIManager 和 FabricUIManager 只能二選一抡四,所以在一個 RN 應用中開啟 Fabric 需要將這個 RN 應用依賴的所有 NativeComponent 都改造為 FabricComponent,否則在頁面上使用該組件的位置會展示一個未實現(xiàn)組件的提示,F(xiàn)abricComponent 需要使用官方提供的 Codegen 工具生成指巡,這里除了自研組件需要適配淑履,依賴的社區(qū)開源的組件也需要升級和改造,這里依賴組件過多改造成本高的話也可以選擇分頁面逐步遷移以降低開發(fā)成本藻雪。

前端代碼適配

RN 升級除了客戶端 RN SDK 升級秘噪、NativeModule、NativeComponent 的改造外勉耀,前端的兼容適配也有比較大的工作量指煎,首先要解決的是 RN 本身迭代導致的變更,跨越 0.60便斥、0.70 版本不少改動和優(yōu)化需要適配至壤,另外就是新架構的 API 變更,開啟 Fabric 后一些老的 API (如 findNodeHandle枢纠、setNativeProps 等) 已無法使用像街,API 遷移可以參考官方文檔說明,這些 API 使用非常廣泛京郑,除了業(yè)務源碼中使用外宅广、我們自己開發(fā)的二方組件、社區(qū)的三方組件都需要進行適配或者更新些举,部分三方常用組件還沒有新架構的適配版本需要我們自行進行適配跟狱,為了盡快完成升級工作,我們選擇了先臨時對三方庫進行私有化户魏,在私有化基礎上進行適配改造驶臊,穩(wěn)定性驗證完畢后可以回饋給社區(qū),這也帶來了另一個問題叼丑,常用三方庫除了直接在業(yè)務代碼中依賴在其他三方庫中也可能被依賴关翎,這樣就造成了私有化的連鎖反應,為了解決這個問題我們通過 alias 方式避免依賴膨脹鸠信,后續(xù)會有前端篇文章專門來介紹纵寝。

云音樂升級實戰(zhàn)

對于云音樂來說,RN 新架構升級這要有兩個問題:

1. 兼容成本高星立。 升級新架構爽茴,除了客戶端之前的 NativeModule、FabricComponent 需要升級適配外绰垂,還要對在云音樂中已存在 100+ RN 應用進行逐個適配回歸室奏,這里面還包含一些營收廣告之類的重要頁面,回歸和上線都需要格外謹慎劲装。

2. 新架構不確定性高胧沫,穩(wěn)定性風險大昌简。 項目開始時距離 RN 0.70 版本發(fā)布只過了 3 個月的時間,還沒有有關大型 App 對新架構的使用和穩(wěn)定性情況的消息绒怨,另外在老架構中我們就遇到一些 JSC 相關的出現(xiàn)概率不低偶現(xiàn) Crash纯赎,新架構擔心會有相同問題。

針對以上問題我們調研制定了比較穩(wěn)健的升級上線方案南蹂,主要涉及工作如下圖:

其中主要工作還是圍繞降低升級成本和穩(wěn)定性保障兩個方面:

降低升級成本
  • 自動化腳本減少適配工作量

在整個 RN 升級工作中工作量占比最高的就是業(yè)務的適配和回歸工作址否,在沒有遇到疑難問題的情況下熟手適配一個應用需要 0.5d,100+ 應用理想情況下預估完全適配完成可能需要 2~3 個月的時間碎紊,適配同時還需要兼顧各業(yè)務線的迭代排期,可能進一步拉長項目時間樊诺,并且已有頁面還在不斷的迭代中仗考,時間越長適配成本就會越高,后期項目可能會失去掌控词爬。
針對以上問題秃嗜,我們經過分析發(fā)現(xiàn)除了少量 API 升級后參數(shù)出現(xiàn)變更,很難通過自動化改造外顿膨,大部分情況可以將改造點收斂到依賴中锅锨,升級依賴即可完成版本升級,所以針對這個特點我們實現(xiàn)了自動化升級腳本恋沃,大部分升級工作通過執(zhí)行腳本完成必搞,只有少量 API 改造和升級出現(xiàn)的 UI 適配問題需要投入人力,實際每個應用適配工作量縮短到 1h~2h 左右囊咏,整體升級成本大大降低恕洲。

  • RN新架構源碼改造,降低改造成本

新架構中客戶端一項重要的工作就是 NativeModule 和 FabricComponent 的改造梅割,這塊在新架構適配部分也有介紹霜第,對于 NativeModule 我們選擇了對部分高頻使用的 Module 進行改造,比如我們的自定義協(xié)議傳輸?shù)?Module户辞,幾乎所有業(yè)務的 JS 和 客戶端通信都需要通過這里泌类,這個 Module 改造完已經解決了大部分問題,對于 FabricComponent 我們通過分析源碼發(fā)現(xiàn)雖然新架構源碼中 FabricUIManager 必須使用 FabricComponent底燎,但是仍然可以通過修改源碼進行兼容刃榨,通過 Codegen 生成的 C++ 代碼當前版本的主要作用是實現(xiàn) Props 的類型定義,真正執(zhí)行時還是會通過 TS 中的 RawProps 來操作需要變更的屬性书蚪。所以最終我們通過更改 ComponentDescriptorRegistry.cpp 和 SurfaceMountingManager.java 的查找邏輯實現(xiàn)了兼容喇澡,重點代碼如下:

ComponentDescriptorRegistry.cpp:


SurfaceMountingManager.java:

通過該更改節(jié)約了大量的自研組件升級帶來的工作量。

  • 新老版本一套代碼殊校、一次打包即可同時上線新老 RN 版本客戶端

對于已經存在 100+ RN 項目的大型 App晴玖,RN 新架構升級這種量級的改動是無法直接線上全量的,需要通過 Android 分流、iOS AB 切換的方式逐步放量呕屎,放量時間短則一兩個迭代让簿,長則可能到一兩個月,這時 RN 頁面日常迭代發(fā)布就需要考慮 RN 0.60 和 RN 0.70 同時兼容的問題秀睛,對此我們設計了一套代碼出雙包的方案尔当,使用該方案業(yè)務更改后,一套代碼一次打包可以同時發(fā)布運行在線上使用 RN 0.60 版本的客戶端蹂安,以及線上使用 RN 0.70 版本的客戶端椭迎,整體方案通過自動化升級腳本和 RN 打包腳本改造實現(xiàn),盡量做到具體業(yè)務最小的開發(fā)適配成本田盈,改造后整體架構如下:

相對應的開發(fā)調試流程也需要相應的變化:

在打包發(fā)布平臺上兼容模式是可配置的畜号,RN 70 全量后,對于一些如營收相關的頁面線上存量的 RN 0.60 版本客戶端也非常重要允瞧,集成在 RN 0.60 版本客戶端上的頁面需要持續(xù)的維護简软,直到 RN 0.70 版本覆蓋率到達到一定程度,到時才可以放棄少量的存量版本述暂,這種情況可以一直保持 RN 0.60 版本和 RN 0.70 版本的配置痹升,對于大部分應用在RN 0.70 全量后 RN 0.60 的兼容包就不需要再維護,則去掉打 RN 0.60 兼容包的配置即可畦韭。

穩(wěn)定性保障

RN 升級需要適配頁面眾多疼蛾,改造成本極大,新架構還帶來了很多的不確定性廊驼,對此我們做了非常多的工作進行穩(wěn)定性保障据过,RN 升級上線后做到了 0 線上問題。

  • 源碼改造妒挎,新增 Hermes绳锅、FabricComponent、TurboModule 降級能力

RN 新架構中 Hermes 引擎酝掩、FabricComponent 和 TurboModule 都還是比較新的東西鳞芙,為避免出現(xiàn)線上問題,我們對 Hermes期虾、Fabric原朝、TurboModule 都增加了動態(tài)降級能力,通過配置的實時下發(fā)隨時可以切換到降級模式镶苞,避免異常突增或某些業(yè)務突現(xiàn) bug 造成諸如資損等嚴重問題喳坠。

  • iOS 雙動態(tài)庫方案,實現(xiàn) AB 階梯放量

在 RN 0.70 版本中茂蚓,新架構和引擎都存在非常大的不確定性壕鹉,根據我們之前的 RN 使用經驗剃幌,老版本中Android JavaScriptCore 引擎在開發(fā)測試期間都比較穩(wěn)定,但是上線后會有一些出現(xiàn)概率不低的引擎?zhèn)犬惓A涝。琲OS 切換為 Hermes 后很可能有相同問題负乡,所以要提前設計好上線的方式和節(jié)奏,尤其是在 iOS 系統(tǒng)上脊凰,由于蘋果應用市場的限制抖棘,幾乎不可能做到和 Android 一樣的灰度和逐個應用市場放量的能力,所以為了避免風險狸涌,我們設計了 RN 0.60 和 RN 0.70 版本雙動態(tài)庫方案切省,即保證了穩(wěn)定性,又為 AB 數(shù)據實驗做好了準備帕胆,詳細實現(xiàn)可以關注后續(xù)文章数尿。

三、升級收益

  • 性能提升
    升級后頁面線上性能數(shù)據普遍提升惶楼,首次渲染白屏有效解決,低端機提升尤其顯著

升級后 RN 頁面 JS 渲染執(zhí)行時的 loading 展示時間已完全不可見诊杆,除了體感的提升外歼捐,我們還對線上的性能數(shù)據進行了全面的統(tǒng)計,統(tǒng)計結果中各項性能數(shù)據均顯著提升晨汹,其中 Android 最大元素渲染完成時間(LCP)提升 20%~50%豹储,iOS LCP提升 10%~20%,在 Android淘这、iOS 低端機上提升都更加顯著剥扣,RN 升級后頁面 LCP 時間基本都可以做到 1s 內,做到了頁面秒開铝穷。

  • 穩(wěn)定性提升
    升級后客戶端各項穩(wěn)定性數(shù)據均有提升

RN 升級后 Android 端穩(wěn)定性得到顯著提升钠怯,JavaSriptCore 引擎偶現(xiàn)崩潰得以解決,Hermes 偶現(xiàn)萬分位異常曙聂,引擎帶來的穩(wěn)定性問題基本已被解決晦炊,由于新架構新引擎的內存占用減低,一些內存不足引起的異常也減少很多宁脊。

相關鏈接:
https://reactnative.dev/docs/next/new-architecture-intro
https://reactnative.cn/architecture/fabric-renderer

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末断国,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子榆苞,更是在濱河造成了極大的恐慌稳衬,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坐漏,死亡現(xiàn)場離奇詭異薄疚,居然都是意外死亡碧信,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門输涕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來音婶,“玉大人,你說我怎么就攤上這事莱坎∫率剑” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵檐什,是天一觀的道長乃正。 經常有香客問我瓮具,道長,這世上最難降的妖魔是什么叹阔? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任耳幢,我火速辦了婚禮欧啤,結果婚禮上邢隧,老公的妹妹穿的比我還像新娘倒慧。我一直安慰自己,他們只是感情好院峡,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布照激。 她就那樣靜靜地躺著俩垃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪口柳。 梳的紋絲不亂的頭發(fā)上跃闹,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天苛秕,我揣著相機與錄音找默,去河邊找鬼惩激。 笑死,一個胖子當著我的面吹牛浅缸,可吹牛的內容都是我干的。 我是一名探鬼主播蚌父,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼喝滞,長吁一口氣:“原來是場噩夢啊……” “哼右遭!你這毒婦竟也來了缤削?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎帅刀,沒想到半個月后远剩,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瓜晤,經...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡痢掠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年志群,在試婚紗的時候發(fā)現(xiàn)自己被綠了锌云。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片桑涎。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡攻冷,死狀恐怖遍希,靈堂內的尸體忽然破棺而出凿蒜,到底是詐尸還是另有隱情废封,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站刽漂,受9級特大地震影響贝咙,放射性物質發(fā)生泄漏颈畸。R本人自食惡果不足惜没讲,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望嘁信。 院中可真熱鬧疏叨,春花似錦蚤蔓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽昏苏。三九已至,卻和暖如春洼专,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背句柠。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工精盅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留谜酒,地道東北人僻族。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像愕掏,于是被迫代替她去往敵國和親饵撑。 傳聞我的和親對象是個殘疾皇子滑潘,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內容