WebViewJavascriptBridge 原理分析

網(wǎng)上好多都是在介紹 WebViewJavascriptBridge如何使用岔绸,這篇文章就來說說WebViewJavascriptBridge 設(shè)計(jì)原理。

主要從兩個(gè)過程來講一下:js調(diào)用UIViewController中的代碼(Native)误褪,Native調(diào)用js

1.概述

首先有兩個(gè)問題:

a. Native(中的UIWebView)是否可以直接調(diào)用js method(方法)? 可以碾褂。

b. js 是否可以直接調(diào)用Native的 method?不行历葛。

明確上述兩個(gè)問題正塌,那么上圖就不難明白了,webpage中的 js method和 webview 本地的method之間關(guān)系恤溶。那WebViewJavascriptBridge出現(xiàn)是否解決這個(gè)問題(這個(gè)問題就是讓js可以直接調(diào)用native的method)呢乓诽?答案是否定的?沒有本質(zhì)還是用uiwebview的代理方法進(jìn)行字段攔截(判斷url scheme)咒程,實(shí)現(xiàn)js間接調(diào)用native的method鸠天。

我們來看WebViewJavascriptBridge提供的demo:

主要的核心是下面兩個(gè),接下來我們就來討論一下其設(shè)計(jì)原理帐姻。

2. js調(diào)用Native method

在概述中說過稠集,js是不能直接調(diào)用native的method所以,需要借助

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType

這個(gè)方法大家不陌生饥瓷,每次在重新定向URL的時(shí)候剥纷,這個(gè)方法就會(huì)被觸發(fā),通常情況呢铆,我們會(huì)在這里做一些攔截完成js和本地的間接交互什么的晦鞋。那么WebViewJavascriptBridge也不另外,也是這么做棺克。

我們先來看看在ExampleApp.html文件中點(diǎn)擊一個(gè)按鈕發(fā)起請(qǐng)求的代碼:

var callbackButton = document.getElementById('buttons').appendChild(document.createElement('button'))

callbackButton.innerHTML ='Fire testObjcCallback'

callbackButton.onclick = function(e) {

e.preventDefault()

log('JS calling handler "testObjcCallback"')

//1

bridge.callHandler('testObjcCallback', {'foo':'cccccccccccc'}, function(response) {

log('JS got response', response)

})

}

估計(jì)大家大體都能看懂悠垛,唯獨(dú)有疑問的地方是:

bridge.callHandler('testObjcCallback', {'foo':'cccccccccccc'}, function(response) {

log('JS got response', response)

})

}

這段代碼先不說,上面代碼就是一個(gè)按鈕的普通單擊事件方法娜谊。我們一起想一下确买,如果這個(gè)按鈕需要被點(diǎn)擊之后調(diào)用native中的funtion函數(shù),之后需要把這個(gè)(native的)funtion函數(shù)處理結(jié)果返回給js中的方法繼續(xù)處理因俐。這個(gè)是我們需求拇惋,帶著這個(gè)需求我們看一下這個(gè)方法,testObjcCallBack這個(gè)我們猜測(cè)一下應(yīng)該native中的方法或者一個(gè)能夠調(diào)用到方法的name/id抹剩,后面這個(gè)是個(gè)json{‘foo’:‘ccccccccccccc’},應(yīng)該是個(gè)參數(shù)撑帖,那么后面這個(gè)方法一看log應(yīng)該知道,是對(duì)native返回的result進(jìn)行處理的方法澳眷。拿具體是不是呢胡嘿?只要找到callHandler方法就知道了。

在文件WebView JavascriptBridge.js.txt里面我們找找這個(gè)方法:

function callHandler(handlerName, data, responseCallback) {

_doSend({ handlerName:handlerName, data:data }, responseCallback)

}

這里又多了一個(gè)方法叫_doSend連個(gè)參數(shù) 第1個(gè)是字典key-value定義钳踊,第二個(gè)是一個(gè)方法的指針(看看上面的方法你就知道了)衷敌,那我們必須在同一個(gè)文件里面看看能不能找到這個(gè)_doSend方法:

