Hybrid~iOS與JS交互那點(diǎn)事兒之WebViewJavaScriptBridge

圖片來源于網(wǎng)絡(luò)(侵刪).jpg
WebViewJavaScriptBridge是被使用最多狸演,也是最好用的移動(dòng)端與JS交互的第三方框架项阴,在此總結(jié)一下它的使用方法和內(nèi)部原理滑黔。

一.使用方法
iOS端(這里以UIWebView為例)

1.導(dǎo)入頭文件

#import "WebViewJavascriptBridge.h"

2.聲明橋接對(duì)象

@property WebViewJavascriptBridge* bridge;

3.初始化橋接對(duì)象

self.bridge = [WebViewJavascriptBridge bridgeForWebView:webView];

4.注冊(cè)函數(shù)

[self.bridge registerHandler:@"getUserInfo" handler:^(id data, WVJBResponseCallback responseCallback) {

        NSDictionary *dict = @{
                                    @"userid":@"0001"
                                    };
        
        responseCallback(dict);
    }];

@"getUserInfo":與JS端共同商定的函數(shù)名,JS在調(diào)用時(shí)函數(shù)名必須一致
data:函數(shù)調(diào)用時(shí)傳遞過來的數(shù)據(jù)
responseCallback:函數(shù)被調(diào)用時(shí)傳遞過來的回調(diào)函數(shù)

4.調(diào)用函數(shù)

[self.javaScriptBridge callHandler:@"webViewRefresh" data:nil responseCallback:^(id responseData) {
       
 }];

@"webViewRefresh":與JS端共同商定的函數(shù)名环揽,JS在注冊(cè)時(shí)函數(shù)名必須一致
data:調(diào)用函數(shù)時(shí)傳遞過去的數(shù)據(jù)
responseCallback:調(diào)用函數(shù)時(shí)傳遞過去的回調(diào)函數(shù)
responseData:傳遞過去的回調(diào)函數(shù)被JS調(diào)用時(shí)所傳遞過來的數(shù)據(jù)
JS端

1.聲明一個(gè)函數(shù)略荡,將此函數(shù)復(fù)制到JS文件中

function setupWebViewJavascriptBridge(callback) {
    if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
    if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
    window.WVJBCallbacks = [callback];
    var WVJBIframe = document.createElement('iframe');
    WVJBIframe.style.display = 'none';
    WVJBIframe.src = 'https://__bridge_loaded__';
    document.documentElement.appendChild(WVJBIframe);
    setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}

2.注冊(cè)或者調(diào)用交互方法

setupWebViewJavascriptBridge(function(bridge) {
    
    /* Initialize your app here */

    bridge.registerHandler('JS Echo', function(data, responseCallback) {
        console.log("JS Echo called with:", data)
        responseCallback(data)
    })
    bridge.callHandler('ObjC Echo', {'key':'value'}, function responseCallback(responseData) {
        console.log("JS received response:", responseData)
    })
})
二.內(nèi)部原理

1.框架文件

20.pic.jpg

① WebViewJavascriptBridgeBase中聲明和實(shí)現(xiàn)了基礎(chǔ)的方法和屬性
② WebViewJavascriptBridge_js中是一段JS代碼,在初始化時(shí)編譯歉胶,用于交互
③ WebViewJavascriptBridge和WKWebViewJavascriptBridge是分別適應(yīng)UIWebView和WKWebView的繼承于WebViewJavascriptBridgeBase的子類
2.初始化

1.pic.jpg

2.pic.jpg

在JS文件被加載時(shí)會(huì)執(zhí)行setupWebViewJavascriptBridge的方法汛兜,改變iframe.src,就會(huì)發(fā)起頁面請(qǐng)求跨扮,從而被UIWebView的代理方法攔截到序无,執(zhí)行injectJavascriptFile验毡,編譯WebViewJavascriptBridge_js衡创,為后面的交互做好準(zhǔn)備帝嗡,這就是初始化的過程。

function setupWebViewJavascriptBridge(callback) {
    if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
    if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
    window.WVJBCallbacks = [callback];
    var WVJBIframe = document.createElement('iframe');
    WVJBIframe.style.display = 'none';
    WVJBIframe.src = 'https://__bridge_loaded__';
    document.documentElement.appendChild(WVJBIframe);
    setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}

3.JS調(diào)用OC函數(shù)原理(這里以UIWebView為例)
首先需要OC注冊(cè)函數(shù)

[_bridge registerHandler:@"testObjcCallback" handler:^(id data, WVJBResponseCallback responseCallback) {
        NSLog(@"testObjcCallback called: %@", data);
        responseCallback(@"Response from testObjcCallback");
    }];

