WK 與 JS 的那些事

我們的小馬童鞋又發(fā)功了。最近打算將UIWebView替換成WKWebView,所以原來的Hybrid層需要?jiǎng)觿油粒●R小試牛刀。當(dāng)然遇到了一些問題,看看他是怎么一步步解決的吧。

蘋果在iOS 8中推出了 WKWebView,這是一個(gè)高性能的 web 框架,相較于 UIWebView 來說,有巨大提升。本文將針對 WKWebView 進(jìn)行簡單介紹,然后介紹下如何和 JS 進(jìn)行愉快的交互。還望各位大佬不吝賜教。

本文分為兩大部分

  1. WKWebView 簡單介紹
  2. JS 交互

1 WKWebView

就目前移動開發(fā)趨勢來說,很多 APP 都會嵌套一些 H5 的應(yīng)用。H5 有一些 Native 無法比擬的優(yōu)勢,例如:更新快,不用發(fā)版,隨時(shí)上線等等。然而在 iOS 中, UIWebView 是及其難用的。隨著 iOS 8 的推出,Apple 重構(gòu)了 UIWebView,于是 WKWebView 橫空出世。

1.1 WKWebView VS UIWebView

根據(jù)官方文檔,我們來簡單對比一下 UIWebView 和 WKWebView,看看這兩個(gè)到底有什么區(qū)別

WKWebView UIWebView
內(nèi)存占用 大 且有內(nèi)存泄漏
加載速度
與 JS 交互 難 (可與 JSCore 配合)
幀率 60FPS 掉幀

從文檔來看,二者區(qū)別還是很明顯的,但到底區(qū)別有多大的,我們用數(shù)據(jù)說話。打開京東,網(wǎng)易,新浪這三個(gè)網(wǎng)站,從打開時(shí)間和占用內(nèi)存上來比較一下,看誰能勝出。該測試在 2015款 MBP 上打開,使用 Xcode 9 GM 版,在 iPhone 8 Plus 上運(yùn)行

使用 WKWebView 和 UIWebView 打開 京東 網(wǎng)易 新浪 三個(gè)網(wǎng)站所耗費(fèi)的時(shí)長
使用 WKWebView 和 UIWebView 打開 京東 網(wǎng)易 新浪 三個(gè)網(wǎng)站所耗費(fèi)的內(nèi)存

在內(nèi)存測試中發(fā)現(xiàn),UIWebView 占用內(nèi)存很不穩(wěn)定,在打開新浪的網(wǎng)站時(shí),最高內(nèi)存能飆升到 200m 后來慢慢回落到 160m 左右,但會上下波動。但 WKWebView 上就沒有這個(gè)問題。通過上述對比,不難看出,WKWebVeiw 要優(yōu)于 UIWebView。

1.2 如何使用 WKWebView

得益于蘋果 API 的高度封裝,我們使用 WKWebView 及其簡單

- (WKWebView *)wkWebView {
    if (!_wkWebView) {
        
        _wkWebView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:[WKWebViewConfiguration new]]; //1. 
        NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.jd.com"]]; //2.
        [_wkWebView loadRequest:request]; //3. 
    }
    return _wkWebView;
}
  1. 初始化一個(gè) WKWebView,我們需要傳一個(gè) WKWebViewConfiguration 對象,來對 WKWebView 進(jìn)行配置。
  2. 構(gòu)造一個(gè)請求。
  3. 加載這個(gè)請求。

只需要這三步,我們就可以使用一個(gè)高性能的 web 框架。是不是很贊!!!
關(guān)于 WKWebView 如何使用,這里就不做過多的詳細(xì)介紹了,網(wǎng)上這種文章太多了,大家可以自行翻閱。接下來我們說如何與 JS 交互。

2. JS 交互

WebVeiw 與 JS 交互是一個(gè)很古老的問題,如何與 JS 交互是一個(gè) WebVeiw 必須具備的能力,在 UIWebView 時(shí)代,我們可以通過攔截 URL 的方式來進(jìn)行交互,也可以通過 WebViewJavascriptBridge 來進(jìn)行交互,還可以配合 JSCore 來進(jìn)行交互。但是在 WKWebView 時(shí)代,由于它是在一個(gè)單獨(dú)的進(jìn)程中運(yùn)行,我們無法獲取到 JSContext,所以我們無法使用 JSCore 這個(gè)強(qiáng)大的框架來進(jìn)行交互,那我們怎么辦呢,且聽我一一道來。

