Webview秒開框架VasSonic源碼分析(一)

轉(zhuǎn)載請注明出處:
Webview秒開框架VasSonic源碼分析(一)

Webview秒開框架VasSonic源碼分析(二)

地址:http://www.lxweimin.com/p/802129f77c20

目錄

image

起初想把代碼貼上來,一點(diǎn)點(diǎn)分析。但是鑒于VasSoinc中Webview和SonicSession并行執(zhí)行協(xié)同交流時(shí),各狀態(tài)是不確定的,各因素排列組合下來有n多種情況。一個(gè)個(gè)分析讀者會(huì)被繞暈,結(jié)果反而不好。于是便基于源碼,總結(jié)性分析,這樣更好一些。
特別感謝一下騰訊VasSonic框架負(fù)責(zé)人之一的陳同學(xué)@lovekidchen,感謝他的幫助,幫我理解了不少問題。

1 先說幾個(gè)概念

說正文之前,先說下webview的幾個(gè)概念。方便下面的講解。

1.1 webviewClient#shouldInterceptRequest

public WebResourceResponse shouldInterceptRequest(WebView view,  
String url) {  
     return null;  
}  

webview加載頁面整個(gè)過程中(包括點(diǎn)擊webview中的超鏈接),當(dāng)需要加載任何資源請求時(shí),可以通過這個(gè)方法攔截。通過返回WebResourceResponse,讓webview可以加載本地提供的資源,而不再需要webview自己加載url對(duì)應(yīng)的資源。里面的邏輯需要在非ui線程進(jìn)行。
這個(gè)方法在21之后被廢棄了,改為

public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) 

不過使用前面那個(gè)也沒有問題。

1.2 webviewClient#onPageFinished

public void onPageFinished(WebView view, String url) {
''               
''  }

這個(gè)方法在webview成功加載完成html后會(huì)被回調(diào)。

1.3 JS調(diào)用android代碼

todo

1.4 webview的初始化包括哪些

webview 的初始化不只是findViewById來獲取webview的實(shí)例。webview 的初始化包括webview實(shí)例化、設(shè)置webSettings、設(shè)置webViewClient等,如果是第一次初始化webview,那么時(shí)間消耗大約是大幾百毫秒。webview初始化示例:

// 實(shí)例化webview
WebView webView = (WebView) findViewById(R.id.webview);
// 設(shè)置webViewClient
webView.setWebViewClient(new WebViewClient() {
    @Override
    public void onPageFinished(WebView view, String url) {
                //
    }
    @TargetApi(21)
    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
        return shouldInterceptRequest(view, request.getUrl().toString());
    }
    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
        if (sonicSession != null) {
        //step 6: Call sessionClient.requestResource when host allow the application
         // to return the local data .
        return (WebResourceResponse) sonicSession.getSessionClient().requestResource(url);
        }
        return null;
        }
    });
//設(shè)置webSettings
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
    webView.removeJavascriptInterface("searchBoxJavaBridge_");  intent.putExtra(SonicJavaScriptInterface.PARAM_LOAD_URL_TIME, System.currentTimeMillis());
        webView.addJavascriptInterface(new SonicJavaScriptInterface(sonicSessionClient, intent), "sonic");
        webSettings.setAllowContentAccess(true);
        webSettings.setDatabaseEnabled(true);
        webSettings.setDomStorageEnabled(true);
        webSettings.setAppCacheEnabled(true);
        webSettings.setSavePassword(false);
        webSettings.setSaveFormData(false);
        webSettings.setUseWideViewPort(true);
        webSettings.setLoadWithOverviewMode(true);

2 VasSonic特點(diǎn)