function _doSend(message, responseCallback) {

if(responseCallback) {

var callbackId ='cb_'+(uniqueId++)+'_'+newDate().getTime()

responseCallbacks[callbackId] = responseCallback

message['callbackId'] = callbackId

}

sendMessageQueue.push(message)

messagingIframe.src = CUSTOM_PROTOCOL_SCHEME +'://'+ QUEUE_HAS_MESSAGE

}

找到了勿侯。

逐行分析一下,變量callbackId是個(gè)字符串缴罗,responseCallBacks[] 一看就知道是個(gè)字典助琐,這個(gè)字典把回掉(我們猜測(cè))的方法responseCallback給保存起來,這Key(也就是callbackId)應(yīng)該是唯一的面氓,通過計(jì)數(shù)和時(shí)間應(yīng)該知道這個(gè)字符串應(yīng)該是唯一的兵钮,message也是一個(gè)字典,這是給message添加了一個(gè)新的key-value舌界。干嘛呢掘譬?我也不知道,我們來看看sendMessageQueue是什么呻拌,大家一個(gè)push就知道應(yīng)該是個(gè)數(shù)組葱轩。他吧一個(gè)字典放到一個(gè)消息隊(duì)列中(數(shù)組隊(duì)列),讓后產(chǎn)生一個(gè)src(url scheme)藐握。

有兩個(gè)變量我們看看:

var CUSTOM_PROTOCOL_SCHEME ='wvjbscheme'

var QUEUE_HAS_MESSAGE ='__WVJB_QUEUE_MESSAGE__'

干嘛用靴拱,肯定是給webview 的 delegate判斷用的,你感覺呢趾娃?(肯定是)

下面是在文件:WebViewJavascriptBridge.m

好了到了這里大家猜猜這個(gè)要干嘛缭嫡?肯定是要發(fā)url讓web截取對(duì)吧?那還用問啊抬闷,肯定是啊妇蛀,已經(jīng)說過了js能不能調(diào)用native的funtion函數(shù)?不能笤成。我們來看看這個(gè)messagingIframe是:

function _createQueueReadyIframe(doc) {

messagingIframe = doc.createElement('iframe')

messagingIframe.style.display ='none'

messagingIframe.src = CUSTOM_PROTOCOL_SCHEME +'://'+ QUEUE_HAS_MESSAGE

doc.documentElement.appendChild(messagingIframe)

}

原來就是iframe评架,這個(gè)就不同給大家解釋了。好了src一產(chǎn)生就會(huì)出現(xiàn)什么炕泳,uiwebview代理回掉截獲纵诞,此時(shí)我們把目光回到UIWebview的Native下面:

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {

if(webView != _webView) {returnYES; }

NSURL *url = [request URL];

__strong WVJB_WEBVIEW_DELEGATE_TYPE* strongDelegate = _webViewDelegate;

if([[url scheme] isEqualToString:kCustomProtocolScheme])

{

if([[url host] isEqualToString:kQueueHasMessage])

{

//會(huì)走這里

[self _flushMessageQueue];

}

else

{

NSLog(@"WebViewJavascriptBridge: WARNING: Received unknown WebViewJavascriptBridge command %@://%@", kCustomProtocolScheme, [url path]);

}

returnNO;

}

elseif(strongDelegate && [strongDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)])

{

return[strongDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];

}

else

{

returnYES;

}

}

一看就頭大,哈哈培遵,是嚼隘,我也頭大疚宇」瞪常看看上面的注釋說 會(huì)走這里施戴,我們看看為什么會(huì)走那里,最外圈的if([url scheme])判斷是

#define kCustomProtocolScheme @"wvjbscheme"

這個(gè)定義是什么意思皇耗,我們先不做解釋南窗,剛才我們說過js不能直接調(diào)用native的function,大家只要記住這點(diǎn),接著往下走就是了万伤。至于為什么走這里窒悔,自己看代碼(上文有提到),我們看看_flushMessageQueue:

- (void)_flushMessageQueue {

NSString *messageQueueString = [_webView stringByEvaluatingJavaScriptFromString:@"WebViewJavascriptBridge._fetchQueue();"];

//json轉(zhuǎn)成數(shù)組

id messages = [self _deserializeMessageJSON:messageQueueString];

if(![messages isKindOfClass:[NSArrayclass]]) {

NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [messagesclass], messages);

return;

}

