知乎 Android 客戶端組件化實(shí)踐

更多移動(dòng)技術(shù)文章請(qǐng)關(guān)注本文集:知乎移動(dòng)平臺(tái)專(zhuān)欄

背景

知乎 Android 客戶端最早使用的是最常見(jiàn)的單工程 MVC 架構(gòu),所有業(yè)務(wù)邏輯都放在了主工程 Module 里轨帜,網(wǎng)絡(luò)層和一些公共代碼分別被抽成了一個(gè) Module∑枪荆現(xiàn)在看來(lái),當(dāng)時(shí)的業(yè)務(wù)線蚌父、產(chǎn)品功能及研發(fā)團(tuán)隊(duì)都比不上現(xiàn)在的體量和豐富度哮兰,遇到的問(wèn)題隨時(shí)組內(nèi)溝通就可以解決。所以在知乎穩(wěn)步發(fā)展的前幾年苟弛,并沒(méi)有遇到什么大的問(wèn)題喝滞。


早期架構(gòu)圖極簡(jiǎn)版

后來(lái)公司發(fā)展速度加快,拆分了多個(gè)獨(dú)立的事業(yè)部膏秫,每個(gè)事業(yè)部有獨(dú)立的 Android 開(kāi)發(fā)團(tuán)隊(duì)右遭,每個(gè)團(tuán)隊(duì)都有獨(dú)立開(kāi)發(fā)、測(cè)試和部署的需求;隨著業(yè)務(wù)規(guī)模的擴(kuò)大窘哈,早期的代碼耦合導(dǎo)致的問(wèn)題也逐漸顯現(xiàn)出來(lái)吹榴;開(kāi)發(fā)人員也越來(lái)越多,單工程的架構(gòu)在人員協(xié)作方面也顯得越來(lái)越力不從心滚婉。同時(shí)考慮到對(duì)未來(lái)可能出現(xiàn)的多應(yīng)用的支持图筹,我們開(kāi)始了工程的組件化重構(gòu)。今天我們會(huì)在這篇文章中分享我們組件化過(guò)程中的一些實(shí)踐让腹。

組件化實(shí)踐

我們使用的是多工程多倉(cāng)庫(kù)的方案远剩,即每個(gè)組件都有自己的獨(dú)立倉(cāng)庫(kù),均可獨(dú)立于主工程單獨(dú)運(yùn)行骇窍;主工程通過(guò) aar 依賴(lài)各個(gè)組件民宿,自身則逐漸被拆成一個(gè)殼的狀態(tài),不包含業(yè)務(wù)邏輯代碼像鸡。經(jīng)過(guò)一年多的不斷迭代活鹰,現(xiàn)在是這個(gè)樣子:


image.png

它包含 4 個(gè)層次:
主工程:除了一些全局配置和主 Activity 之外,不包含任何業(yè)務(wù)代碼只估。
業(yè)務(wù)組件:最上層的業(yè)務(wù)志群,每個(gè)組件表示一條完整的業(yè)務(wù)線,彼此之間互相獨(dú)立蛔钙。
基礎(chǔ)組件:支撐上層業(yè)務(wù)組件運(yùn)行的基礎(chǔ)業(yè)務(wù)服務(wù)锌云。
基礎(chǔ) SDK:完全業(yè)務(wù)無(wú)關(guān)的基礎(chǔ)代碼。
各層次職責(zé)清晰獨(dú)立吁脱,可以很方便的進(jìn)行拆解和組合桑涎;由于都有自己的版本,業(yè)務(wù)線可以獨(dú)立發(fā)版兼贡,隨時(shí)升級(jí)攻冷、回滾。

基本解耦方案

組件化的第一步就是對(duì)要拆出去的組件進(jìn)行解耦遍希,常見(jiàn)解耦方式有以下幾種:

(1) 公用代碼處理:
基礎(chǔ)業(yè)務(wù)邏輯分別拆成基礎(chǔ)組件
自身邏輯完整等曼、用于完成某一特定功能、不含業(yè)務(wù)邏輯的一組代碼凿蒜,獨(dú)立成 SDK
代碼量很小不足以拆分成單獨(dú)拆分的代碼和資源禁谦,我們統(tǒng)一放在一個(gè)專(zhuān)門(mén)建立的 common 組件中,并且嚴(yán)格限制 common 組件的增長(zhǎng)废封。隨著組件化的逐漸進(jìn)行州泊,common 應(yīng)該逐漸變小而不是增大。
碰巧被共同使用的一些代碼和資源片段漂洋,通常它們被復(fù)用只是因?yàn)楸婚_(kāi)發(fā)人員搜索到而直接使用了遥皂,很多時(shí)候某個(gè)資源已經(jīng)被 A 業(yè)務(wù)聲明了前綴力喷,但是由于沒(méi)有隔離,仍然會(huì)不可避免的被他人在 B 業(yè)務(wù)中強(qiáng)行復(fù)用渴肉,這時(shí)候如果 A 業(yè)務(wù)方要進(jìn)行一些修改冗懦,B 業(yè)務(wù)就會(huì)受到影響 —— 這種情況我們?cè)试S直接復(fù)制

(2) 初始化:有些組件有在應(yīng)用啟動(dòng)時(shí)初始化服務(wù)的需求爽冕,而且很多服務(wù)還是有依賴(lài)關(guān)系的仇祭,最初我們?yōu)槊總€(gè)組件都添加了一個(gè) init() 方法,但是并不能解決依賴(lài)順序問(wèn)題颈畸,需要每個(gè)組件都在 app 工程中按順序添加初始化代碼才能正常運(yùn)行乌奇,這使得不熟悉整套組件業(yè)務(wù)的人很難建立起一個(gè)可以獨(dú)立運(yùn)行的組件 app。因此我們開(kāi)發(fā)了一套多線程初始化框架眯娱,每個(gè)組件只要新建若干個(gè)啟動(dòng) Task 類(lèi)礁苗,并在 Task 中聲明依賴(lài)關(guān)系即可:

初始化 Task 示例
啟動(dòng)順序示例

這樣就解決了組件在主工程中堆積初始化代碼的問(wèn)題,在簡(jiǎn)化了代碼的同時(shí)還有加快啟動(dòng)速度的功效徙缴。

(3) 路由:界面間使用 Url 進(jìn)行跳轉(zhuǎn)试伙,不但實(shí)現(xiàn)了解耦,也統(tǒng)一了各端的頁(yè)面打開(kāi)方式于样。我們實(shí)現(xiàn)了一套靈活小巧的路由框架 ZRouter疏叨,它支持多組件、路由攔截穿剖、AB Test 蚤蔓、參數(shù)正則匹配、降級(jí)策略糊余、任意參數(shù)傳遞以及自定義跳轉(zhuǎn)等功能秀又,可以自定義路由的各個(gè)階段,完全滿足了我們的業(yè)務(wù)需求贬芥。

(4) 接口:除了頁(yè)面間的跳轉(zhuǎn)吐辙,不同業(yè)務(wù)之間不可避免的會(huì)有一些調(diào)用,為了避免組件的直接通信蘸劈,通常都是使用接口依賴(lài)的方式袱讹。我們實(shí)現(xiàn)了一個(gè) Interface Provider 來(lái)支持接口通信,它可以通過(guò)運(yùn)行時(shí)在動(dòng)態(tài)注冊(cè)一個(gè)接口昵时,同時(shí)也實(shí)現(xiàn)了對(duì)于 ServiceLoader 的支持捷雕。只要一方組件將通信接口暴露出來(lái),使用方就可以直接使用接口進(jìn)行調(diào)用壹甥。

動(dòng)態(tài)注冊(cè)接口

Provider.register(AbcInterface.class救巷,new AbcInterfaceImpl())

獲取實(shí)例并調(diào)用

Provider.get(AbcInterface.class).doSomething()

(5) EventBus:這個(gè)自不必說(shuō),雖然說(shuō)濫用是一個(gè)問(wèn)題句柠,但是有些場(chǎng)景下浦译,使用事件還是最為方便簡(jiǎn)單的方式

