【轉(zhuǎn)載】深入理解Flutter Platform Channel

1.理解Platform Channel工作原理

Flutter定義了三種不同類型的Channel,它們分別是

  • BasicMessageChannel:用于傳遞字符串和變結(jié)構(gòu)化的信息掀虎。
  • MethodChannel:用于傳遞方法調(diào)用(method invocation)烹玉。
  • EventChannel:用于數(shù)據(jù)流(event streams)的通信二打。

三種Channel之間互相獨(dú)立继效,各有用途瑞信,但它們在設(shè)計上卻非常相近喧伞。每種Channel均有三個重要成員變量:

  • name:String類型潘鲫,代表Channel的名字肋杖,也是其唯一標(biāo)識符状植。
  • messager:BinaryMessenger類型怨喘,代表消息信使,是消息的發(fā)送與接收的工具梳庆。
  • codec:MessageCodec類型或MethodCodec類型膏执,代表消息的編解碼器更米。

1.1. Channel name

一個Flutter應(yīng)用中可能存在多個Channel征峦,每個Channel在創(chuàng)建時必須指定一個獨(dú)一無二的name眶痰,Channel之間使用name來區(qū)分彼此。當(dāng)有消息從Flutter端發(fā)送到Platform端時因宇,會根據(jù)其傳遞過來的channel name找到該Channel對應(yīng)的Handler(消息處理器)察滑。

1.2. 消息信使:BinaryMessenger

雖然三種Channel各有用途饲化,但是他們與Flutter通信的工具卻是相同的吃靠,均為BinaryMessenger巢块。

BinaryMessenger是Platform端與Flutter端通信的工具族奢,其通信使用的消息格式為二進(jìn)制格式數(shù)據(jù)。當(dāng)我們初始化一個Channel泣栈,并向該Channel注冊處理消息的Handler時,實(shí)際上會生成一個與之對應(yīng)的BinaryMessengerHandler疼进,并以channel name為key伞广,注冊到BinaryMessenger中疼电。當(dāng)Flutter端發(fā)送消息到BinaryMessenger時蔽豺,BinaryMessenger會根據(jù)其入?yún)hannel找到對應(yīng)的BinaryMessenger沧侥,并交由其處理宴杀。

BinaryMessenger在Android端是一個接口旺罢,其具體實(shí)現(xiàn)為FlutterNativeView。而其在iOS端是一個協(xié)議罩驻,名稱為FlutterBinaryMessenger,F(xiàn)lutterViewController遵循了它护赊。

BinaryMEssenger并不知道Channel的存在惠遏,它只和BinaryMessengerHandler打交道砾跃。而Channel和BinaryMessengerHandler則是一一對應(yīng)的。由于Channel從BinaryMessengerHandler接收到的消息是二進(jìn)制格式數(shù)據(jù)节吮,無法直接使用抽高,故Channel會將該二進(jìn)制消息通過Codec(消息編解碼器)解碼為能識別的消息并傳遞給Handler進(jìn)行處理。

當(dāng)Handler處理完消息之后透绩,會通過回調(diào)函數(shù)返回result翘骂,并將result通過編解碼器編碼為二進(jìn)制格式數(shù)據(jù),通過BinaryMessenger發(fā)送會Flutter端。

1.3. 消息編解碼器:Codec

消息編解碼器Codec主要用于將二進(jìn)制格式的數(shù)據(jù)轉(zhuǎn)化為Handler能夠識別的數(shù)據(jù)煤禽,F(xiàn)lutter定義了兩種Codec:MessageCodec和MethodCodec。

1.3.1. MessageCodec

MessageCodec用于二進(jìn)制格式數(shù)據(jù)與基礎(chǔ)數(shù)據(jù)之間的編解碼。BasicMessageChannel所使用的編解碼器就是MessageCodec斤程。

Android中,MessageCodec是一個接口,定義了兩個方法:encodeMessage接收一個特定的數(shù)據(jù)類型T,并將其編碼為二進(jìn)制數(shù)據(jù)ByteBuffer颁虐,而decodeMessage則接收二進(jìn)制數(shù)據(jù)ByteBuffer笋籽,將其解碼為特定數(shù)據(jù)類型T容劳。
iOS中留量,其名稱為FlutterMessageCodec可岂,是一個協(xié)議,定義了兩個方法:encode接收一個類型為id的消息,將其編碼為NSData類型,而decode接收NSData類型消息,將其解碼為id類型數(shù)據(jù)。