VasSonic是騰訊開源的、解決webview首屏提速的框架。這里先說下VasSonic的主要特點(diǎn):

  • 分離webview初始化和web頁面主url(html)的請求。
  • 主url(html)請求的數(shù)據(jù)進(jìn)行了本地緩存。除了緩存整個(gè)html,還對(duì)緩存進(jìn)行了更精細(xì)化的分割,分為:
    模板(html string中不經(jīng)常變動(dòng)的字符串)和html string中經(jīng)常變動(dòng)的字符串。
    首先這么分割的對(duì)象是主url對(duì)應(yīng)的html字符串,js/css/圖片資源還未請求加載,都以string的形式存在。分割時(shí)注意,不一定模板就是css,js,非模板就是數(shù)據(jù)。有些css和js也經(jīng)常變動(dòng),有些頁面數(shù)據(jù)也不會(huì)變。根據(jù)業(yè)務(wù)場景分割html 字符串。分割標(biāo)記需要前端來支持。如果請求時(shí)發(fā)現(xiàn)模板沒變,只是非模板的數(shù)據(jù)變了,那么可以實(shí)現(xiàn)頁面的局部刷新。局部刷新時(shí),需要js調(diào)android代碼。

3 web界面的兩種加載方式 :預(yù)加載和常規(guī)加載

說web界面進(jìn)行常規(guī)加載之前,先說下web界面的預(yù)加載模式。

3.1 web界面預(yù)加載方式

3.1.1 預(yù)加載整個(gè)html

預(yù)加載指開發(fā)者預(yù)先判斷哪些頁面可能會(huì)被用戶打開,預(yù)加載這些web頁面。比如貓眼資訊列表/頭條資訊列表等。預(yù)加載時(shí)先把html從服務(wù)器拉下來(都是文本,所以銷毀流量少,用戶可以接受)。然后等打開這個(gè)頁面時(shí),使用webview.loadDataWithBaseURL(html)加載頁面。之后進(jìn)行css、js、圖片加載時(shí),可以使用webviewClient#shouldInterceptRequest加載本地資源(apk asset目錄下可以預(yù)置一些資源)。

3.1.2 apk預(yù)置模板

如果業(yè)務(wù)形式像貓眼的feed流那樣,所有的資訊幾乎模樣都是一樣的。那么我們甚至可以把html的模板(概念等同于上面提到的模板)放到apk的asset目錄下。這樣預(yù)加載時(shí)只加載非模板部分(文字,圖片url,css等字符串),等用戶打開那一頁的時(shí)候,拼接非模板數(shù)據(jù)和模板為整個(gè)的html字符串。之后的流程和上面是一樣的,使用webview.loadDataWithBaseURL(html)加載頁面。之后進(jìn)行css、js、圖片加載時(shí),可以使用webviewClient#shouldInterceptRequest加載本地資源。

3.1.3 web界面采用預(yù)加載時(shí)VasSonic的使用方式