(6) 組件 API 模塊:上面提到的接口和事件以及一些跨組件使用的 Model 放到哪里好呢棒假?如果直接將這些類(lèi)下沉到一個(gè)公共組件中,由于業(yè)務(wù)的頻繁更新精盅,這個(gè)公共組件可能會(huì)更新得十分頻繁帽哑,開(kāi)發(fā)也十分的不方便,所以使用公共組件是行不通的叹俏,于是我們采取了另一種方式——組件 API :為每個(gè)有對(duì)外暴露需求的組件添加一個(gè) API 模塊妻枕,API 模塊中只包含對(duì)外暴露的 Model 和組件通信用的 Interface 與 Event。有需要引用這些類(lèi)的組件只要依賴(lài) API 即可粘驰。


相互獨(dú)立的組件屡谐,其實(shí)可能是藕斷絲連的

一個(gè)典型的組件工程結(jié)構(gòu)是這個(gè)樣子:


image.png

以上圖為例,它包含三個(gè)模塊:
  • template :組件代碼蝌数,它包含了這個(gè)組件所有業(yè)務(wù)代碼
  • template-api:組件的接口模塊愕掏,專(zhuān)門(mén)用于與其他組件通信,只包含 Model顶伞、Interface 和 Event饵撑,不存在任何業(yè)務(wù)和邏輯代碼
  • app 模塊:用于獨(dú)立運(yùn)行 app,它直接依賴(lài)組件模塊唆貌,只要添加一些簡(jiǎn)單的配置滑潘,即可實(shí)現(xiàn)組件獨(dú)立運(yùn)行

組件半自動(dòng)拆分

有了解耦的方法,剩下的就是采取行動(dòng)拆分組件了挠锥,拆組件是一個(gè)很頭疼的問(wèn)題众羡,它非常考慮一個(gè)人的細(xì)心與耐心蓖租,由于無(wú)法準(zhǔn)確知道有哪些代碼要被拆走粱侣,也不能直觀的知曉依賴(lài)關(guān)系,移動(dòng)變得非常的困難且容易出錯(cuò)蓖宦,一旦不能一次性拆分成功齐婴,到處都是編譯錯(cuò)誤,便只能靠人肉一點(diǎn)一點(diǎn)的挪稠茂。
工欲善其事柠偶,必先利其器。為了解決這個(gè)問(wèn)題睬关,我們開(kāi)發(fā)了一個(gè)輔助工具 RefactorMan: 它可以遞歸的解析出工程中所有源碼的引用和被引用情況诱担,同時(shí)會(huì)根據(jù)預(yù)設(shè)規(guī)則自動(dòng)分析出所有不合理的依賴(lài),在開(kāi)發(fā)人員根據(jù)提示解決了不合理依賴(lài)之后电爹,即可將組件一鍵移出蔫仙,大大減少了拆組件的工作量。我們?cè)诮M件化初期曾經(jīng)走過(guò)一些彎路丐箩,最初拆出的八個(gè)組件工程的的部分源碼經(jīng)歷了幾次的反復(fù)移動(dòng)才得出最優(yōu)解摇邦,而有了 RefactorMan恤煞,我們可以面對(duì)反復(fù)的拆分和組合組件有恃無(wú)恐
Bonus :由于可以分析和移動(dòng)資源,所以額外獲得了清理無(wú)用資源的功能

聯(lián)合編譯完整包

單獨(dú)運(yùn)行組件 app 并不能完整的覆蓋所有的 case施籍,尤其是在給 QA 測(cè)試的時(shí)候居扒,還是需要編譯完整的主工程包的,所以我們需要一個(gè)直接編譯完整包的方案:
最初我們的實(shí)現(xiàn)方式只針對(duì)組件丑慎,比較簡(jiǎn)單:
首先在 setting.gradle 中動(dòng)態(tài)引入組件 module:

def allComponents = ["base", "account" ... "template" ...]
allComponents.forEach({ name ->
    if (shouldUseSource(name)) {
        // 動(dòng)態(tài)引入外部模塊
        include ":${name}"
        project(":${name}").projectDir = getComponentDir(name);
    }
})

然后在 app/build.gradle 中切換依賴(lài)喜喂,需要將所有被間接依賴(lài)的組件全部 exclude 以防止同時(shí)依賴(lài)了一個(gè)組件的 module 和 aar:

allComponents.forEach({ name ->
    if (shouldUseSource(name)) {
        implementation(project(":${name}")) { exclude group: COMPONENT_GROUP }
    } else {
        implementation("${COMPONENT_GROUP}:${name}:${versions[name]}") { exclude group: COMPONENT_GROUP }
    }
})