OC注冊(cè)函數(shù)需要調(diào)用WebViewJavascriptBridge的方法

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

那么看一下它的做了什么

- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
    _base.messageHandlers[handlerName] = [handler copy];
}

它將handlerName為key璃氢,以handler為value儲(chǔ)存在一個(gè)叫做messageHandlers的字典中哟玷,注冊(cè)就完成了
然后需要JS調(diào)用OC注冊(cè)好的函數(shù)

bridge.callHandler('testObjcCallback', {'foo': 'bar'}, function(response) {
                log('JS got response', response)
            })

JS調(diào)用函數(shù)需要用到WebViewJavascriptBridge_js的方法

function callHandler(handlerName, data, responseCallback) {
        if (arguments.length == 2 && typeof data == 'function') {
            responseCallback = data;
            data = null;
        }
        _doSend({ handlerName:handlerName, data:data }, responseCallback);
    }

function _doSend(message, responseCallback) {
        if (responseCallback) {
            var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime();
            responseCallbacks[callbackId] = responseCallback;
            message['callbackId'] = callbackId;
        }
        sendMessageQueue.push(message);
        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
    }

這個(gè)方法將handlerName, data, responseCallback儲(chǔ)存在message字典中,并將message字典儲(chǔ)存在sendMessageQueue數(shù)組中一也,最后改變了iframe.src巢寡,發(fā)送請(qǐng)求

3.pic.jpg
- (NSString *)webViewJavascriptFetchQueyCommand {
    return @"WebViewJavascriptBridge._fetchQueue();";
}

被被UIWebView的代理方法攔截到之后首先執(zhí)行JS方法WebViewJavascriptBridge._fetchQueue(),拿到要調(diào)用的函數(shù)的信息

function _fetchQueue() {
        var messageQueueString = JSON.stringify(sendMessageQueue);
        sendMessageQueue = [];
        return messageQueueString;
    }

然后執(zhí)行OC的flushMessageQueue方法

- (void)flushMessageQueue:(NSString *)messageQueueString{
    if (messageQueueString == nil || messageQueueString.length == 0) {
        NSLog(@"WebViewJavascriptBridge: WARNING: ObjC got nil while fetching the message queue JSON from webview. This can happen if the WebViewJavascriptBridge JS is not currently present in the webview, e.g if the webview just loaded a new page.");
        return;
    }

    id messages = [self _deserializeMessageJSON:messageQueueString];
    for (WVJBMessage* message in messages) {
        if (![message isKindOfClass:[WVJBMessage class]]) {
            NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [message class], message);
            continue;
        }
        [self _log:@"RCVD" json:message];
        
        NSString* responseId = message[@"responseId"];
        if (responseId) {//當(dāng)OC調(diào)用JS注冊(cè)的函數(shù)后執(zhí)行回調(diào)方法時(shí)才有responseId椰苟,此處后面會(huì)介紹
            WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
            responseCallback(message[@"responseData"]);
            [self.responseCallbacks removeObjectForKey:responseId];
        } else {//當(dāng)JS調(diào)用OC注冊(cè)的函數(shù)
            WVJBResponseCallback responseCallback = NULL;
            NSString* callbackId = message[@"callbackId"];
            if (callbackId) {//如果有JS的回調(diào)
                responseCallback = ^(id responseData) {
                    if (responseData == nil) {
                        responseData = [NSNull null];
                    }
                    
                   //注意此處添加了responseId抑月,之后在WebViewJavascriptBridge_js的_dispatchMessageFromObjC方法中會(huì)用到
                    WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
                    [self _queueMessage:msg];//執(zhí)行JS回調(diào)函數(shù)
                };
            } else {
                responseCallback = ^(id ignoreResponseData) {
                    // Do nothing
                };
            }
            
            WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];//根據(jù)函數(shù)名拿到handler
            
            if (!handler) {
                NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);
                continue;
            }
            
            handler(message[@"data"], responseCallback);//執(zhí)行handler
        }
    }
}

下面我們?cè)倏匆幌翵S的回調(diào)函數(shù)是如何被執(zhí)行的

- (void)_queueMessage:(WVJBMessage*)message {
    if (self.startupMessageQueue) {
        [self.startupMessageQueue addObject:message];
    } else {
        [self _dispatchMessage:message];
    }
}

- (void)_dispatchMessage:(WVJBMessage*)message {
    NSString *messageJSON = [self _serializeMessage:message pretty:NO];
    [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"];

    //執(zhí)行JS的_handleMessageFromObjC拿到要執(zhí)行的回調(diào)函數(shù)
    NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];

    //執(zhí)行回調(diào)函數(shù)
    if ([[NSThread currentThread] isMainThread]) {
        [self _evaluateJavascript:javascriptCommand];

    } else {
        dispatch_sync(dispatch_get_main_queue(), ^{
            [self _evaluateJavascript:javascriptCommand];
        });
    }
}

