Android全面插件化RePlugin流程與源碼解析

RePlugin左电,360開源的全面插件化框架,按照官網(wǎng)說的页响,其目的是“盡可能多的讓模塊變成插件”篓足,并在很穩(wěn)定的前提下,盡可能像開發(fā)普通App那樣靈活闰蚕。那么下面就讓我們一起深入♂了解它吧栈拖。 (ps :閱讀本文請多參考源碼圖片 ( ̄^ ̄)ゞ )

一、介紹

RePlugin對比其他插件化没陡,它的強(qiáng)大和特色涩哟,在于它只Hook住了ClassLoader。One Hook這個(gè)堅(jiān)持诗鸭,最大程度保證了穩(wěn)定性染簇、兼容性和可維護(hù)性,詳見《全面插件化——RePlugin的使命》强岸。當(dāng)然锻弓,One Hook也極大的提高了實(shí)現(xiàn)復(fù)雜程度性,其中主要體現(xiàn)在:

  • 增加了Gradle插件腳本蝌箍,實(shí)現(xiàn)開發(fā)中自動代碼修改與生成青灼。
  • 分割了插件庫和宿主庫的代碼實(shí)現(xiàn)。
  • 代碼中存在很多不少@deprecated妓盲、TODO和臨時(shí)修改杂拨。
  • 初始化、加載悯衬、啟動等邏輯比較復(fù)雜弹沽。
圖一 Replugin項(xiàng)目結(jié)構(gòu)

本篇將竭盡所能,為各位介紹其流程和內(nèi)部實(shí)現(xiàn)筋粗,如果存在一些地方存在紕漏策橘,還請指出。文章篇幅較長娜亿,需耐心閱讀丽已,閱讀時(shí)可結(jié)合圖片源碼,同時(shí)歡迎收藏买决,或選擇感興趣點(diǎn)閱讀沛婴,下面主要涉及:

  • 二吼畏、ClassLoader基礎(chǔ)知識。
  • 三嘁灯、Replugin項(xiàng)目原理和結(jié)構(gòu)分析泻蚊。
  • 四、Replugin的ClassLoader旁仿。
  • 五藕夫、Replugin的相關(guān)類介紹。
  • 六枯冈、Replugin的初始化毅贮。
  • 七、Replugin啟動Activity尘奏。
此處應(yīng)有圖

二滩褥、ClassLoader基礎(chǔ)知識

既然Replugin選擇Hook住ClassLoader,那先簡單介紹下ClassLoader的基本知識吧炫加,如熟悉者請略過瑰煎。

ClassLoader又叫類加載器,是專門處理類加載俗孝,一個(gè)APP可以存在多個(gè)ClassLoader酒甸,它使用的是雙親代理模型,如下圖所示赋铝,創(chuàng)建一個(gè)ClassLoader插勤,需要使用一個(gè)已有的ClassLoader對象,作為新建的實(shí)例的ParentLoader革骨。

抽象基類ClassLoader

這樣的條件下农尖,一個(gè)App中所有的ClassLoader都聯(lián)系了起來。當(dāng)加載類時(shí)良哲,如果當(dāng)前ClassLoader未加載此類盛卡,就查詢ParentLoader是否加載過,一直往上查找筑凫,如果存在就返回滑沧,如果都沒有,就執(zhí)行該Loader去執(zhí)行加載工作巍实。這樣避免了類重復(fù)加載的浪費(fèi)滓技。其中常見的Loader有:

  • BootClassLoader 是系統(tǒng)啟動時(shí)創(chuàng)建的,一般不需要用到蔫浆。
  • PathClassLoader 是應(yīng)用啟動時(shí)創(chuàng)建的殖属,只能加載內(nèi)部dex姐叁。
  • DexClassLoader 可以加載外部的dex瓦盛。

RePlugin中存在兩個(gè)主要ClassLoaer:

  • 1洗显、RePluginClassLoader 宿主App中的Loader,繼承PathClassLoader原环,也是唯一Hook住系統(tǒng)的Loader挠唆。

  • 2、PluginDexClassLoader 加載插件的Loader嘱吗,繼承DexClassLoader玄组。用來做一些“更高級”的特性。