2.1 Native 調(diào)用 JS

還記的上邊說的 WKWebViewConfiguration 么,在這個(gè)類里邊,有一個(gè)屬性

@property (nonatomic, strong) WKUserContentController *userContentController;

Native 和 H5 交互基本全靠這個(gè)對象, 在 WKWebVeiw 中,我們使用我們有兩種方式來調(diào)用 JS,

  1. 使用 WKUserScript
  2. 直接調(diào)用 JS 字符串

2.1.1 使用 WKUserScript

要想使用 WKUserScript,首先,我們要構(gòu)造一個(gè) WKUserScript 對象,構(gòu)造方法及其簡單,我們使用下邊代碼來創(chuàng)建一個(gè) WKUserScript 對象。

// source 就是我們要調(diào)用的 JS 函數(shù)或者我們要執(zhí)行的 JS 代碼
// injectionTime 這個(gè)參數(shù)我們需要指定一個(gè)時(shí)間,在什么時(shí)候把我們在這段 JS 注入到 WebVeiw 中,它是一個(gè)枚舉值,WKUserScriptInjectionTimeAtDocumentStart 或者 WKUserScriptInjectionTimeAtDocumentEnd
// MainFrameOnly 因?yàn)樵?JS 中,一個(gè)頁面可能有多個(gè) frame,這個(gè)參數(shù)指定我們的 JS 代碼是否只在 mainFrame 中生效
- initWithSource:injectionTime:forMainFrameOnly:

至此,我們已經(jīng)構(gòu)建了一個(gè) WKUserScript,然后呢,我們要做的就是要把它添加進(jìn)來

- addUserScript:

至此使用 WKUserScript 調(diào)用 JS 完成。

2.1.2 直接調(diào)用 JS 字符串

在 WKWebView 中,我們也可以直接執(zhí)行 JS 字符串

- (void)evaluateJavaScript: completionHandler:

我們通過調(diào)用這個(gè)方法來執(zhí)行 JS 字符串,然后在 completionHandler 中拿到執(zhí)行這段 JS 代碼后的返回值。

至此,Native 調(diào)用 JS 完成。是不是簡單到害怕

2.2 JS 調(diào)用 Native

在 WK 這套框架下,JS 調(diào)用 Native 簡直簡單到喪心病狂。還記的上邊那個(gè) WKUserContentController,我們也是要通過它來進(jìn)行,而你所需要做的,只需要三步,需要三步,三步。

  1. 向 JS 注入一個(gè)字符串
[_webView.configuration.userContentController addScriptMessageHandler:self name:@"nativeMethod"];

我們向 JS 注入了一個(gè)方法,叫做 nativeMethod

  1. JS 調(diào)用 Native
window.webkit.messageHandlers.nativeMethod.postMessage(value);

一句話調(diào)用,我們就可以在 Native 中接收到 value

  1. 接收 JS 調(diào)用

上邊我們調(diào)用 addScriptMessageHandler:name 的時(shí)候,我們要遵守 WKScriptMessageHandler 協(xié)議,然后實(shí)現(xiàn)這個(gè)協(xié)議。

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
 NSString * name = message.name // 就是上邊注入到 JS 的哪個(gè)名字,在這里是 nativeMethod
 id param = message.body // 就是 JS 調(diào)用 Native 時(shí),傳過來的 value
 // TODO: do your stuff
}

完了,Native 調(diào)用 JS 就這么簡單,是不是喪心病狂,簡直簡單到不能再簡單了。

但是,你以為這么就完了么,上邊寫的這些東西在網(wǎng)上隨便一搜都有一大片,重新再寫一遍,貌似意義不是很大啊,怎么也得來點(diǎn)稍微不一樣的東西吧。

2.3 JS 調(diào)用 Native 后的回調(diào)

舉一個(gè)很常見的例子,假設(shè)我們有這么一個(gè)需求,我的 JS 要調(diào)用 Native 發(fā)一個(gè)網(wǎng)絡(luò)請求,Native 執(zhí)行完了,把請求數(shù)據(jù)回傳給 JS。
很簡單的一個(gè)需求,來,想想怎么執(zhí)行。