最后再看一下JS的handleMessageFromObjC方法是如何找到相應(yīng)的回調(diào)函數(shù)的

function _dispatchMessageFromObjC(messageJSON) {
        if (dispatchMessagesWithTimeoutSafety) {
            setTimeout(_doDispatchMessageFromObjC);
        } else {
             _doDispatchMessageFromObjC();
        }
        
        function _doDispatchMessageFromObjC() {
            var message = JSON.parse(messageJSON);
            var messageHandler;
            var responseCallback;

            if (message.responseId) {
//之前OC的flushMessageQueue方法中已經(jīng)添加了responseId,那么進(jìn)到這個(gè)判斷,根據(jù)responseId找到回調(diào)函數(shù)舆蝴,然后執(zhí)行
                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({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData });
                    };
                }
                
                var handler = messageHandlers[message.handlerName];
                if (!handler) {
                    console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
                } else {
                    handler(message.data, responseCallback);
                }
            }
        }
    }

至此谦絮,JS調(diào)用OC注冊(cè)的函數(shù)流程完結(jié),難點(diǎn)在于responseCallback如何執(zhí)行洁仗,要記住responseCallback是由調(diào)用方傳入的參數(shù)层皱,應(yīng)由調(diào)用方執(zhí)行。

4.OC調(diào)用JS函數(shù)原理(這里以UIWebView為例)
首先需要JS注冊(cè)函數(shù)

bridge.registerHandler('testJavascriptHandler', function(data, responseCallback) {
            log('ObjC called testJavascriptHandler with', data)
            var responseData = { 'Javascript Says':'Right back atcha!' }
            log('JS responding with', responseData)
            responseCallback(responseData)
        })

看一下這個(gè)方法的實(shí)現(xiàn)赠潦,在WebViewJavascriptBridge_js中

function registerHandler(handlerName, handler) {
        messageHandlers[handlerName] = handler;
    }

可以看到這里和OC注冊(cè)函數(shù)時(shí)的操作一樣叫胖,都是將handlerName為key,以handler為value儲(chǔ)存在一個(gè)叫做messageHandlers的字典中她奥,注冊(cè)就完成了

然后就是OC開始調(diào)用這個(gè)函數(shù)

[_bridge callHandler:@"testJavascriptHandler" data:@{ @"foo":@"before ready" }];

看一下這個(gè)方法的實(shí)現(xiàn)瓮增,在WebViewJavascriptBridge中

- (void)callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback {
    [_base sendData:data responseCallback:responseCallback handlerName:handlerName];
}

- (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName {
    NSMutableDictionary* message = [NSMutableDictionary dictionary];
    
    if (data) {
        message[@"data"] = data;
    }
    
    if (responseCallback) {//注意這里,如果有responseCallback哩俭,就會(huì)callbackId為key存起來
        NSString* callbackId = [NSString stringWithFormat:@"objc_cb_%ld", ++_uniqueId];
        self.responseCallbacks[callbackId] = [responseCallback copy];
        message[@"callbackId"] = callbackId;
    }
    
    if (handlerName) {
        message[@"handlerName"] = handlerName;
    }
    [self _queueMessage:message];
}

- (void)_queueMessage:(WVJBMessage*)message {
    if (self.startupMessageQueue) {
        [self.startupMessageQueue addObject:message];
    } else {
        [self _dispatchMessage:message];
    }
}

- (void)_dispatchMessage:(WVJBMessage*)message {
    NSString *messageJSON = [self _serializeMessage:message pretty:NO];
    [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]) {
        [self _evaluateJavascript:javascriptCommand];

    } else {
        dispatch_sync(dispatch_get_main_queue(), ^{
            [self _evaluateJavascript:javascriptCommand];
        });
    }
}

可見最終還是執(zhí)行到WebViewJavascriptBridge_js中的handleMessageFromObjC方法才能拿到要執(zhí)行的JS代碼钉赁,那我們看一下這里跟JS調(diào)用OC函數(shù)不同在哪里