三谒麦、Replugin項(xiàng)目原理和結(jié)構(gòu)分析

1俄讹、基礎(chǔ)原理

簡單來說,其核心是hook住了 ClassLoader绕德,在Activity啟動前:

  • 記錄下目標(biāo)頁 ActivityA患膛,替換成已自動注冊在 AndroidManifest 中的坑位 ActivityNS
  • ClassLoader 中攔截ActivityNS的創(chuàng)建耻蛇,創(chuàng)建出ActivityA返回踪蹬。
  • 返回的ActivityA占用著 ActivityNS 這個(gè)坑位,坑位由Gradle編譯時(shí)自動生成在AndroidManifest中臣咖。

在編譯時(shí)跃捣,replugin-replugin-library腳本,會替換代碼中的基礎(chǔ)類和方法夺蛇。如下圖【官方原理圖】所示疚漆,替換的基類里會做一些初始化,所以這一塊稍微有點(diǎn)入侵性蚊惯。此外愿卸,replugin-host-library生成AndroidManifest配置相關(guān)信息截型、打包等趴荸,也由Gradle插件自動完成。

打包獨(dú)立APK宦焦,或者打包為插件发钝,可單可插,這就是RePlugin波闹。

官方原理圖

2酝豪、項(xiàng)目結(jié)構(gòu)

RePlugin整個(gè)項(xiàng)目結(jié)構(gòu),目前分為四個(gè)module精堕,其中又分為兩個(gè)gradle插件module孵淘,兩個(gè)library的java module,詳細(xì)如開頭【圖一 Replugin項(xiàng)目結(jié)構(gòu)】歹篓,本文主要分析library相關(guān)瘫证,如果對gradle插件感興趣的揉阎,可以查看結(jié)尾其他推薦。

2.1背捌、replugin-host-gradle :

對應(yīng)com.qihoo360.replugin:replugin-host-gradle:xxx依賴毙籽,主要負(fù)責(zé)在主程序的編譯期中生產(chǎn)各類文件:

  • 根據(jù)用戶的配置文件,生成HostBuildConfig類毡庆,方便插件框架讀取并自定義其屬性坑赡,如:進(jìn)程數(shù)、各類型占位坑的數(shù)量么抗、是否使用AppCompat庫毅否、Host版本、pulgins-builtin.json文件名蝇刀、內(nèi)置插件文件名等搀突。

  • 自動生成帶 RePlugin 插件坑位的 AndroidManifest.xml文件,文件中帶有如:

<activity 
    android:theme="@style/Theme.AppCompat" 
    android:name="com.qihoo360.replugin.sample.host.loader.a.ActivityN1STTS0"
    android:exported="false" 
    android:screenOrientation="portrait"
    android:configChanges="keyboard|keyboardHidden|orientation|screenSize" 
/>
2.2熊泵、replugin-host-library:

對應(yīng)com.qihoo360.replugin:replugin-host-lib:xxx依賴仰迁,是一個(gè)Java工程,由主程序負(fù)責(zé)引入顽分,是RePlugin的核心工程徐许,負(fù)責(zé)初始化、加載卒蘸、啟動雌隅、管理插件等。

2.3缸沃、replugin-plugin-gradle:

對應(yīng)com.qihoo360.replugin:replugin-plugin-gradle:xxx 恰起,是一個(gè)Gradle插件,由插件負(fù)責(zé)引入趾牧,主要負(fù)責(zé)在插件的編譯期中:配置插件打包相關(guān)信息骂远;動態(tài)替換插件工程中的繼承基類躺酒,如下,修改Activity的繼承、Provider的重定向等压固。

    /* LoaderActivity 替換規(guī)則 */
    def private static loaderActivityRules = [
            'android.app.Activity'                    : 'com.qihoo360.replugin.loader.a.PluginActivity',
            'android.app.TabActivity'                 : 'com.qihoo360.replugin.loader.a.PluginTabActivity',
            'android.app.ListActivity'                : 'com.qihoo360.replugin.loader.a.PluginListActivity',
            'android.app.ActivityGroup'               : 'com.qihoo360.replugin.loader.a.PluginActivityGroup',
            'android.support.v4.app.FragmentActivity' : 'com.qihoo360.replugin.loader.a.PluginFragmentActivity',
            'android.support.v7.app.AppCompatActivity': 'com.qihoo360.replugin.loader.a.PluginAppCompatActivity',
            'android.preference.PreferenceActivity'   : 'com.qihoo360.replugin.loader.a.PluginPreferenceActivity',
            'android.app.ExpandableListActivity'      : 'com.qihoo360.replugin.loader.a.PluginExpandableListActivity'
    ]