由于所有組件的 group 都是一樣的,所以這樣做并沒(méi)有什么問(wèn)題立哑,但是后來(lái)一些基礎(chǔ) SDK 也出現(xiàn)了這種需求夜惭,這時(shí)候就需要一種通用的源碼依賴(lài)方案姻灶,因此做了一下修改铛绰,直接使用 gradle 提供的依賴(lài)替換功能,只需要修改 setting.gradle 即可:

// ... 忽略讀取配置代碼 ...
configs.forEach { artifact, prj ->
    include ":${prj.name}"
    project(":${prj.name}").projectDir = new File(prj.dir)
}
gradle.allprojects { project ->
    if (project == project.rootProject) {
        return
    }
    project.configurations.all {
        resolutionStrategy.dependencySubstitution {
            configs.forEach { artifact, prj ->
                // 在這里進(jìn)行替換
                substitute module(artifact) with project(":${prj.name}")
            }
        }
    }
}

而 build.gradle 的依賴(lài)寫(xiě)法與普通的工程完全一樣产喉。
普通狀態(tài)下的的主工程:


image.png

源碼引用 template 組件后的主工程:


image.png

這樣我們就可以像之前在單工程中一樣寫(xiě)代碼了捂掰。
得益于源碼引用,我們直接在提交組件代碼的時(shí)候曾沈,CI 會(huì)自動(dòng)聯(lián)合主工程編譯出完整包这嚣,QA 會(huì)根據(jù)完整包進(jìn)行測(cè)試,在測(cè)試通過(guò)后即可自動(dòng)發(fā)布到公司的倉(cāng)庫(kù)塞俱,并通過(guò)內(nèi)部的集成平臺(tái)集成到主工程姐帚。

小 tip :工程 .idea/vcs.xml 中定義了當(dāng)前工程關(guān)聯(lián)的 Git 倉(cāng)庫(kù),可以在聯(lián)合編譯的同時(shí)通過(guò)修改 vcs.xml 來(lái)把組件目錄也關(guān)聯(lián)到主工程 Git 配置中障涯,在開(kāi)發(fā)過(guò)程中就可以使用 Android Studio 的內(nèi)置 Git 功能了罐旗。

包含子業(yè)務(wù)線的組件

我們當(dāng)前的組件,絕大部分是一個(gè)組件一個(gè)倉(cāng)庫(kù)的唯蝶,對(duì)于一般的組件來(lái)說(shuō)九秀,并沒(méi)有什么問(wèn)題,但是對(duì)于有的業(yè)務(wù)線粘我,本身規(guī)模比較大鼓蜒,包含了若干個(gè)子業(yè)務(wù),比如知乎大學(xué)征字,電子書(shū)都弹、live 和私家課等子業(yè)務(wù),這些子業(yè)務(wù)本身功能獨(dú)立匙姜,但是共享整個(gè)業(yè)務(wù)線的基礎(chǔ)代碼畅厢,同時(shí)大業(yè)務(wù)線也有會(huì)一些匯總所有子業(yè)務(wù)的頁(yè)面,它們的關(guān)系是這個(gè)樣子:


我是復(fù)雜的業(yè)務(wù)線

這幾個(gè)業(yè)務(wù)如果都要拆分出去獨(dú)立成組件搁料,然后抽離公共部分成為也成為一個(gè)業(yè)務(wù)線基礎(chǔ)組件或详,這時(shí)候會(huì)面臨一個(gè)很大的問(wèn)題:由于幾條業(yè)務(wù)線都屬于同一個(gè)主業(yè)務(wù)線系羞,做活動(dòng)或者上新 Feature 的時(shí)候,這幾個(gè)組件經(jīng)常會(huì)發(fā)生聯(lián)動(dòng)霸琴,需要先更新 base 再更新其他業(yè)務(wù)線椒振,提交 mr 也要同時(shí)提多個(gè)倉(cāng)庫(kù),出現(xiàn)頻繁的連鎖更新梧乘;而如果不拆的話澎迎,業(yè)務(wù)線代碼本身就已經(jīng)很龐大,即使是單獨(dú)編譯組件 app 也會(huì)很慢选调,并且隨著時(shí)間的推移夹供,各個(gè)業(yè)務(wù)線的代碼邊界會(huì)像組件化之前的主工程一樣逐漸劣化,耦合會(huì)越來(lái)越嚴(yán)重仁堪。