因?yàn)閂asSonic分離了webview初始化和主url(html)的請求。所以VasSonic本身支持預(yù)加載(調(diào)用sonicSession#preload()方法)。

不過,預(yù)加載不一定需要VasSonic的參與。即使你不使用VasSonic也可以進(jìn)行預(yù)加載。使用你app中的網(wǎng)絡(luò)框架將html加載下拉。然后等打開頁面時(shí),使用webview.loadDataWithBaseURL(html)進(jìn)行加載。

說下VasSonic的預(yù)加載過程。在沒打開頁面時(shí),使用sonnicSession#preload來加載數(shù)據(jù)。把html數(shù)據(jù)放到本地的某個(gè)位置,當(dāng)打開頁面后,使用webview.loadDataWithBaseURL(html)直接加載。(當(dāng)然html中的css ,js等url對(duì)應(yīng)的資源還沒有加載。VasSonic目前的版本并沒有對(duì)這些副資源進(jìn)行預(yù)加載或這些副資源的本地存儲(chǔ)好消息是下一個(gè)版本會(huì)支持。你可以自己預(yù)置一些css、js、圖片的資源到apk中,使用WebviewClient#shouldInterceptRequest自己攔截加載)。

3.2 web頁面的常規(guī)加載過程:

3.2.1 常規(guī)加載過程

當(dāng)我們不使用預(yù)加載時(shí),我們打開一個(gè)頁面,需要一下流程加載頁面:
初始化webview->webview進(jìn)行主框架url加載請求(html)->成功后,進(jìn)行css,js,圖片的請求(圖片可以等界面顯示時(shí)再加載)->顯示界面
(圖,這里應(yīng)該有個(gè) 圖,todo)

3.2.2 web頁面常規(guī)加載過程中的時(shí)間浪費(fèi)

我們看下可能存在的時(shí)間浪費(fèi):

  • 首次初始化webview會(huì)銷毀幾百毫秒時(shí)間。webview初始化之前是不能進(jìn)行主html加載的。這個(gè)等待時(shí)間浪費(fèi)掉了。
  • 主url(html)請求成功后,才能進(jìn)行css,js等資源的請求。因?yàn)橹挥械鹊絟tml獲取到以后,才知道css、js、圖片的url是什么。這個(gè)問題的優(yōu)化方案可以采用把資源放到本地。前面說了,目前的VasSonic沒有對(duì)js/css等副資源的加載、緩存做優(yōu)化。使用webviewClient#onIntercepterRequest來進(jìn)行本地資源加載。
    綜上,我們接下來就看下VasSonic對(duì)第一個(gè)點(diǎn)是怎么優(yōu)化的。

3.2.3 webview的初始化和sonicSession的并發(fā)進(jìn)行

對(duì)于上面的第一個(gè)問題,VasSnoic是這么解決的:webview的初始化和主url(html)請求(之后統(tǒng)稱SonicSession)并發(fā)進(jìn)行。

3.2.3.1 webview初始化流程:

webview的初始化主要包括webview的實(shí)例化、webviewSetting的設(shè)置、webviewClient的設(shè)置。示例代碼前面已經(jīng)給出了。這里要說的是,在我們初始化完成后,要手動(dòng)調(diào)用(代碼中sonicSessionClient是webview的代理類。因?yàn)橛锌赡懿煌捻?xiàng)目用的webview不一樣)

if (sonicSessionClient != null) {
            sonicSessionClient.bindWebView(webView);
            sonicSessionClient.clientReady();
        } 

通知SonicSession webview已經(jīng)初始化好了。

3.2.3.2 SonicSession數(shù)據(jù)請求流程:

如果有緩存,先加載緩存(通知webview加載)。然后(不管有沒有緩存),從服務(wù)器獲取html。發(fā)送請求獲取服務(wù)端的html時(shí),VasSonic的第一個(gè)版本需要我們在請求中提供一些自定義的header(緩存html的摘要sha1,緩存html模板的摘要sha1)。這樣服務(wù)端根據(jù)這些摘要來判斷決定返回整個(gè)html或只返回html中非模板的部分(返回的整個(gè)html或部分html通知webview刷新)。第一個(gè)版本的VasSonic需要后臺(tái)的參與。為了讓框架對(duì)后臺(tái)透明,降低接入難度。VasSonic的2.0在版本中,不需要后臺(tái)的參與。自己模擬后臺(tái)的行為。即,在任何時(shí)候,都會(huì)從服務(wù)端拉取整個(gè)html,分割成模板和非模板。然后與本地的html的模板和非模板摘要對(duì)比。如果整個(gè)html都相同,那么什么都不做;如果模板相同,那么通知webview只刷新頁面中的非模板區(qū)域;如果都不相同,那么通知webview刷新整個(gè)頁面。

3.2.4 webview的初始化和sonicSession的協(xié)同合作

3.2.4.1 協(xié)同合作的通信機(jī)制和時(shí)機(jī)

既然webview的初始化和sonicSession請求數(shù)據(jù)并發(fā)進(jìn)行,那么肯定需要進(jìn)行通信交流來進(jìn)行協(xié)同合作。那使用什么機(jī)制什么時(shí)候來通知對(duì)方呢?