MessageCodec有多種不同的實(shí)現(xiàn):

  • BinaryCodec

    BinaryCodec是最為簡單的一種Codec,因?yàn)槠浞祷刂殿愋秃腿雲(yún)⒌念愋拖嗤谴啵鶠槎M(jìn)制格式(Android中為ByteBuffer屈雄,iOS中為NSData)惋嚎。實(shí)際上质况,BinaryCodec在編解碼過程中什么都沒做,只是原封不動將二進(jìn)制數(shù)據(jù)消息返回而已视哑」虺剩或許你會因此覺得BinaryCodec沒有意義,但是在某些情況下它非常有用,比如使用BinaryCodec可以使傳遞內(nèi)存數(shù)據(jù)塊時在編解碼階段免于內(nèi)存拷貝晶丘。

  • StringCodec

    StringCodec用于字符串與二進(jìn)制數(shù)據(jù)之間的編解碼郁油,其編碼格式為UTF-8案站。

  • JsonMessageCodec

    JsonMessageCodec用于基礎(chǔ)數(shù)據(jù)與二進(jìn)制數(shù)據(jù)之間的編解碼富岳,其支持基礎(chǔ)數(shù)據(jù)以及列表蜒灰、字典。其在iOS端使用了NSJSONSerialization作為序列化的工具网杆,而在Android端則使用了其自定義的JSONUtil與StringCodec作為序列化工具座柱。

  • StandardMessageCodec

    StandardMessageCodec是BasicMessageChannel的默認(rèn)編解碼器,其支持基礎(chǔ)數(shù)據(jù)類型秋秤、二進(jìn)制數(shù)據(jù)涩咖、列表零院、字典炫惩,其工作原理在下文中詳細(xì)介紹粘咖。

1.3.2. MethodCodec

MethodCodec用于二進(jìn)制數(shù)據(jù)與方法調(diào)用(MethodCall)和返回結(jié)果之間的編解碼。MethodChannel和EventChannel所使用的編解碼器均為MethodCodec巡社。

與MessageCodec不同的是姜胖,MethodCodec用于MethodCall對象的編解碼胀瞪,一個MethodCall對象代表一次從Flutter端發(fā)起的方法調(diào)用。MethodCall有2個成員變量:String類型的method代表需要調(diào)用的方法名稱朴肺,通用類型(Android中為Object鞍盗,iOS中為id)的arguments代表需要調(diào)用的方法入?yún)ⅰ?/p>

由于處理的是方法調(diào)用敷存,故相比于MessageCodec谱煤,MethodCodec多了對調(diào)用結(jié)果的處理令野。當(dāng)方法調(diào)用成功時餐抢,使用encodeSuccessEnvelope將result編碼為二進(jìn)制數(shù)據(jù)现使,而當(dāng)方法調(diào)用失敗時,則使用encodeErrorEnvelope將error的code旷痕、message碳锈、detail編碼為二進(jìn)制數(shù)據(jù)。

MethodCodec有兩種實(shí)現(xiàn):

  • JSONMethodCodec

    JSONMethodCodec的編解碼依賴于JSONMessageCodec欺抗,當(dāng)其在編碼MethodCall時售碳,會先將MethodCall轉(zhuǎn)化為字典{"method":method,"args":args}。其在編碼調(diào)用結(jié)果時佩迟,會將其轉(zhuǎn)化為一個數(shù)組团滥,調(diào)用成功為[result],調(diào)用失敗為[code,message,detail]报强。再使用JSONMessageCodec將字典或數(shù)組轉(zhuǎn)化為二進(jìn)制數(shù)據(jù)灸姊。

  • StandardMethodCodec

    MethodCodec的默認(rèn)實(shí)現(xiàn),StandardMethodCodec的編解碼依賴于StandardMessageCodec秉溉,當(dāng)其編碼MethodCall時力惯,會將method和args依次使用StandardMessageCodec編碼,寫入二進(jìn)制數(shù)據(jù)容器召嘶。其在編碼方法的調(diào)用結(jié)果時父晶,若調(diào)用成功,會先向二進(jìn)制數(shù)據(jù)容器寫入數(shù)值0(代表調(diào)用成功)弄跌,再寫入StandardMessageCodec編碼后的result甲喝。而調(diào)用失敗,則先向容器寫入數(shù)據(jù)1(代表調(diào)用失旑踔弧)埠胖,再依次寫入StandardMessageCodec編碼后的code糠溜,message和detail。