所以現(xiàn)在需求變成了這個(gè)樣子:
對(duì)外保持只有一個(gè)組件:有聯(lián)動(dòng)需求的時(shí)候哮洽,組件仍然只發(fā)布一次更新
各個(gè)子業(yè)務(wù)仍舊保持互相獨(dú)立和隔離,可以獨(dú)立運(yùn)行
我們?cè)?jīng)試圖使用 sourceSets 的方式將不同的業(yè)務(wù)代碼放到不同的文件夾弦聂,但是 sourceSets 的問(wèn)題在于鸟辅,它并不能限制各個(gè) sourceSet 之間互相引用,base 模塊甚至可以直接引用最上層的代碼莺葫,雖然可以在編譯期進(jìn)行檢查匪凉,但是總有一些后知后覺(jué)的意味,并且使用 sourceSets 想讓各個(gè)模塊單獨(dú)跑起來(lái)配置也比較麻煩捺檬。而 Android Studio 的 module 天然具有隔離的優(yōu)勢(shì)再层。所以我們的解決方案是在組件工程中使用多 Module 結(jié)構(gòu):


image.png

各個(gè)子業(yè)務(wù)線分別拆成同一個(gè)工程中不同的 Module:它們共同依賴(lài) base ,同時(shí)各個(gè)業(yè)務(wù)線互相不依賴(lài)堡纬,這些子業(yè)務(wù)又在一個(gè)主 Module 中匯集起來(lái)聂受,正如上面圖片所示那樣

對(duì)于外界來(lái)說(shuō)只有一個(gè) main 組件,如果直接通過(guò) ./gradlew :main:uploadArchives 來(lái)發(fā)布隐轩,那么就只能把 main Module 的代碼發(fā)布上去饺饭,其他 Module 的代碼是無(wú)法發(fā)布的,所以我們需要在發(fā)布的時(shí)候?qū)⑺械拇a合并到 main 中去职车。這時(shí)候只能使用添加 sourceSet 的方式瘫俊,而一旦使用了 sourceSet,代碼就不再隔離了悴灵。所以我們使用了一個(gè)動(dòng)態(tài)的策略:編譯時(shí)使用 sourceSet 依賴(lài)扛芽,其他時(shí)候使用 module 依賴(lài),這樣可以同時(shí)擁有兩者的優(yōu)勢(shì)积瞒。

也就是說(shuō):表面看起來(lái)川尖,這是一個(gè)普通的多模塊的工程,但是實(shí)際上茫孔,他們的關(guān)系是動(dòng)態(tài)的:寫(xiě)代碼時(shí)是七個(gè)葫蘆娃叮喳,編譯時(shí)是葫蘆小金剛:


image.png

如何做到呢被芳,可以簡(jiǎn)單的判斷當(dāng)前啟動(dòng)的 Task,一般我們只在 assemble馍悟、install畔濒、upload 的時(shí)候使用合體操作,而其他時(shí)候使用普通的 project 依賴(lài)锣咒,示例代碼如下:

boolean useSource = gradle.startParameter.taskNames.any {
    it.contains("assemble") || it.contains("install") || it.contains("upload"))
}
subProject.forEach { subProject ->
    if (useSource) {
        android.sourceSets.main {
            java.srcDirs += file("../${subProject}/src/main/java")
            res.srcDirs += file("../${subProject}/src/main/res")
        }
    } else {
        dependencies { implementation project(":$subProject") }
    }
}

其他資源例如 resources侵状、assets、aidl毅整、renderscript趣兄、jni、jniLibs悼嫉、shaders 以及 aar 和 jar 文件艇潭,它們都是多文件的,可以使用與上面類(lèi)似的方法添加承粤。

但是 manifest 不同暴区,一個(gè) module 中只有一個(gè) AndroidManifest.xml 闯团,所以需要有一個(gè)方法將子業(yè)務(wù)的 manifest 合并辛臊。我們使用了官方提供的 ManifestMerger 實(shí)現(xiàn)了 manifest 的合并,這里不再展開(kāi)合并的具體代碼房交,有興趣的同學(xué)可以自己去看源碼彻舰。