3.2.4.1.1 webview向SonicSession發(fā)送信號(hào)
  1. 前面提到了,當(dāng)webview初始化好了之后,會(huì)手動(dòng)調(diào)用sonicSessionClient#clientReady()來通知sonicSession webview已經(jīng)初始化好了。
  2. 如果沒有緩存時(shí),框架內(nèi)部調(diào)用webview.load(url)時(shí)(url為html對(duì)應(yīng)的url),webviewClient#shouldInterceptRequest會(huì)被觸發(fā),會(huì)通知sonicSession把已經(jīng)加載到內(nèi)存的資源傳遞給webview,讓webview加載。其實(shí)質(zhì)是”webviewClient#shouldInterceptRequest觸發(fā)后,中斷sonicSession的加載,把已經(jīng)解析到內(nèi)存的流和節(jié)點(diǎn)流包成一個(gè)自定義InputStream流“傳遞給webview,webview加載這個(gè)流時(shí)會(huì)先加載內(nèi)存中的流,再加載節(jié)點(diǎn)流。
  3. 1.0版本中,如果有緩存時(shí)先加載緩存。當(dāng)緩存頁面加載完成后,會(huì)觸發(fā)webviewClient#onPageFinished,繼而通知sonicSession中斷服務(wù)器端數(shù)據(jù)的加載,把已經(jīng)解析到內(nèi)存的流和節(jié)點(diǎn)流包成一個(gè)自定義InputStream流“傳遞給webview,webview加載這個(gè)流時(shí)會(huì)先加載內(nèi)存中的流,再加載節(jié)點(diǎn)流。不過,在2.0版本中,如果有緩存時(shí),也是先加載緩存。然后啟動(dòng)服務(wù)端的數(shù)據(jù)加載,一直加載直到html加載完成,不會(huì)受緩存頁面加載成功信號(hào)的中斷。多說一句,為什么這里會(huì)一直加載html直到結(jié)束呢?因?yàn)?.0版本進(jìn)行了后臺(tái)模擬的工作,拉取服務(wù)端完整的html和緩存中的html做etag,templetag對(duì)比,自己模擬來使response返回相應(yīng)的header。這樣就不需要后臺(tái)的參與了。也因?yàn)檫@個(gè),所以需要一直加載服務(wù)端html直到html加載完成。
    上面三點(diǎn)講的是webview向SonicSession發(fā)送信號(hào)。簡單來說就是webview初始化好的時(shí)候、webviewClient#shouldInterceptRequest被觸發(fā)時(shí)、webviewClient#onPageFinished被觸發(fā)時(shí)。
3.2.4.1.2 SonicSession向webview發(fā)送信號(hào)

那么SonicSession在加載過程中,也會(huì)發(fā)送信號(hào)給webview,讓其加載數(shù)據(jù)。通過前面的SonicSession工作過程分析,知道sonicSession會(huì)先進(jìn)行緩存加載,然后進(jìn)行服務(wù)端html加載。服務(wù)端html加載時(shí)(1.0)/加載后(2.0),分析webview需要首次加載還是非模板數(shù)據(jù)更新還是模板跟新,數(shù)據(jù)加載和分析邏輯在VasSonic中對(duì)應(yīng):

    protected abstract void handleFlow_FirstLoad();
    protected abstract void handleFlow_DataUpdate(String serverRsp);
    protected abstract void handleFlow_TemplateChange(String newHtml);

獲取到相應(yīng)的數(shù)據(jù)后,就需要發(fā)送信號(hào)通知webview進(jìn)行加載。因?yàn)镾onicSeesion的加載數(shù)據(jù)過程是在異步線程,webview加載數(shù)據(jù)在主線程。所以需要handler 發(fā)送message來通知。我們看一眼VasSonic 的handler處理邏輯:

public boolean handleMessage(Message msg) {

        // fix issue[https://github.com/Tencent/VasSonic/issues/89]
        if (super.handleMessage(msg)) {
            return true; // handled by super class
        }

        if (CLIENT_CORE_MSG_BEGIN < msg.what && msg.what < CLIENT_CORE_MSG_END && !clientIsReady.get()) {
            pendingClientCoreMessage = Message.obtain(msg);
            SonicUtils.log(TAG, Log.INFO, "session(" + sId + ") handleMessage: client not ready, core msg = " + msg.what + ".");
            return true;
        }

        switch (msg.what) {
            case CLIENT_CORE_MSG_PRE_LOAD:
                handleClientCoreMessage_PreLoad(msg);
                break;
            case CLIENT_CORE_MSG_FIRST_LOAD:
                handleClientCoreMessage_FirstLoad(msg);
                break;
            case CLIENT_CORE_MSG_CONNECTION_ERROR:
                handleClientCoreMessage_ConnectionError(msg);
                break;
            case CLIENT_CORE_MSG_SERVICE_UNAVAILABLE:
                handleClientCoreMessage_ServiceUnavailable(msg);
                break;
            case CLIENT_CORE_MSG_DATA_UPDATE:
                handleClientCoreMessage_DataUpdate(msg);
                break;
            case CLIENT_CORE_MSG_TEMPLATE_CHANGE:
                handleClientCoreMessage_TemplateChange(msg);
                break;
            case CLIENT_MSG_NOTIFY_RESULT:
                setResult(msg.arg1, msg.arg2, true);
                break;
            case CLIENT_MSG_ON_WEB_READY: {
                diffDataCallback = (SonicDiffDataCallback) msg.obj;
                setResult(srcResultCode, finalResultCode, true);
                break;
            }

            default: {
                if (SonicUtils.shouldLog(Log.DEBUG)) {
                    SonicUtils.log(TAG, Log.DEBUG, "session(" + sId + ") can not  recognize refresh type: " + msg.what);
                }
                return false;
            }

        }
        return true;
    }

處理的情況還是不少的。因?yàn)椴淮_定因素導(dǎo)致協(xié)同合作是一個(gè)動(dòng)態(tài)的過程。所以協(xié)同起來還是很繁瑣的。

3.2.4.2 協(xié)同合作的動(dòng)態(tài)性

上面講的是什么時(shí)候發(fā)送信號(hào),但發(fā)送信號(hào)后的處理是不確定的。舉個(gè)例子,當(dāng)webview初始化完成時(shí),SonicSession可能已經(jīng)加載好了緩存數(shù)據(jù),但可能沒加載好網(wǎng)絡(luò)數(shù)據(jù),也可能是其他情況。反過來sonicSession下載好了數(shù)據(jù)讓webview進(jìn)行加載緩存/刷新模板/模板更新/第一次加載時(shí),webview初始化有么有完成不確定。或者讓webview進(jìn)行模板更新時(shí),webview這時(shí)候初始化完成情況不確定,有沒有加載緩存不確定。這些不確定因素導(dǎo)致,整個(gè)協(xié)同工作是一個(gè)排列組合多因素動(dòng)態(tài)的過程。所以
如果在這里如果每一種情況都進(jìn)行分析,顯然是不現(xiàn)實(shí)的。但是,通讀完他們的代碼,我有這么一個(gè)感覺:他們把能減少的時(shí)間等待都利用了,用來做一些其他的事情,或者想辦法減少這種等待時(shí)間。所以對(duì)于協(xié)同合作的動(dòng)態(tài)性,我們也可以有個(gè)整體的把握。說到這里,其實(shí)我在擼他們代碼的時(shí)候,就越發(fā)的佩服騰訊團(tuán)隊(duì)做出的這個(gè)框架。配合他們的耐心與做事情追求極致的精神。

3.2.4.3 協(xié)同合作情況舉例