2.3.1 postMessage 的坑

可能很快就想到了,postMessage 的時(shí)候,直接把這個(gè)方法傳過去不就行了。一開始我也是這么做的。

    const person = {
        firstName: "John",
        lastName: "Doe",
        age: 50,
        eyeColor: "blue",
    };
    document.getElementById("li1").onclick = function (nativeValue) {
        person.callBack = function () {
            console.log("native call");
        }
        window.webkit.messageHandlers.nativeMethod.postMessage(person);
    };

首先構(gòu)造一個(gè) person,然后我們給 person 增加一個(gè) callBack 屬性,然后傳進(jìn)去,運(yùn)行程序。打開 Safari 選擇 開發(fā)->模擬器,打開調(diào)試界面,然后我們點(diǎn)擊查看控制臺。


然后你會發(fā)現(xiàn),報(bào)錯(cuò)了,為什么呢,這一切都是因?yàn)?postMessag 這個(gè)方法。
打開 postMessage文檔 ,你會發(fā)現(xiàn),

message
將要發(fā)送到其他 window的數(shù)據(jù)。它將會被結(jié)構(gòu)化克隆算法序列化。這意味著你可以不受什么限制的將數(shù)據(jù)對象安全的傳送給目標(biāo)窗口而無需自己序列化

這個(gè) message 需要支持 結(jié)構(gòu)化克隆算法 。很遺憾,這個(gè)算法目前不支持傳遞 FunctionError,它只支持一下幾種類型

對象類型 注意
所有的原始類型 除了symbols
Boolean 對象
String 對象
Date
RegExp lastIndex 字段不會被保留。
Blob
File
FileList
ArrayBuffer
ArrayBufferView 這基本上意味著所有的 類型化數(shù)組 ,比如 Int32Array 等等。
ImageData
Array
Object 僅包括普通對象 (比如對象字面量 )
Map
Set

說好的不受限制呢

15088520633631.jpg

2.3.2 function 轉(zhuǎn)為 字符串

那既然它不支持傳一個(gè) Function ,那我們就得另辟蹊徑了,String 總支持吧,我們把一個(gè)方法轉(zhuǎn)為字符串,然后傳到 Native,然后 Native 執(zhí)行這個(gè)字符串。貌似可行的,我們來試一下。

JS 代碼


    document.getElementById("li1").onclick = function () {

        person.callBack = function (nativeValue) {
            console.log("native call");
        }.toString();
        window.webkit.messageHandlers.nativeMethod.postMessage(person);
    };

OC 代碼


- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    
    if ([message.name isEqualToString:@"nativeMethod"]) {
        NSLog(@"body:%@, ", message.body);
        NSDictionary *dict = @{@"key1": @"value1",
                               @"key2": @"value2"
                               }; // 構(gòu)造回傳 js 數(shù)據(jù)
        id data = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:nil];
        NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; // 轉(zhuǎn)為 json 字符串
        [_webView evaluateJavaScript:[NSString stringWithFormat:@"(%@)(%@)", message.body[@"callBack"], jsonString] completionHandler:^(id _Nullable jsData, NSError * _Nullable error) {
            
        }];        
    }
}

果然不出我們所料,我們可以直接得到這個(gè) Native 傳遞給 JS 的值。
但是,這個(gè)作用域會不會變化呢,我們在來改一下 JS 代碼

document.getElementById("li1").onclick = function () {

    var arg1 = 100;
    var arg2 = 200;
    person.callBack = function (nativeValue) {
        console.log(nativeValue);
        console.log(arg1 + arg2);
    }.toString();
    window.webkit.messageHandlers.nativeMethod.postMessage(person);
};

大家猜能不能打印出來 300,我們來試一下。

完蛋,找不到 arg1。。。。

怎么回事呢?

我們把一個(gè) function 轉(zhuǎn)換成 字符串之后,傳給 Native,Native 在執(zhí)行的時(shí)候,他的作用域已經(jīng)變了,變成了 window,這個(gè)時(shí)候,window 下是沒有 arg1 和 arg2 的,所以我們找不到。