將上面代碼封裝了一個(gè)方法 using,主 module 就可以這樣引用子 module 了:

dependencies {
    using "base"
    using "sub1"
    using "sub2"
    using 'sub3'
    using 'sub4'
}

由于每個(gè)子業(yè)務(wù)組件都是獨(dú)立的候味,仍然可以單獨(dú)配置獨(dú)立編譯獨(dú)立運(yùn)行刃唤,由于每個(gè)業(yè)務(wù)的代碼量相對(duì)整個(gè)業(yè)務(wù)線來(lái)說(shuō)大大減少了,所以得到了更快的編譯速度白群。

總結(jié)

最近兩年很多公司都開(kāi)始了 App 的組件化尚胞,組件化的基礎(chǔ)思想都是相通的,但是并沒(méi)有一個(gè)放之四海而皆準(zhǔn)的通用解決方案帜慢,各個(gè)公司在組件化的過(guò)程中都會(huì)根據(jù)自身的情況不斷的調(diào)整方案笼裳,適合自身發(fā)展的,才是最好的粱玲。一些組件化初期看起來(lái)不起眼的問(wèn)題躬柬,可能進(jìn)行到后期才會(huì)慢慢顯現(xiàn)出來(lái),這時(shí)候就要及時(shí)調(diào)整方案抽减。知乎的組件化也是在不斷的變動(dòng)中逐漸完善的允青,并且以后肯定也會(huì)隨著業(yè)務(wù)和代碼的變動(dòng)不斷的進(jìn)行優(yōu)化,這會(huì)是一個(gè)持續(xù)的過(guò)程卵沉,后續(xù)我們也會(huì)持續(xù)分享一些組件化遇到的問(wèn)題和解決方案颠锉。
以上就是我們?cè)诮M件化過(guò)程中的一部分實(shí)踐法牲,由于本人的水平有限,如有錯(cuò)誤和疏漏琼掠,歡迎各位同學(xué)指正皆串。

關(guān)于作者

Peter Porker,2016 年加入知乎眉枕,現(xiàn)為知乎 Android 基礎(chǔ)架構(gòu)團(tuán)隊(duì)負(fù)責(zé)人恶复,有著豐富的 Android 工程化,組件化經(jīng)驗(yàn)速挑,設(shè)計(jì)并主導(dǎo)了知乎的 Android 組件化拆分工作谤牡。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市姥宝,隨后出現(xiàn)的幾起案子翅萤,更是在濱河造成了極大的恐慌,老刑警劉巖腊满,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件套么,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡碳蛋,警方通過(guò)查閱死者的電腦和手機(jī)胚泌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)肃弟,“玉大人玷室,你說(shuō)我怎么就攤上這事◇允埽” “怎么了穷缤?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)箩兽。 經(jīng)常有香客問(wèn)我津肛,道長(zhǎng),這世上最難降的妖魔是什么汗贫? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任身坐,我火速辦了婚禮,結(jié)果婚禮上芳绩,老公的妹妹穿的比我還像新娘掀亥。我一直安慰自己,他們只是感情好妥色,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布搪花。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪撮竿。 梳的紋絲不亂的頭發(fā)上吮便,一...
    開(kāi)封第一講書(shū)人閱讀 49,185評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音幢踏,去河邊找鬼髓需。 笑死,一個(gè)胖子當(dāng)著我的面吹牛房蝉,可吹牛的內(nèi)容都是我干的僚匆。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼搭幻,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼咧擂!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起檀蹋,我...
    開(kāi)封第一講書(shū)人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤松申,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后俯逾,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體贸桶,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年桌肴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了皇筛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡识脆,死狀恐怖设联,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情灼捂,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布换团,位于F島的核電站悉稠,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏艘包。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望认然。 院中可真熱鬧耸成,春花似錦、人聲如沸舌厨。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至躏哩,卻和暖如春署浩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背扫尺。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工筋栋, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人正驻。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓弊攘,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親姑曙。 傳聞我的和親對(duì)象是個(gè)殘疾皇子肴颊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

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