1.4. 消息處理器:Handler

當(dāng)我們接收二進(jìn)制格式消息并使用Codec將其解碼為Handler能處理的消息后直撤,就該Handler上場了非竿。Flutter定義了三種類型的Handler,與Channel類型一一對應(yīng)谋竖。我們向Channel注冊一個Handler時红柱,實(shí)際上就是向BinaryMessager注冊一個與之對應(yīng)的BinaryMessageHandler。當(dāng)消息派分到BinaryMessageHandler后蓖乘,Channel會通過Codec將消息解碼锤悄,并傳遞給Handler處理。

1.4.1. MessageHandler

MessageHandler用戶處理字符串或者半結(jié)構(gòu)化的消息嘉抒,其onMessage方法接收一個T類型的消息铁蹈,并異步返回一個相同類型result。MessageHandler的功能比較基礎(chǔ)众眨,使用場景較少,但是其配合BinaryCodec使用時容诬,能夠方便傳遞二進(jìn)制數(shù)據(jù)消息娩梨。

1.4.2. MethodHandler

MethodHandler用于處理方法的調(diào)用,其onMessage方法接收一個MethodCall類型消息览徒,并根據(jù)MethodCall的成員變量method去調(diào)用對應(yīng)的API狈定,當(dāng)處理完成后,根據(jù)方法調(diào)用成功或失敗习蓬,返回對應(yīng)的結(jié)果纽什。

1.4.3. StreamHandler

StreamHandler與前兩者稍顯不同,用于事件流的通信躲叼,最為常見的用途就是Platform端向Flutter端發(fā)送事件消息芦缰。當(dāng)我們實(shí)現(xiàn)一個StreamHandler時,需要實(shí)現(xiàn)其onListenonCancel方法枫慷。而在onListen方法的入?yún)⒅腥美伲幸粋€EventSink(其在Android是一個對象,iOS端則是一個block)或听。我們持有EventSink后探孝,即可通過EventSink向Flutter端發(fā)送事件消息。

實(shí)際上誉裆,StreamHandler工作原理并不復(fù)雜顿颅。當(dāng)我們注冊了一個StreamHandler后,實(shí)際上會注冊一個對應(yīng)的BinaryMessageHandler到BinaryMessager足丢。而當(dāng)Flutter端開始監(jiān)聽事件時粱腻,會發(fā)送一個二進(jìn)制消息到Platform端庇配。Platform端用MethodCodec將該消息解碼為MethodCall,如果MethodCall的method的值為"listen"栖疑,則調(diào)用StreamHandler的onListen方法讨永,傳遞給StreamHandler一個EventSink。而通過EventSink向Flutter端發(fā)送消息時遇革,實(shí)際上就是通過BinaryMessager的send方法將消息傳遞過去卿闹。

2. 理解消息編解碼過程

在官方文檔《Writing custom platform-specific code with platform channels》中的獲取設(shè)備電量的例子中我們發(fā)現(xiàn),Android端的返回值是java.lang.Integer類型的萝快,而iOS端返回值則是一個NSNumber類型的(通過NSNumber numberWithInt:獲榷亡)。而到了Flutter端時揪漩,這個返回值自動"變成"了dart語言的int類型旋恼。那么這中間發(fā)生了什么呢?

Flutter官方文檔表示奄容,standard platform channels使用standard messsage codecmessageresponse進(jìn)行序列化和反序列化冰更,messageresponse可以是booleans, numbers, Strings, byte buffers,List, Maps等等,而序列化后得到的則是二進(jìn)制格式的數(shù)據(jù)昂勒。

所以在上文提到的例子中蜀细,java.lang.IntegerNSNumber類型的返回值先是被序列化成了一段二進(jìn)制格式的數(shù)據(jù),然后該數(shù)據(jù)傳遞到傳遞到flutter側(cè)后戈盈,被反序列化成了dart語言中的int類型的數(shù)據(jù)奠衔。

Flutter默認(rèn)的消息編解碼器是StandardMessageCodec,其支持的數(shù)據(jù)類型如下:

當(dāng)message或response需要被編碼為二進(jìn)制數(shù)據(jù)時,會調(diào)用StandardMessageCodec的writeValue方法塘娶,該方法接收一個名為value的參數(shù)归斤,并根據(jù)其類型,向二進(jìn)制數(shù)據(jù)容器(NSMutableData或ByteArrayOutputStream)寫入該類型對應(yīng)的type值刁岸,再將該數(shù)據(jù)轉(zhuǎn)化為二進(jìn)制表示脏里,并寫入二進(jìn)制數(shù)據(jù)容器。