2.4怕敬、replugin-plugin-library:

對應(yīng)com.qihoo360.replugin:replugin-plugin-lib:xxx依賴诗轻,是一個(gè)Java工程贮聂,由插件端負(fù)責(zé)引入,主要提供通過“Java反射”來調(diào)用主程序中RePlugin Host Library的相關(guān)接口认臊,并提供“雙向通信”的能力圃庭,以及各種基類Activity等
  
  其中的RePluginRePluginInternalPluginServiceClient都是反射宿主App :replugin-host-library 中的 RePlugin 剧腻、 RePluginInternal 斟薇、PluginServiceClient 類方法。

四恕酸、Replugin的ClassLoader。

這里主要介紹胯陋,宿主和插件使用的ClassLoader蕊温,以及它們的創(chuàng)建和Hook住時(shí)機(jī)。這是RePlugin唯一的Hook點(diǎn)遏乔,而其中插件ClassLoader和宿主ClassLoader是相互關(guān)系的义矛,如下圖

將就的圖
1盟萨、宿主的ClassLoader

RePluginClassLoader凉翻,宿主的ClassLoader,繼承 PathClassLoader捻激,構(gòu)造方法使用原ClassLoader制轰,和原ClassLoader的Parent生成。其中ParentLoader是因?yàn)?strong>雙親代理模型胞谭,創(chuàng)建ClassLoader所需垃杖,而原Loader用于保留在后期使用,如下圖丈屹。

如下兩圖调俘,RePluginClassLoader 在創(chuàng)建時(shí),淺拷貝原Loader的資源到 RePluginClassLoader 中旺垒,用于欺騙系統(tǒng)還處于原Loader彩库,并且從原Loader中反射出常用方法,用于重載方法中使用先蒋。

拷貝資源
方式方法

宿主Loader中骇钦,主要是重載了 loadClass,其中從 PMF(RePlugin中公開接口類)中查找class竞漾,如果存在即返回插件class司忱,如果不存在就從原Loader中加載。從而實(shí)現(xiàn)了對加載類的攔截畴蹭。

這里的 PMF 在加載class時(shí)坦仍,其實(shí)用的是下面【2、插件的ClassLoader 】:PluginDexClassLoader叨襟,這個(gè)后面流程會講到繁扎。

2、插件的ClassLoader

PluginDexClassLoader,繼承DexClassLoader梳玫,構(gòu)造時(shí)持有了宿主的ClassLoader爹梁,從宿主ClassLoader中反射獲取loadClass方法,當(dāng)自己的loadClass方法找不到類時(shí)提澎,從宿主Loader中加載姚垃。

3、創(chuàng)建和Hook

創(chuàng)建:上面1盼忌、2中兩個(gè)Loader积糯,是宿主在初始化時(shí)創(chuàng)建的,初始化時(shí)可以選擇配置RePluginCallbacks谦纱,callback中提供方法默認(rèn)創(chuàng)建Loader看成,你也可以實(shí)現(xiàn)自定義的ClassLoader,但是需要繼承以上的Loader跨嘉,如下圖川慌。

//初始化方式創(chuàng)建
RePlugin.getConfig().getCallbacks()
.createClassLoader(oClassLoader.getParent(), oClassLoader);
RePluginCallbacks

Hook:初始化時(shí),PatchClassLoaderUtils會在Application的attachBaseContext()中祠乃,通過patch(application)Hook住宿主的ClassLoader梦重,patch內(nèi)部如下圖

hook ClassLoader

五亮瓷、Replugin的相關(guān)類介紹