如果我們這么做的話,確實(shí)是可以實(shí)現(xiàn)上述的需求的,但是,這樣作用域就改變了,所有的變量都要定義為全局變量,函數(shù)要改為全局函數(shù),以遍能夠在回調(diào)中獲取正確的變量。

這確實(shí)是一個(gè)可行的方法,但有沒有更好的方法呢?H5 本來寫的好好的,匿名函數(shù)寫的 6 的飛起,干嘛都要改成全局變量,全局函數(shù),要是這么寫,我都不好意思給 H5 提需求讓人家改。

我就想,能不能像 UIWebView 一樣使用 JSCore,但是使用 JSCore 的話,我們要獲取 JSContext,而 WKWebView 是運(yùn)行在一個(gè)單獨(dú)的進(jìn)程中,我們是不可能進(jìn)行應(yīng)用間的通信的(目前我沒發(fā)現(xiàn),如果有的話,還請多多指教)。我就想,要不去扒一扒 WebKit 的源碼,看看會有什么發(fā)現(xiàn)。

2.3.3 改下源碼 ?

然后我就找啊找,終于找到了關(guān)鍵的方法

virtual void didPostMessage(WebKit::WebPageProxy& page, WebKit::WebFrameProxy& frame, const WebKit::SecurityOriginData& securityOriginData, WebCore::SerializedScriptValue& serializedScriptValue)
{
   @autoreleasepool {
       RetainPtr<WKFrameInfo> frameInfo = wrapper(API::FrameInfo::create(frame, securityOriginData.securityOrigin()));
    
       ASSERT(isUIThread());
       static JSContext* context = [[JSContext alloc] init]; //1. 創(chuàng)建一個(gè) JSContext
    
       JSValueRef valueRef = serializedScriptValue.deserialize([context JSGlobalContextRef], 0);
       JSValue *value = [JSValue valueWithJSValueRef:valueRef inContext:context];
       id body = value.toObject; // 把 JS 的類型轉(zhuǎn)為 OC 類型
    
       auto message = adoptNS([[WKScriptMessage alloc] _initWithBody:body webView:fromWebPageProxy(page) frameInfo:frameInfo.get() name:m_name.get()]); // 構(gòu)造 message
  
       [m_handler userContentController:m_controller.get() didReceiveScriptMessage:message.get()]; // 調(diào)用代理對象,傳遞 message
   }
}

看到這里,我想,能不能把這個(gè) JSContext 漏出來,這樣的話,說不定還能想 UIWebView 和 JSCore 一樣。但是轉(zhuǎn)念一想,WKWebView 從 iOS 8 就出現(xiàn)了,現(xiàn)在到 iOS 11 了,難道都沒想過如何解決回調(diào)這個(gè)問題么?難道蘋果那幫開發(fā)都沒發(fā)現(xiàn)么?怎么辦,這不科學(xué)啊。

2.3.4 我有一個(gè)同學(xué)

其實(shí),我們一開始就想錯(cuò)了。一直在想,如何把這個(gè)方法傳過來,其實(shí)縱使能把一個(gè) function 傳過來,我們也沒有辦法去執(zhí)行,因?yàn)槲覀兡軋?zhí)行的只有一個(gè)字符串,而這個(gè)字符串執(zhí)行后作用域肯定是會變的。所以,歸根到底,這是 H5 的工作,我們做不了,想要支持回調(diào),讓 H5 自己去研究。我敢保證,你如果這么去給 H5 說,他追出去三條街,也要把砍你。

我們要先幫 H5 解決這個(gè)問題,我們才能去推動 H5 解決這個(gè)問題。

然而,我有一個(gè)同學(xué),一個(gè)做 H5 的同學(xué),@勵(lì)志成為網(wǎng)紅的網(wǎng)黃,在我苦苦思索不能解決的時(shí)候,我給他說了我的問題。然后我們就這個(gè)問題和看法進(jìn)行了深入的探討和交流。在達(dá)成了某些不可描述的交易之后,我們終于找到了一種解決辦法。

他說,可以用 BroadcastChannel 來解決這個(gè)問題。

BroadcastChannel API 允許同一原始域和用戶代理下的所有窗口,iFrames等進(jìn)行交互。也就是說,如果用戶打開了同一個(gè)網(wǎng)站的的兩個(gè)標(biāo)簽窗口,如果網(wǎng)站內(nèi)容發(fā)生了變化,那么兩個(gè)窗口會同時(shí)得到更新通知。