而message或者response需要被解碼時难捌,使用的是StandardMessageCodec的readValue方法膝宁,該方法接收到二進(jìn)制格式數(shù)據(jù)后,會先讀取一個byte表示其type根吁,再根據(jù)其type將二進(jìn)制數(shù)據(jù)轉(zhuǎn)化為對應(yīng)的數(shù)據(jù)類型员淫。

在獲取設(shè)備電量的例子中,假設(shè)設(shè)備的電量為100击敌,當(dāng)這個值被轉(zhuǎn)化為二進(jìn)制數(shù)據(jù)時介返,會先向二進(jìn)制數(shù)據(jù)容器寫入int類型對應(yīng)的type值:3,再寫入由電量值100轉(zhuǎn)化而得的4個byte。而當(dāng)Flutter端接收到該二進(jìn)制數(shù)據(jù)時圣蝎,先讀取第一個byte值刃宵,并根據(jù)其值得出該數(shù)據(jù)為int類型,接著徘公,讀取緊跟其后的4個byte牲证,并將其轉(zhuǎn)化為dart類型的int。

對于字符串关面、列表坦袍、字典的編碼會稍微復(fù)雜一些。字符串使用UTF-8編碼得到的二進(jìn)制數(shù)據(jù)是長度不定的等太,因此會在寫入type后捂齐,先寫入一個代表二進(jìn)制數(shù)據(jù)長度的size,再寫入數(shù)據(jù)缩抡。列表和字典則是寫入type后奠宜,先寫入一個代表列表或字典中元素個數(shù)的size,再遞歸調(diào)用writeValue方法將其元素依次寫入瞻想。

3. 理解消息傳遞過程

消息是如何從Flutter端傳遞到Platform端的呢压真?接下來我們以一次MethodChannel的調(diào)用為例,去理解消息的傳遞過程蘑险。

3.1. 消息傳遞:從Flutter到Platform

3.1.1. Dart層

當(dāng)我們在Flutter端使用MethodChannel的invokeMethod方法發(fā)起一次方法調(diào)用時榴都,就開始了我們的消息傳遞之旅。invokeMethod方法會將其入?yún)?code>message和arguments封裝成一個MethodCall對象漠其,并使用MethodCodec將其編碼為二進(jìn)制格式數(shù)據(jù),再通過BinaryMessages將消息發(fā)出竿音。(注意和屎,此處提到的類名與方法名均為dart層的實(shí)現(xiàn))

上述過程最終會調(diào)用到ui.Window的_sendPlatformMessage方法,該方法是一個native方法春瞬,其實(shí)現(xiàn)在native層柴信,這與Java的JNI技術(shù)非常類似。我們向native層發(fā)送了三個參數(shù):

  • name宽气,String類型随常,代表Channel名稱

  • data,ByteData類型萄涯,即之前封裝的二進(jìn)制數(shù)據(jù)

  • callback绪氛,F(xiàn)unction類型,用于結(jié)果回調(diào)

3.1.2. Native層

到native層后涝影,window.cc的SendPlatformMessage方法接受了來自dart層的三個參數(shù)枣察,并對它們做了一定的處理:dart層的回調(diào)callback封裝為native層的PlatformMessageResponseDart類型的response;dart層的二進(jìn)制數(shù)據(jù)data轉(zhuǎn)化為std::vector<uint8_t>類型數(shù)據(jù)data;根據(jù)response,data以及Channel名稱name創(chuàng)建一個PlatformMessage對象序目,并通過dart_state->window()->client()->HandlePlatformMessage方法處理PlatformMessage對象臂痕。

dart_state->window()->client()是一個WindowClient,而其具體的實(shí)現(xiàn)為RuntimeController猿涨,RuntimeController會將消息交給其代理RuntimeDelegate處理握童。

RuntimeDelegate的實(shí)現(xiàn)為Engine,Engine在處理Message時叛赚,會判斷該消息是否是為了獲取資源(channel等于"flutter/assets"),如果是澡绩,則走獲取資源邏輯,否則調(diào)用Engine::Delegate的OnEngineHandlePlatformMessage方法红伦。