for(WVJBMessage* message in messages) {

if(![message isKindOfClass:[WVJBMessageclass]]) {

NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [messageclass], message);

continue;

}

[self _log:@"RCVD"json:message];

//用于js回掉

NSString* responseId = message[@"responseId"];

if(responseId) {

WVJBResponseCallback responseCallback = _responseCallbacks[responseId];

responseCallback(message[@"responseData"]);

[_responseCallbacks removeObjectForKey:responseId];

}else{

WVJBResponseCallback responseCallback = NULL;

NSString* callbackId = message[@"callbackId"];

if(callbackId) {

responseCallback = ^(id responseData) {

if(responseData == nil) {

responseData = [NSNullnull];

}

WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };

[self _queueMessage:msg];

};

}else{

responseCallback = ^(id ignoreResponseData) {

// Do nothing

};}

WVJBHandler handler;

if(message[@"handlerName"]) {

handler = _messageHandlers[message[@"handlerName"]];

}else{

handler = _messageHandler;

}

if(!handler) {

[NSException raise:@"WVJBNoHandlerException"format:@"No handler for message from JS: %@", message];

}

handler(message[@"data"], responseCallback);

}}}

這下牛逼了敌买,不忍直視凹蛑椤!這么多虹钮,哈哈北救,多不可怕,可怕是你堅(jiān)持不下去了芜抒。

我們逐行來看:

NSString *messageQueueString = [_webView stringByEvaluatingJavaScriptFromString:@"WebViewJavascriptBridge._fetchQueue();"];

我們必須回去到j(luò)s文件中去,這里是webview直接調(diào)用js中的方法:

function _fetchQueue() {

var messageQueueString = JSON.stringify(sendMessageQueue)

sendMessageQueue = []

return messageQueueString

}

謝天謝地這個(gè)方法代碼不多托启,這個(gè)消息很眼熟宅倒,SendMessageQueue,剛才我們說什么來屯耸?他是一個(gè)字典拐迁,那里面有哪些東西,我么來看看

handlerName:handlerName,

data:data,

callbackId:callbackId

這個(gè)消息字典此時(shí)被取出來準(zhǔn)備做什么疗绣,這里提示下我們已經(jīng)走到webview 的delegate里面了线召,所以拿到這些信息肯定是調(diào)用native的method對(duì)吧?肯定是的多矮。接著往下走缓淹,接著會(huì)把json字符串轉(zhuǎn)成數(shù)組,然后進(jìn)行判斷塔逃,

NSString* responseId = message[@"responseId"];

有沒有responseid讯壶,你說又沒,肯定沒有巴宓痢(你不行看看上面)伏蚊,所以就這這里了

WVJBResponseCallback responseCallback = NULL;

NSString* callbackId = message[@"callbackId"];

if(callbackId) {

responseCallback = ^(id responseData) {

if(responseData == nil) {

responseData = [NSNullnull];

}

WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };

[self _queueMessage:msg];

};

}else{

responseCallback = ^(id ignoreResponseData) {

// Do nothing

};}

WVJBHandler handler;

if(message[@"handlerName"]) {

handler = _messageHandlers[message[@"handlerName"]];

}else{

handler = _messageHandler;

}

if(!handler) {

[NSException raise:@"WVJBNoHandlerException"format:@"No handler for message from JS: %@", message];

}

handler(message[@"data"], responseCallback);

這部分是重點(diǎn),到底他是怎么要調(diào)用本地function的格粪,callbackId大家熟悉吧躏吊,判斷是否為空,不為空給他指定一個(gè)block帐萎,這個(gè)不說了比伏,block指定,此時(shí)不調(diào)用(手動(dòng)調(diào)用才會(huì)執(zhí)行)吓肋,這個(gè)剛才說了用來處理native的function處理的result用于把處理后的值返回給js的凳怨,接著往下去,看到handler這個(gè)方法會(huì)從message找到handlerName,這里我們看一下多了一個(gè)_messageHandlers字典肤舞,從這個(gè)字典獲取一個(gè)block(WVJBHandler是一個(gè)block)紫新,直接執(zhí)行了。那我們看看_messageHandlers是怎么被添加block的:

- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {

_messageHandlers[handlerName] = [handler copy];

}

那又是誰調(diào)用了這個(gè)方法:

找到了(在文件 ExampleAppViewController.m的viewdidload中)李剖,這里有方法testObjecCallback

[_bridge registerHandler:@"testObjcCallback"handler:^(id data, WVJBResponseCallback responseCallback) {

NSLog(@"testObjcCallback called: %@", data);

responseCallback(@"Response from testObjcCallback");

}];

有點(diǎn)亂了芒率。剛才我們的思路都是倒推的,如果我們整過來篙顺,首先肯定是viewdidload初始化偶芍,初始化之后會(huì)把這個(gè)block加入到_messageHandlers中,之后因?yàn)閖s調(diào)用動(dòng)態(tài)讀取這個(gè)block調(diào)用德玫,在調(diào)用之前匪蟀,我們又把定一個(gè)block付給回掉處理的responseCallback的block,這個(gè)block在handler中調(diào)用而調(diào)用宰僧,有點(diǎn)繞材彪,自己可以多想想。

我們接著來看看:

responseCallback = ^(id responseData) {

if(responseData == nil) {

responseData = [NSNullnull];

}

WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };

[self _queueMessage:msg];

};

這個(gè)就是你繞的地方琴儿,他是后被定義的段化,所以一開不執(zhí)行,只有在處理數(shù)據(jù)后回調(diào)才會(huì)被調(diào)用造成,這里有個(gè)方法_queueMessage:

- (void)_queueMessage:(WVJBMessage*)message {

if(_startupMessageQueue) {

[_startupMessageQueue addObject:message];

}else{

[self _dispatchMessage:message];

}}

這里面還有個(gè)方法:

- (void)_dispatchMessage:(WVJBMessage*)message {

NSString *messageJSON = [self _serializeMessage:message];

[self _log:@"SEND"json:messageJSON];

messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\\"withString:@"\\\\"];

messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\""withString:@"\\\""];

messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\'"withString:@"\\\'"];

messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\n"withString:@"\\n"];

messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\r"withString:@"\\r"];

messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\f"withString:@"\\f"];

messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2028"withString:@"\\u2028"];

messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2029"withString:@"\\u2029"];

NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];

if([[NSThread currentThread] isMainThread]) {

[_webView stringByEvaluatingJavaScriptFromString:javascriptCommand];

}else{

__strong WVJB_WEBVIEW_TYPE* strongWebView = _webView;

dispatch_sync(dispatch_get_main_queue(), ^{

[strongWebView stringByEvaluatingJavaScriptFromString:javascriptCommand];

});}}

我們?cè)诨氐絎ebViewJavascriptBridge.js.txt文件中看到

function _handleMessageFromObjC(messageJSON) {

if(receiveMessageQueue) {

receiveMessageQueue.push(messageJSON)

}else{//肯定走這個(gè)? 為什么呢显熏?

_dispatchMessageFromObjC(messageJSON)

}}

再來看看:

function _dispatchMessageFromObjC(messageJSON) {

setTimeout(function _timeoutDispatchMessageFromObjC() {

var message = JSON.parse(messageJSON)

var messageHandler

var responseCallback

if(message.responseId) {

responseCallback = responseCallbacks[message.responseId]

if(!responseCallback) {return; }

responseCallback(message.responseData)

delete responseCallbacks[message.responseId]

}else{

if(message.callbackId) {

var callbackResponseId = message.callbackId

responseCallback = function(responseData) {

_doSend({ responseId:callbackResponseId, responseData:responseData })

}}

var handler = WebViewJavascriptBridge._messageHandler

if(message.handlerName) {

handler = messageHandlers[message.handlerName]

}

try{

handler(message.data, responseCallback)

}catch(exception) {

if(typeof console !='undefined') {

console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception)

}}}

})

}

大家還記得我們返回的對(duì)象是:

@{ @"responseId":callbackId, @"responseData":responseData }