接下來就分析webview和sonicSession通信協(xié)同合作的兩種常見情況,其他情況可以直接閱讀源碼進(jìn)行分析。
既然是常見情況,那么我們做以下兩點(diǎn)假設(shè):

  1. SonicSession緩存加載時(shí)間比webview的初始化時(shí)間要長。
  2. SonicSession服務(wù)器加載html的時(shí)間比SonicSession緩存加載時(shí)間要長。
3.2.4.3.1 本地有緩存時(shí)

當(dāng)本地有緩存時(shí),那么我們分析一下這種情況。

  1. webview初始化與SonicSession同時(shí)進(jìn)行。
  2. SonicSession進(jìn)行緩存加載,加載好緩存數(shù)據(jù)以后,封裝成message通過handler傳遞主線程looper 隊(duì)列中。即,通知webview加載緩存數(shù)據(jù)。webview這時(shí)候還沒初始化好,那么會(huì)把之前的message再封裝成pendingClientCoreMessage存起來。等到webview初始化好以后,會(huì)再把這個(gè)message傳遞到主線程looper隊(duì)列中等待webview加載。
  3. SonicSession開始從服務(wù)端加載html數(shù)據(jù)。如果是2.0的情況并且我們設(shè)置使用純終端模式,那么SonicSession會(huì)把服務(wù)端的整個(gè)html數(shù)據(jù)全部加載下來(此過程不會(huì)被外界中斷)。然后模擬得到etag,templeTag等response 的header值。然后通過handler通知webview進(jìn)行局部刷新/模板更新。這里的具體情況可以看我寫的 Webview秒開框架VasSonic源碼分析(二)-1.0與2.0版本的不同
3.2.4.3.2 本地沒有緩存時(shí)-“截流加載”

如果本地沒有緩存時(shí),我們分析一下這種情況。

  1. webview初始化與SonicSession同時(shí)進(jìn)行。

  2. SonicSession從服務(wù)端加載html。

  3. webview初始化好了,這時(shí)候服務(wù)端的html數(shù)據(jù)還沒加載完成。如果等到html加載完成以后再讓webview進(jìn)行加載,那么這里就有一個(gè)等待情況。為了避免這種等待浪費(fèi)。VasSonic是這么做的:webview初始化好時(shí),會(huì)發(fā)送一個(gè)信號(hào),讓SonicSession中斷其網(wǎng)絡(luò)加載。中斷時(shí),會(huì)把已經(jīng)加載的數(shù)據(jù)放到內(nèi)存的outputSream流和serverRsp 字符串中,然后把內(nèi)存outputSream流和未加載的節(jié)點(diǎn)流包成SonicSessionStream返回。sonicInputstream#read時(shí)會(huì)先讀取內(nèi)存中的流,再讀取節(jié)點(diǎn)流中的數(shù)據(jù):

      public synchronized InputStream getResponseStream(AtomicBoolean breakConditions) {
             if (readServerResponse(breakConditions)) {
                 BufferedInputStream netStream = !TextUtils.isEmpty(serverRsp) ? null : connectionImpl.getResponseStream();
                 return new SonicSessionStream(this, outputStream, netStream);
             } else {
                 return null;
             }
         }
    

代碼中的breakConditions就是外界的中斷信號(hào)。當(dāng)內(nèi)部有調(diào)用webview.load(url)時(shí),webviewClient#shouldInterceptRequest會(huì)被觸發(fā),進(jìn)而把sonicInputstream傳遞給webview進(jìn)行解析渲染。

4 如果前端不介入,使用VasSonic框架的好處

2.0版本不需要后臺(tái)介入,但需要前端配合來進(jìn)行局部刷新。如果前端不介入。那么優(yōu)點(diǎn)就是

  • 首次加載,利用webview初始化的時(shí)間進(jìn)行服務(wù)端html的加載,然后webview初始化后,webview“截流加載”。
  • 如果有緩存,那么webview先加載緩存。再進(jìn)行模板更新,因?yàn)闆]有前端配合,沒有模板概念,也沒有局部刷新。這時(shí)候加載的是新的完整的html。

5 感謝/參考資料

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

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