Engine::Delegate的具體實(shí)現(xiàn)為Shell英古,其OnEngineHandlePlatformMessage接收到消息后,會向PlatformTaskRunner添加一個Task昙读,該Task會調(diào)用PlatformView的HandlePlatformMessage方法召调。值得注意的是,Task中的代碼執(zhí)行在Platform Task Runner中蛮浑,而之前的代碼均執(zhí)行在UI Task Runner中唠叛。

3.2. 消息處理

PlatformView的HandlePlatformMessage方法在不同平臺有不同的實(shí)現(xiàn),但是其基本原理是相同的沮稚。

3.2.1. PlatformViewAndroid

PlatformViewAndroid的是Platformview的子類艺沼,也是其在Android端的具體實(shí)現(xiàn)。當(dāng)PlatformViewAndroid接收到PlatformMessage類型的消息時蕴掏,如果消息中有response(類型為PlatformMessageResponseDart)障般,則生成一個自增長的response_id,并以response_id為key,response為value存入字典pending_responses_中盛杰。接著挽荡,將channeldata均轉(zhuǎn)化為Java可識別的數(shù)據(jù),通過JNI向Java層發(fā)起調(diào)用即供,將response_id定拟、channeldata傳遞過去。

Java層中逗嫡,被調(diào)用的代碼為FlutterNativeView (BinaryMessager的具體實(shí)現(xiàn))的handlePlatformMessage青自,該方法會根據(jù)channel找到對應(yīng)的BinaryMessageHandler并將消息傳遞給它處理。其具體處理過程我們已經(jīng)在上文中詳細(xì)分析過了驱证,此處不再贅述延窜。

BinaryMessageHandler處理完成后,F(xiàn)lutterNativeView會通過JNI調(diào)用native的方法抹锄,將response_dataresponse_id傳遞到native層需曾。

native層,PlatformViewAndroid的InvokePlatformMessageResponseCallback接收到了respond_idresponse_data。其先將response_data轉(zhuǎn)化為二進(jìn)制結(jié)果呆万,并根據(jù)response_id商源,從panding_responses_中找到對應(yīng)的PlatformMessageResponseDart對象,調(diào)用其Complete方法將二進(jìn)制結(jié)果返回谋减。

3.2.2. PlatformViewIOS

PlatformViewIOS是PlatformView的子類牡彻,也是其在iOS端的具體實(shí)現(xiàn),當(dāng)PlatformViewIOS接收到message時會交給PlatformMessageRouter處理出爹。

PlatformMessageRouter通過PlatformMessage中的channel找到對應(yīng)的FlutterBinaryMessageHandler庄吼,并將二進(jìn)制消息其處理,消息處理完成后严就,直接調(diào)用PlatformMessage對象中的PlatformMessageResponseDart對象的Complete方法將二進(jìn)制結(jié)果返回总寻。

3.3. 結(jié)果回傳:從Platform到Flutter

PlatformMessageResponseDart的Complete方法向UI Task Runner添加了一個新的Task,這個Task的作用是將二進(jìn)制結(jié)果從native的二進(jìn)制數(shù)據(jù)類型轉(zhuǎn)化為Dart的二進(jìn)制數(shù)據(jù)類型response梢为,并調(diào)用dart的callback將response傳遞到Dart層渐行。

Dart層接收到二進(jìn)制數(shù)據(jù)后,使用MethodCodec將數(shù)據(jù)解碼铸董,并返回給業(yè)務(wù)層祟印。至此,一次從Flutter發(fā)起的方法調(diào)用就完整結(jié)束了粟害。

4. 問題解析

4.1. Platform Channel的代碼運(yùn)行在什么線程

在文章《深入理解Flutter引擎線程模型》中提及蕴忆,F(xiàn)lutter Engine自己不創(chuàng)建線程,其線程的創(chuàng)建于管理是由enbedder提供的悲幅,并且Flutter Engine要求Embedder提供四個Task Runner,分別是Platform Task Runner,UI Task Runner套鹅,GPU Task Runner和IO Task Runner。

實(shí)際上汰具,在Platform側(cè)執(zhí)行的代碼運(yùn)行在Platform Task Runner中芋哭,而在Flutter app側(cè)的代碼則運(yùn)行在UI Task Runner中。在Android和iOS平臺上郁副,Platform Task Runner跑在主線程上。因此豌习,不應(yīng)該在Platform端的Handler中處理耗時操作存谎。

4.2. Platform Channel是否線程安全