function _dispatchMessageFromObjC(messageJSON) {
        if (dispatchMessagesWithTimeoutSafety) {
            setTimeout(_doDispatchMessageFromObjC);
        } else {
             _doDispatchMessageFromObjC();
        }
        
        function _doDispatchMessageFromObjC() {
            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 {//注意這次會(huì)進(jìn)入下面的判斷
                if (message.callbackId) {//如果有responseCallback,注意之前的處理
                    var callbackResponseId = message.callbackId;
//注意此處它為responseCallback賦值了一個(gè)函數(shù)携茂,當(dāng)執(zhí)行responseCallback時(shí)會(huì)執(zhí)行_doSend方法你踩,并且以responseId為key傳入了callbackResponseId
                    responseCallback = function(responseData) {
                        _doSend({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData });
                    };
                }
                
               //找到相應(yīng)的handler并執(zhí)行
                var handler = messageHandlers[message.handlerName];
                if (!handler) {
                    console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
                } else {
                    handler(message.data, responseCallback);
                }
            }
        }
    }

此處關(guān)鍵在于responseCallback是OC傳入的代碼塊,應(yīng)該由OC執(zhí)行讳苦,所以JS調(diào)用_doSend方法改變iframe.src發(fā)起請(qǐng)求带膜,通知OC執(zhí)行responseCallback

function _doSend(message, responseCallback) {
        if (responseCallback) {
            var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime();
            responseCallbacks[callbackId] = responseCallback;
            message['callbackId'] = callbackId;
        }
        sendMessageQueue.push(message);
        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
    }

然后OC攔截到請(qǐng)求的操作之前已經(jīng)過了一遍,那不同的地方在哪呢鸳谜,那就是有了responseId

- (void)flushMessageQueue:(NSString *)messageQueueString{
    if (messageQueueString == nil || messageQueueString.length == 0) {
        NSLog(@"WebViewJavascriptBridge: WARNING: ObjC got nil while fetching the message queue JSON from webview. This can happen if the WebViewJavascriptBridge JS is not currently present in the webview, e.g if the webview just loaded a new page.");
        return;
    }

    id messages = [self _deserializeMessageJSON:messageQueueString];
    for (WVJBMessage* message in messages) {
        if (![message isKindOfClass:[WVJBMessage class]]) {
            NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [message class], message);
            continue;
        }
        [self _log:@"RCVD" json:message];
        
        NSString* responseId = message[@"responseId"];
        if (responseId) {//有了responseId膝藕,這很關(guān)鍵,根據(jù)responseId找到responseCallback并執(zhí)行
            WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
            responseCallback(message[@"responseData"]);
            [self.responseCallbacks removeObjectForKey:responseId];
        } else {
            WVJBResponseCallback responseCallback = NULL;
            NSString* callbackId = message[@"callbackId"];
            if (callbackId) {
                responseCallback = ^(id responseData) {
                    if (responseData == nil) {
                        responseData = [NSNull null];
                    }
                    
                    WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
                    [self _queueMessage:msg];
                };
            } else {
                responseCallback = ^(id ignoreResponseData) {
                    // Do nothing
                };
            }
            
            WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];
            
            if (!handler) {
                NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);
                continue;
            }
            
            handler(message[@"data"], responseCallback);
        }
    }
}

找到了之前存儲(chǔ)的responseCallback并執(zhí)行咐扭,到此OC調(diào)用JS注冊(cè)的函數(shù)流程完畢芭挽。

三.總結(jié)

受限于JS知識(shí)水平不足滑废,有些地方分析的不太好,不對(duì)之處還望指正袜爪,不勝感激蠕趁!另外我還寫了一篇JavaScriptCore的使用介紹,JavaScriptCore是蘋果提供的一套用于與JS交互的框架辛馆,感興趣的朋友可以點(diǎn)這里看一下

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末俺陋,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子昙篙,更是在濱河造成了極大的恐慌腊状,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件苔可,死亡現(xiàn)場(chǎng)離奇詭異缴挖,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)焚辅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門映屋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人法焰,你說我怎么就攤上這事秧荆。” “怎么了埃仪?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵乙濒,是天一觀的道長。 經(jīng)常有香客問我卵蛉,道長颁股,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任傻丝,我火速辦了婚禮甘有,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘葡缰。我一直安慰自己亏掀,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布泛释。 她就那樣靜靜地躺著滤愕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪怜校。 梳的紋絲不亂的頭發(fā)上间影,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音茄茁,去河邊找鬼魂贬。 笑死巩割,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的付燥。 我是一名探鬼主播宣谈,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼机蔗!你這毒婦竟也來了蒲祈?” 一聲冷哼從身側(cè)響起甘萧,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤萝嘁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后扬卷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體牙言,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年怪得,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了咱枉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡徒恋,死狀恐怖蚕断,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情入挣,我是刑警寧澤亿乳,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站径筏,受9級(jí)特大地震影響葛假,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜滋恬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一聊训、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧恢氯,春花似錦带斑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至指黎,卻和暖如春朋凉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背醋安。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來泰國打工杂彭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留墓毒,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓亲怠,卻偏偏與公主長得像所计,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子团秽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

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