提前介紹一些功能類忍饰,后面就不做詳細(xì)介紹。

** 1寺庄、RePlugin** :RePlugin的對外入口類艾蓝,提供install、uninstall斗塘、preload赢织、startActivity、fetchPackageInfo馍盟、fetchComponentList于置,fetchClassLoader等等統(tǒng)一的方法入口,用戶操作的主要是它贞岭。
  
2八毯、RePlugin.App:RePlugin中的內(nèi)部類,針對Application的入口類瞄桨,所有針對插件Application的調(diào)用應(yīng)從此類開始和初始化话速,想象成插件的Application吧。

3芯侥、PmBase:RePlugin常用mPluginMgr變量表示泊交,可以看作插件管理者乳讥。初始化插件、加載插件等一般都是從它開始廓俭。

4云石、PluginContainers:插件容器管理中心。

5研乒、PmLocalImpl:各種本地接口實(shí)現(xiàn)汹忠,如startActivity,getActivityInfo雹熬,loadPluginActivity等宽菜。

6、PmInternalImpl:類似Activity的接口實(shí)現(xiàn)橄唬,內(nèi)部實(shí)現(xiàn)了真正startActivity的邏輯、還有插件Activity生命周期的接口参歹。

準(zhǔn)備好了嗎仰楚,騷年

六、Replugin的初始化

那就是從 Application 初始化開始看起犬庇,枯燥的流程就要開始了僧界,忍住兄弟,我們能贏臭挽。首先我們先看下面這流程圖捂襟,大致了解啟動流程:

將就的看吧
1、attachBaseContext

首先是從 Application 的 attachBaseContext 初始化開始欢峰。如下圖葬荷,這里主要是配置 RePluginConfigRePluginCallbacks ,然后根據(jù) Config 去初始化插件纽帖。值得注意的是宠漩,RePluginConfig 中的 RePluginCallbacks 提供了默認(rèn)方法創(chuàng)建 RePlugin 的 ClassLoader,還記得上面的介紹嗎懊直?

看圖看圖
2扒吁、插件App.attachBaseContext

繼續(xù)上面的流程,進(jìn)入RePlugin.App.attachBaseContext(this, c)室囊,如下圖雕崩,這里主要是初始化插件相關(guān)的進(jìn)程、配置信息融撞、插件的主框架和接口盼铁、根據(jù)默認(rèn)路徑、加載默認(rèn)插件等尝偎。插件的初始化從這里開始捉貌,其中主要為 PMF.init()PMF.callAttach()

繼續(xù)看圖看圖
3、主程序接口 PMF.init()/PMF.callAttach()

先進(jìn)入到 PMF.init() 趁窃,如下圖牧挣,這里主要實(shí)例化了 PmBase 類,并初始化了它醒陆,創(chuàng)建了內(nèi)部使用的 PmLocalImplPmInternalImp 接口 瀑构,同時(shí)Hook住主程序的 ClassLoader,替換為 RePluginClassLoader刨摩,所以接下來的流程寺晌,主要是在 PmBase

PMF.init()澡刹,看圖吧

PmBase呻征,按照項(xiàng)目中的變量名 mPluginMgr,可以理解為插件的管理者罢浇,它管理內(nèi)部直接或間接的陆赋,管理著坑位分配、ClassLoader嚷闭、插件攒岛、進(jìn)程、啟動\停止頁面的接口等胞锰,如下圖灾锯。

PmBase創(chuàng)建,還是看圖

PmBase 的初始化嗅榕,也就是插件的初始化顺饮,這里會啟動各類進(jìn)程,初始化各種默認(rèn)插件集合凌那,為后續(xù)加載做準(zhǔn)備领突。其中默認(rèn)插件和配置文件的位置,一般默認(rèn)是在 assert 的 plugins-builtin.json 和 "plugins" 文件夾下案怯。

PmBase.init() 看圖看圖

接著PMF.callAttach() 其實(shí)就是 PmBase.callAttach()君旦,如下圖這里開始真正加載插件,初始化插件的 PluginDexClassLoader 嘲碱、加載插件金砍、初始化插件環(huán)境和接口。其中在執(zhí)行 p.load() 的時(shí)候麦锯,會通過 Plugind.callAppLocked() 創(chuàng)建插件的 Application恕稠,并初始化。