Platform Channel并非是線程安全的,這一點(diǎn)在官方的文檔也有提及肥隆。Flutter Engine中多個組件是非線程安全的既荚,故跟Flutter Engine的所有交互(接口調(diào)用)必須發(fā)生在Platform Thread。故我們在將Platform端的消息處理結(jié)果回傳到Flutter端時栋艳,需要確鼻∑福回調(diào)函數(shù)是在Platform Thread(也就是Android和iOS的主線程)中執(zhí)行的。

4.3. 是否支持大內(nèi)存數(shù)據(jù)塊的傳遞

Platform Channel實(shí)際上是支持大內(nèi)存數(shù)據(jù)塊的傳遞,當(dāng)需要傳遞大內(nèi)存數(shù)據(jù)塊時晴叨,需要使用BasicMessageChannel以及BinaryCodec凿宾。而整個數(shù)據(jù)傳遞的過程中,唯一可能出現(xiàn)數(shù)據(jù)拷貝的位置為native二進(jìn)制數(shù)據(jù)轉(zhuǎn)化為Dart語言二進(jìn)制數(shù)據(jù)兼蕊。若二進(jìn)制數(shù)據(jù)大于閾值時(目前閾值為1000byte)則不會拷貝數(shù)據(jù)初厚,直接轉(zhuǎn)化,否則拷貝一份再轉(zhuǎn)化孙技。

4.4. 如何將Platform Channel原理應(yīng)用到開發(fā)工作中

實(shí)際上Platform Channel的應(yīng)用場景非常多产禾,我們這里舉一個例子:

在平常的業(yè)務(wù)開發(fā)中,我們需要使用到一些本地圖片資源牵啦,但是Flutter端是無法使用Platform端已存在的圖片資源的亚情。當(dāng)Flutter端需要使用一個Platform端已有的圖片資源時,只有將該圖片資源拷貝一份到Flutter的Assert目錄下才能使用哈雏。實(shí)際上楞件,讓Flutter端使用Platform端的資源并不是一件難事。

我們可以使用BasicMessageChannel來完成這個工作僧著。Flutter端將圖片資源名name傳遞給Platform端履因,Native端使用Platform端接收到name后,根據(jù)name定位到圖片資源盹愚,并將該圖片資源以二進(jìn)制數(shù)據(jù)格式栅迄,通過BasicMessageChannel葵陵,傳遞回Flutter端汰蜘。

總結(jié)

在Flutter與Native混合開發(fā)的模式下淮逊,Platform Channel的應(yīng)用場景非常多汇竭,理解Platform Channel的工作原理派歌,有助于我們在從事這方面開發(fā)時能做到得心應(yīng)手钦无。

[轉(zhuǎn)載]閑魚技術(shù):深入理解Flutter Platform Channel -https://www.yuque.com/xytech/flutter/fu7h25

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末铝耻,一起剝皮案震驚了整個濱河市卧晓,隨后出現(xiàn)的幾起案子虱黄,更是在濱河造成了極大的恐慌悦即,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件橱乱,死亡現(xiàn)場離奇詭異辜梳,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)泳叠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門作瞄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人危纫,你說我怎么就攤上這事宗挥∥谑” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵契耿,是天一觀的道長瞒大。 經(jīng)常有香客問我,道長宵喂,這世上最難降的妖魔是什么糠赦? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮锅棕,結(jié)果婚禮上拙泽,老公的妹妹穿的比我還像新娘。我一直安慰自己裸燎,他們只是感情好顾瞻,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著德绿,像睡著了一般荷荤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上移稳,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天蕴纳,我揣著相機(jī)與錄音,去河邊找鬼个粱。 笑死古毛,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的都许。 我是一名探鬼主播稻薇,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼胶征!你這毒婦竟也來了塞椎?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤睛低,失蹤者是張志新(化名)和其女友劉穎案狠,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钱雷,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡骂铁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了急波。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡瘪校,死狀恐怖澄暮,靈堂內(nèi)的尸體忽然破棺而出名段,到底是詐尸還是另有隱情,我是刑警寧澤泣懊,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布伸辟,位于F島的核電站,受9級特大地震影響馍刮,放射性物質(zhì)發(fā)生泄漏信夫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一卡啰、第九天 我趴在偏房一處隱蔽的房頂上張望静稻。 院中可真熱鬧,春花似錦匈辱、人聲如沸振湾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽押搪。三九已至,卻和暖如春浅碾,著一層夾襖步出監(jiān)牢的瞬間大州,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工垂谢, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留厦画,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓埂陆,卻偏偏與公主長得像苛白,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子焚虱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355