所以這里messageHandlers剛才也說過了用來存方法的,callbackId被換了個(gè)名字叫responseId意思一樣晒屎,只要值沒變就行喘蟆,所以就會(huì)執(zhí)行:

bridge.callHandler('testObjcCallback', {'foo':'cccccccccccc'}, function(response) {

log('JS got response', response)

})

中的方法,好了夷磕,完了履肃。

總結(jié)一下:js這邊

先把方法名字、參數(shù)坐桩、處理方法保存成一個(gè)字典在轉(zhuǎn)成json字符串尺棋,在通過UIWebview調(diào)用js中某個(gè)方法把這個(gè)json字符串傳到Native中

去(不是通過url傳的,這樣太low了)绵跷,同時(shí)把這個(gè)處理的方法以key-value形式放到一個(gè)js的字典中膘螟。

UIWebView在收到這個(gè)json之后,進(jìn)行數(shù)據(jù)處理碾局、還有js的回掉的處理方法(就是那個(gè)callbackId)處理完成后也會(huì)拼成一個(gè)key-value字典通過調(diào)用js傳回去(可以直接調(diào)用js)荆残。

js在接到這個(gè)json后,根據(jù)responseId讀取responseCallbacks中處理方法進(jìn)行處理Native code返回的數(shù)據(jù)净当。

3.Native調(diào)用js method

過程不是直接調(diào)用js内斯,也是通過js調(diào)用Native過程一樣的處理方式蕴潦。

大體來看一下,先看一個(gè)按鈕的單擊事件:

- (void)callHandler:(id)sender {

id data = @{ @"greetingFromObjC": @"Hi there, JS!"};

[_bridge callHandler:@"testJavascriptHandler"data:data responseCallback:^(id response) {

NSLog(@"testJavascriptHandler responded: %@", response);

}];

}

看看callHandler:

- (void)callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback {

[self _sendData:data responseCallback:responseCallback handlerName:handlerName];

}

看看_sendData:

- (void)_sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName {

NSMutableDictionary* message = [NSMutableDictionary dictionary];

if(data) {

message[@"data"] = data;

}

if(responseCallback) {

NSString* callbackId = [NSString stringWithFormat:@"objc_cb_%ld", ++_uniqueId];

_responseCallbacks[callbackId] = [responseCallback copy];

message[@"callbackId"] = callbackId;

}

if(handlerName) {

message[@"handlerName"] = handlerName;

}

[self _queueMessage:message];}

到_queueMessage:之后流程就和上面一樣了俘闯,這里面native也有個(gè):

NSString* responseId = message[@"responseId"];

if(responseId) {

WVJBResponseCallback responseCallback = _responseCallbacks[responseId];

responseCallback(message[@"responseData"]);

[_responseCallbacks removeObjectForKey:responseId];

}

這個(gè)和js中的處理思想是一樣的潭苞。

總結(jié):native將方法名、參數(shù)真朗、回到的id放到一個(gè)對(duì)象中傳給js此疹。

js根據(jù)方法名字調(diào)用相應(yīng)方法,之后將返回?cái)?shù)據(jù)和responseId拼裝遮婶,最后通過src 重定向到UIWebview 的delegate蝗碎。

native得到數(shù)據(jù)后根據(jù)responseId調(diào)用事先裝入_responseCallbacks的block,動(dòng)態(tài)讀取調(diào)用旗扑,從而完成交互蹦骑。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市臀防,隨后出現(xiàn)的幾起案子脊串,更是在濱河造成了極大的恐慌,老刑警劉巖清钥,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異放闺,居然都是意外死亡祟昭,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門怖侦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來篡悟,“玉大人,你說我怎么就攤上這事匾寝“嵩幔” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵艳悔,是天一觀的道長急凰。 經(jī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
  • 文/蒼蘭香墨 我猛地睜開眼邪码,長吁一口氣:“原來是場噩夢(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ú)居荒郊野嶺守林人離奇死亡掘剪,尸身上長有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
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至溉浙,卻和暖如春烫止,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背戳稽。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來泰國打工馆蠕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人惊奇。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓互躬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親颂郎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子吼渡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

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