PMF.callAttach() 看圖唄

以上是在主APP的初始化扶欣,深入 PmBase 中鹅巍,Plugin.load()在加載時(shí)千扶,會調(diào)用PluginDexClassLoader, 通過類名加載 Entry 類骆捧,然后反射出create方法澎羞,執(zhí)行插件的初始化。其中 Entry 位于Plugin-lib庫中敛苇。這里初始化就去到了插件中了妆绞,插件中初始化時(shí),會通過反射的到宿主host類的方法枫攀。

4括饶、Application的onCreate

這里主要是切換handler到主線程,注冊各種廣播接收監(jiān)聽来涨,如增加插件图焰、卸載插件、更新插件蹦掐,可以看出這里設(shè)計(jì)很多內(nèi)部進(jìn)程通信的技羔。

七、Replugin啟動Activity

這里僅描述了Activity啟動的其中一個(gè)流程笤闯,也是簡化版的堕阔,實(shí)際代碼邏輯復(fù)雜多了棍厂,但是萬變不離其宗颗味,這里幫你梳理流程,描述一些關(guān)鍵的點(diǎn)牺弹,讓你快速理解Activity的啟動流程浦马。

再將就下吧,看圖
1张漂、startActivity

從上面的流程圖我們知道晶默,啟動插件Activity可以從RePlugin.startActivity開始,startActivity經(jīng)歷了 Factory 航攒、 PmLocalImpl 磺陡,其實(shí)大部分啟動的邏輯其實(shí)主要在 PmInternalImpl 中。

具體流程如下圖漠畜,這里簡化了實(shí)際代碼币他,關(guān)鍵在于 loadPluginActivity。這里獲取了插件對應(yīng)的坑位憔狞,然后保存了目標(biāo)Activity的信息蝴悉,通過系統(tǒng)啟動坑位。

因?yàn)橐呀?jīng)Hook住了ClassLoader瘾敢,在 loadClass 時(shí)再加載出目標(biāo)Activity拍冠,這樣坑位中承載的尿这,便是繞過系統(tǒng)打開的目標(biāo)Activity。下面我們進(jìn)入 loadPluginActivity庆杜。

說了看圖
2射众、loadPluginActivity

loadPluginActivity 其實(shí)是 PmBase 中的 PmLocalImpl 內(nèi)部方法。如下圖欣福,這里主要是根據(jù)獲取到 ActivityInfo责球,然后根據(jù)坑位去為目標(biāo)Activity分配坑位。

其中 getActivityInfo 是通過插件名稱拓劝,獲得插件對象 Plugin雏逾, Plugin可能是初始化中已加載的,如果未加載就加載返回郑临,然后根據(jù) Plugin 中緩存的坑位信息栖博,返回 ActivityInfo

下面進(jìn)入 allocActivityContainer 看坑位的分配厢洞,只有分配到坑位仇让,插件的Activity才可以啟動,這是一個(gè)IPC過程躺翻。

看圖沒丧叽?
2、allocActivityContainer

allocActivityContainer 在類 PluginProcessPer 中公你,還記得我們在 PmBase.init() 時(shí)初始化過它么踊淳? 分配坑位也是RePlugin的核心之一。

allocActivityContainer 中陕靠, 主要邏輯是bindActivity 迂尝,如下圖,bindActivity 去找到目標(biāo)Activity匹配的容器剪芥,然后加載目標(biāo)Activity判斷是否存在垄开,并建立映射,返回容器税肪。然后分配的邏輯溉躲,在 PluginContainers.alloc 中。

看我大圖
3益兄、PluginContainers.alloc

alloc / alloc2 方法分配坑位锻梳,最后都是到了 allocLocked 方法中,其實(shí)RePlugin中偏塞,如下圖唱蒸,便是坑位分配的邏輯:

  • 如果存在未啟動的坑位,就使用它灸叼。
  • 如果沒有就找最老的:已經(jīng)被釋放的神汹、或者時(shí)間最老的庆捺。
  • 如果還不行,那么擠掉最老的一個(gè)屁魏。