然后進(jìn)行了一波研究之后,發(fā)現(xiàn) API 不支持。有興趣的可以研究這個(gè) API

然后,我們繼續(xù)進(jìn)行交易,好在,這次交易,取得了重大成功。
有一天,他在看 Vue 的源碼時(shí),發(fā)現(xiàn)了這么一個(gè)類 MessageChannel ,看起來可以解決這個(gè)問題。

官方文檔上這么說

Channel Messaging API的MessageChannel接口允許我們創(chuàng)建一個(gè)新的消息通道,并通過它的兩個(gè)MessagePort屬性發(fā)送數(shù)據(jù)

它有兩個(gè)端口,port1 和 port2,這兩個(gè)端口可以互相發(fā)消息,可以互相監(jiān)聽,這樣的話,我們是不是可以另辟蹊徑來解決這個(gè)問題呢,我們來看下代碼。

JS 代碼

document.getElementById("li1").onclick = function () {
    const  arg1 = 100;
    const  arg2 = 200;
    _postMessage(person, 'nativeMethod').then((val) => {
      // 6.
      console.log(val);
      console.log(arg1 + arg2);
    })
};
    
function _postMessage(val, name){
   var channel = new MessageChannel(); // 創(chuàng)建一個(gè) MessageChannel
   window.nativeCallBack = function(nativeValue) {
     // 3. 
     channel.port1.postMessage(nativeValue) 
   };
   // 1.
   window.webkit.messageHandlers[name].postMessage(val); 
   return new Promise((resolve, reject) => {
     channel.port2.onmessage = function(e){ 
         // 4
         var data = e.data;
         // 5.
         resolve(data); 
         channel = null;
         window.nativeCallBack = null;
     }
   })
}

我們封裝了一個(gè) _postMessage 方法,在這個(gè)方法中我們,返回了一個(gè) Promise 對象,其實(shí) JS 調(diào)用 Native 是一個(gè)異步操作,JS 調(diào)用客戶端,等待客戶端執(zhí)行完畢,執(zhí)行完畢后,告訴 JS,JS 在執(zhí)行接下來的操作。

OC 代碼


- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    
    if ([message.name isEqualToString:@"nativeMethod"]) {
       NSLog(@"body:%@, ", message.body);
       NSDictionary *dict = @{
           @"key1": @"value1",
           @"key2": @"value2"
       }; // 構(gòu)造回傳 js 數(shù)據(jù)
       id data = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:nil];
       NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; // 轉(zhuǎn)為 json 字符串        
    
       // 2
       [_webView evaluateJavaScript:[NSString stringWithFormat:@"%@(%@)", @"nativeCallBack", jsonString] completionHandler:^(id _Nullable jsData, NSError * _Nullable error) {
           
       }];
       
    }
}

在 OC 代碼中,我們構(gòu)造一個(gè) JSON ,然后執(zhí)行 JS nativeCallBack(jsonString) ,把構(gòu)造的 JSON 傳給 JS。

注意上邊代碼的注釋,我們來一步一步看,發(fā)生了什么。

  1. 把值傳給 Native。
  2. Native 接受到之后,調(diào)用 JS 的 nativeCallBack 方法。
  3. 接收到 Native 調(diào)用之后,channel 的 port1 把 Native 的值轉(zhuǎn)出去。
  4. channel 的 port2 接收到 port1 發(fā)送的值之后,在 prot2 的 onmessage 方法中接收。
  5. 執(zhí)行 Promise 的 then,并把 data 傳過去。
  6. then 接收到調(diào)用,執(zhí)行里邊的代碼。

那到底能不能執(zhí)行呢,我們運(yùn)行一下試試

哈哈哈,果然和我們預(yù)料的一樣,我只想說一句,

總結(jié)

上邊啰嗦了這么多,其實(shí)很簡單,利用 MessageChannel 端口轉(zhuǎn)發(fā)功能來解決作用域改變的問題,JS 不用傳遞方法給 Native,Native 直接調(diào)用一個(gè)統(tǒng)一的全局方法就行。交互簡單方便。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,802評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,762評論 2 372

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