看圖說話
4滔以、PulginActivity

上面的流程總結(jié),是替換目標(biāo)Activity氓拼,加載插件你画,分配坑位,啟動目標(biāo)坑位桃漾,攔截ClassLoader的loadClass去加載返回目標(biāo)Activity坏匪。

這個(gè)時(shí)候啟動的Activity還不完整,從模塊框架中我們知道撬统,在編譯時(shí)适滓,RePlugin會把繼承的Activity替換為如 PluginActivity(當(dāng)前還有AppComPluginActivity等)。這時(shí)候加載啟動的目標(biāo)Activity恋追,其實(shí)是繼承了 PluginActivity凭迹。

如下圖PluginActivity 重載Activity中的一些方法苦囱,實(shí)現(xiàn)了Activity的補(bǔ)全和自定義操作嗅绸,如坑位管理,啟動宿主Activity等撕彤。

至此鱼鸠,一個(gè)插件Activity就啟動起來了,頭暈?zāi)垦A藳]喉刘?為了實(shí)現(xiàn) One Hook 這個(gè)信念瞧柔,RePlugin 實(shí)現(xiàn)了復(fù)雜的流程漆弄,從代碼中可以看出睦裳,這些年作者們從中走的的各種坑、各種妥協(xié)與堅(jiān)持撼唾、復(fù)雜的技術(shù)積累廉邑、已經(jīng)經(jīng)歷了多年的嚴(yán)酷考驗(yàn)。

不知道有多少人能完整看到這倒谷,碼字不易蛛蒙,如有疏漏還是多多包涵,由于篇(tou)幅(lan)原因渤愁,關(guān)于Service等的就不多做敘述了牵祟,不知道本文對你是否能有些幫助,歡迎留言討論抖格。

最后說“一”句

為什么要去了解一個(gè)庫實(shí)現(xiàn)原理呢诺苹?學(xué)習(xí)框架的架構(gòu)思想咕晋?這是一個(gè)原因。但是歸根結(jié)底收奔,是幫助你在使用庫的過程中掌呜,能靠自己解決各種問題。程序員的日常一般都忙于各種工作坪哄,各種技術(shù)群中的大佬們质蕉,大部分時(shí)候,沒辦法一一解答你的各種咨詢翩肌,所以使用它模暗、了解它、多嘗試靠自己去探索突破吧念祭。

其他推薦

注意到了嗎汰蓉?最后的總是我!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末棒卷,一起剝皮案震驚了整個(gè)濱河市顾孽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌比规,老刑警劉巖若厚,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蜒什,居然都是意外死亡测秸,警方通過查閱死者的電腦和手機(jī)灾常,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門霎冯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人沈撞,你說我怎么就攤上這事缠俺。” “怎么了躏救?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵睁本,是天一觀的道長。 經(jīng)常有香客問我凡泣,道長,這世上最難降的妖魔是什么航闺? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任乖杠,我火速辦了婚禮卫漫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘粥谬。我一直安慰自己派哲,他們只是感情好芭届,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布即硼。 她就那樣靜靜地躺著,像睡著了一般哥艇。 火紅的嫁衣襯著肌膚如雪祖乳。 梳的紋絲不亂的頭發(fā)上隙赁,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼抡爹。 笑死,一個(gè)胖子當(dāng)著我的面吹牛瘫析,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播桃序,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼杖虾,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了媒熊?” 一聲冷哼從身側(cè)響起奇适,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎芦鳍,沒想到半個(gè)月后嚷往,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡柠衅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年皮仁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片菲宴。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡贷祈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出喝峦,到底是詐尸還是另有隱情势誊,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布愈犹,位于F島的核電站键科,受9級特大地震影響闻丑,放射性物質(zhì)發(fā)生泄漏漩怎。R本人自食惡果不足惜勋颖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望勋锤。 院中可真熱鬧饭玲,春花似錦、人聲如沸叁执。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽谈宛。三九已至次哈,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間吆录,已是汗流浹背窑滞。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留恢筝,地道東北人哀卫。 一個(gè)月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像撬槽,于是被迫代替她去往敵國和親此改。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

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