網(wǎng)易 Android 工程模板化實踐


視頻鏈接

0 背景

我們網(wǎng)易前端技術(shù)部 - 移動技術(shù)組作為公司的移動端基礎(chǔ)技術(shù)部門寺庄,主要為其他部門提供解決方案艾蓝、技術(shù)支持和產(chǎn)品孵化。在幾年的積累過程中斗塘,我們擁有一些自己的框架和 SDK赢织,如輕應(yīng)用框架、熱更新 SDK馍盟、網(wǎng)絡(luò)請求庫于置、本地存儲庫、頁面管理等贞岭,服務(wù)過網(wǎng)易新聞八毯、云音樂、考拉瞄桨、易信等億級產(chǎn)品话速,先后孵化過青果攝像頭、二次元Gacha芯侥、嚴(yán)選等重要產(chǎn)品泊交。

在多年的Android開發(fā)中,對于 Android 端產(chǎn)品開發(fā)柱查,我們有如下幾點體會:

  1. 產(chǎn)品孵化排期緊張

    產(chǎn)品經(jīng)理一般關(guān)心的是具體的業(yè)務(wù)邏輯廓俭,而前期基礎(chǔ)模塊的搭建,如各模塊如何組織物赶,使用代碼結(jié)構(gòu)如何選擇白指,圖片、網(wǎng)絡(luò)酵紫、本地存儲等選用哪個 sdk 等告嘲,一般不會有專門排期错维。

  2. 基礎(chǔ)模塊的需求具有相似性

    內(nèi)容型產(chǎn)品,其搭建的基礎(chǔ)模塊基本上都會包含圖片顯示橄唬、網(wǎng)絡(luò)請求赋焕、本地存儲、通信等仰楚。

  3. 基礎(chǔ)模塊的選型和工具類具有可重用性

    網(wǎng)上相關(guān)的第三方庫有很多隆判,當(dāng)然一般的公司也是會有自己開發(fā)或者維護的各個基礎(chǔ) SDK。很多時候僧界,SDK 選型會更偏向于自己公司開發(fā)維護的 SDK侨嘀,或者選擇自己最熟悉,或最主流捂襟、最可靠的 SDK咬腕。因此當(dāng)開發(fā)多個相同類型產(chǎn)品時,這里的技術(shù)選型是可重用的葬荷。

  4. 網(wǎng)絡(luò)請求的代碼具有機械性

    客戶端開發(fā)需要根據(jù)網(wǎng)絡(luò)接口協(xié)議涨共,編寫相關(guān)的 GETPOST 等請求代碼和對應(yīng)的 JavaBean宠漩,這部分的代碼編寫其實是非常機械的举反。

1 網(wǎng)易工程模板是什么?

對于各個基礎(chǔ)模塊扒吁,我們團隊封裝了自己的 SDK火鼻,如網(wǎng)絡(luò)庫、本地存儲庫雕崩、頁面管理庫凝危、圖片庫等。使用我們的工程模板生成的初始工程晨逝,就已經(jīng)包含了我們提供的基礎(chǔ)模塊蛾默,產(chǎn)品團隊的開發(fā)不需要再花費重復(fù)的時間做技術(shù)調(diào)研、選型捉貌、SDK封裝集成等工作支鸡,而只需要關(guān)心自己的業(yè)務(wù)邏輯編寫。我們期望產(chǎn)品團隊只需 1 分鐘就能得到自己的初始工程趁窃,并能馬上投入業(yè)務(wù)邏輯開發(fā)牧挣,既能縮短開發(fā)周期,也能保證工程代碼質(zhì)量醒陆。

此外瀑构,我們也提供了 Android Studio 插件 (NEIPlugin),集成插件后刨摩,就能在 Android Studio 中通過菜單點擊自動下載集成我們的工程模板寺晌,也能自動生成網(wǎng)絡(luò)請求相關(guān)的代碼世吨。

image

工程模板 HTTemplate

image

代碼生成結(jié)果示例

2 Android 模板工程實現(xiàn)

最初我們使用終端腳本命令的方式,通過文件拷貝和文本查找替換(主要是替換包名等)的方式實現(xiàn)呻征。但終歸對 Android 開發(fā)人員不太友好耘婚,畢竟大家更習(xí)慣使用 Android Studio 生成工程。所幸陆赋,強大的 Android Studio 已經(jīng)提供了較為全面的模板功能沐祷,這里大概可以分為以下幾類:

2.1 Android 工程模板基礎(chǔ)知識

2.1.1 工程模板實例介紹

對于 Android Studio,模板位置:

Windows 的路徑在 `${android studio 安裝路徑}/plugins/android/lib/templates/`

MacOS 的路徑在 `${Android Studio.app 存放路徑}/Contents/plugins/android/lib/templates/`

有關(guān)模板的文件夾:

  1. activities:工程模板相關(guān)攒岛,如 EmptyActivity 文件夾用于創(chuàng)建一個空頁面的模板赖临,GoogleMapsActivity 文件夾對應(yīng)創(chuàng)建一個地圖頁面的模板等

  2. gradle:放置了 gradle 模板,用于在新建工程的根目錄下生成 gradle 文件夾灾锯,支持用戶不用安裝 gradle 就能使用 gradlew 命令

  3. gradle-project:工程模板相關(guān)思杯,用于構(gòu)建 moduleAndroid Project挠进,Java Library

  4. other:構(gòu)建文件模板等

這里我們關(guān)心的是 activities 文件夾里面的內(nèi)容

首先查看下 EmtpyActivity (空白頁面模板) 里面的內(nèi)容

  1. globals.xml.ftl: 全局變量文件,保存一些全局變量誊册,當(dāng)中可以引用其他文件的全局變量

  2. recipe.xml.ftl: 配置要引用的模板路徑以及文件的生成規(guī)則

  3. template.xml: 模板的配置信息领突,包括模板的顯示圖標(biāo),界面的表現(xiàn)案怯,全局變量文件和執(zhí)行文件的指定等

    image
  4. template_blank_activity.png: 顯示的縮略圖

  5. SimpleActivity.java.ftl: Activity 模板文件

  6. 代碼生成過程圖

    image

    圖片摘自 Tutorial How To Create Custom Android Code Templates

Android Studio 使用的是 FreeMarker 模板引擎君旦,所以文件后綴都是 .ftl

2.1.2 常用標(biāo)簽使用

  • ${}: FreeMarker 的語法,如 ${packageName}, ${superClass}globals.xml.ftl 全局變量文件或template.xml.ftl 中定義變量引用

  • <#if></#if>: FreeMarker 的語法嘲碱,條件判斷語句

  • <#include>: FreeMarker 的語法金砍,包含語句

  • copy: 將文件或者文件夾從 from 標(biāo)簽拷貝到 to 標(biāo)簽指定的路徑

  • instantiate: 將文件或者文件夾,執(zhí)行 FreeMarker 語法麦锯,從 from 標(biāo)簽實例化到 to 標(biāo)簽指定的路徑

  • merge: 合并 from 和 to 標(biāo)簽分別指定的文件

  • open: 在工程打開后恕稠,默認(rèn)打開指定的文件

    實例:使用空白頁面模板生成工程并打開后,可以看到默認(rèn)打開了 MainActivity.javaactivity_main.xml 文件

2.2 工程模板創(chuàng)建

新建 HTTemplate 文件夾內(nèi)容如下:

  1. template.xml

    指定模板名、描述、最低支持 sdk 版本饿自、類別等刑枝,輸入界面要求指定包名和 Application 類名

  2. globals.xml.ftl

    引用公共文件內(nèi)容

  3. recipe.xml.ftl

    • merge AndroidManifest.xml 文件

    • copy 或者 merge 資源文件

    • copy 或 instantiate java 代碼

    • merge build.gradle 文件

    • merge settings.gradle 文件

    • copy lib 文件夾里面的全部內(nèi)容

    • copy module 工程

    • copy proguard-rules.pro 文件

  4. root 文件夾

    放置相關(guān)模板源文件,其中將源工程中依賴于配置的代碼候学,按照 FreeMarker 語法進(jìn)行替換

  5. 添加工程模板圖標(biāo),并在 template.xml 中添加引用

image

工程模板創(chuàng)建結(jié)果

2.3 遇到的坑與解決辦法

2.3.1 build.gradle ${} 通配符沖突

當(dāng)工程模板實例化時,${} 會被 FreeMarker 語法處理敛苇,導(dǎo)致錯誤。

解決辦法:定義 FreeMarker 轉(zhuǎn)義字符如下

$ ==> ${"$"}

2.3.2 gradle.properties.ftl 合并失敗

根據(jù)錯誤提示顺呕,執(zhí)行合并操作是只能針對 xml 或者 gradle 文件進(jìn)行枫攀,其他文件并不支持合并括饶。另外改用 copyinstantiate 命令也同樣失敗

proguard-rules.pro 生成失敗。

解決辦法:將需要定義常量的代碼移動到工程根目錄 build.gradle 中:定義在 ext{ } 內(nèi)

2.3.3 build.gradle 合并問題

  1. apply 合并失敗

    期望結(jié)果

    apply plugin: 'com.android.application'
    apply plugin: 'com.neenbedankt.android-apt'
    

    實際結(jié)果

    apply plugin: 'com.neenbedankt.android-apt' plugin: 'com.android.application'
    
  2. dependencies 中脓豪,apt 引用代碼沒有出現(xiàn)

2.3.4 settings.gradle 文件合并問題

為了工程目錄結(jié)構(gòu)更清晰些巷帝,我們在 settings.gradle.ftl 文件中指定 module 的相對路徑,在 recipe.xml.ftl 執(zhí)行了 merge 操作扫夜。但得到錯誤提示:settings.gradle.ftl 中只允許 include 命令楞泼。

解決辦法:將 module 工程放置在默認(rèn)目錄下,不再指定路徑

2.3.5 Java 代碼實例化問題

模板中 java 代碼較多笤闯,我們統(tǒng)一放在 root/src/ 文件夾下堕阔,里面有部分文件含有 FreeMarker 標(biāo)簽,有部分只是純粹的 java 代碼颗味。而使用 instantiate 命令對整個文件夾進(jìn)行實例化操作超陆,并不會觸發(fā) FreeMarker 語法執(zhí)行。

解決辦法:因 java 文件比較多浦马,手寫 recipe.xml 標(biāo)簽命令繁瑣且容易出錯时呀。我們通過程序遞歸遍歷 root/src/ 下的全部代碼文件,并生成相應(yīng)的 instantiatecopy 命令

3 工程模板遺留問題解答

工程模板相關(guān)源碼位置:

Mac 平臺:
${android studio 安裝路徑}/Contents/plugins/android/lib/android.jar

Windows 平臺:
${android studio 安裝路徑}/plugins/android/lib/android.jar

具體類在 com/android/tools/idea/templates/ 里面晶默。

3.1 copy 和 instantiate 問題

  • gradle.properties 文件執(zhí)行 copy 或者 instantiate 操作無效果原因谨娜?

  • copyinstantiate 對文件夾操作的區(qū)別

3.1.1 copy 命令

查看 DefaultRecipeExecutor.copy 方法,這里是直接簡單的調(diào)用 copyTemplateResource 方法磺陡,該函數(shù)的基本邏輯如下:

  • 如果 source 是一個文件夾趴梢,則執(zhí)行 copyDirectory 方法,里面會遞歸的執(zhí)行文件夾內(nèi)的文件币他,其中如果葉子文件 (非文件夾) 對應(yīng)的目標(biāo)文件存在坞靶,則不執(zhí)行拷貝,繼續(xù)處理其他文件

  • 如果 source 非文件夾蝴悉,且目標(biāo)文件存在彰阴,則不執(zhí)行拷貝

  • 當(dāng)上面的條件都不滿足的情況下,執(zhí)行文件拷貝操作

  • 期間沒有使用 FreemarkerUtilsFreeMarker 語法進(jìn)行處理

3.1.2 instantiate 命令

直接查看 DefaultRecipeExecutor.instantiate 方法拍冠,該函數(shù)的基本邏輯如下:

  • 如果 from 文件是一個文件夾硝枉,則執(zhí)行 copyTemplateResource 方法,和 copy 流程一樣

  • 如果 from 文件非文件夾倦微,且目標(biāo)文件已經(jīng)存在了妻味,則不執(zhí)行文件操作

  • 當(dāng)上面的條件都不滿足的情況下,先執(zhí)行 FreemarkerUtils 的靜態(tài)方法 processFreemarkerTemplate 來處理 FreeMarker 語法欣福,之后再執(zhí)行文件拷貝操作

3.1.3 遺留問題解答

  • gradle.properties 文件執(zhí)行 copy 或者 instantiate 操作無效果原因责球?

    解答:在執(zhí)行我們的工程模板執(zhí)行,已經(jīng)執(zhí)行了 gradle-projects/NewAndroidProject 模板,并生成了 gradle.properties 文件雏逾,因此執(zhí)行 copyinstantiate 都因目標(biāo)文件已經(jīng)存在而不再執(zhí)行

  • copyinstantiate 對文件夾操作的區(qū)別

    解答:如果 from 指定一個文件夾嘉裤,都是執(zhí)行 copyTemplateResource 方法,2 者沒有區(qū)別

3.2 merge 問題

  • gradle.properties 文件執(zhí)行 merge 操作失敗原因

  • settings.gradle 文件合并栖博,指定 module 路徑錯誤原因

  • apt 語句消失原因

  • apply 語句合并錯誤原因

3.2.1 merge 主流程解析

查看 DefaultRecipeExecutor.merge 方法屑宠,基本邏輯如下:

image

3.2.2 settings.gradle 合并

查看 RecipeMergeUtils.mergeGradleSettingsFile 方法,基本邏輯如下:

  • 讀取目標(biāo)文件的每一行內(nèi)容仇让,并判斷每行內(nèi)容的開頭是否是 include 開頭

    • 是:在 include 后面插入內(nèi)容
    • 否:拋出異常
  • 返回合并的內(nèi)容

3.2.3 build.gradle 合并

查看 GradleFileMerger.mergeGradleFiles 方法典奉,里面會調(diào)用 mergePsi 方法,其基本邏輯如下:

  • 讀取文件 sourcedest 文件的內(nèi)容丧叽,并轉(zhuǎn)化得到 GroovyFile 類型對象

  • 執(zhí)行 mergePsi 方法

這里 mergePsi 執(zhí)行合并的邏輯是

image

繼續(xù)查看 dependencies 合并的源碼 GradleFileMerger.mergeDependencies 方法

里面的基本邏輯邏輯是:

  • 收集 toRoot 中能解析的 compile 子元素卫玖,并將收集到的子元素從 toRoot 中刪除

  • 收集 fromRoot 中的能解析的 compile 子元素,并刪除能解析的 compile 子元素踊淳,另外單獨收集不能解析的 complie 子元素

  • 遍歷全部能解析的 compile 子元素假瞬,比較相同 compile 語句的最大版本號,并插入到 toRoot 中

  • 遍歷不能解析的 compile 子元素迂尝,將內(nèi)容添加至 toRoot

fromRoot 是我們自定義的模板文件夾中定義的 dependencies 內(nèi)容

toRoot 是執(zhí)行 gradle-project 中的工程模板初始創(chuàng)建的 dependencies 內(nèi)容

3.2.4 遺留問題解答

  • gradle.properties 文件執(zhí)行 merge 操作失敗原因

    解答:根據(jù) DefaultRecipeExecutor.merge 方法的邏輯脱茉,我們可以看到當(dāng) to 文件不存在,則執(zhí)行 copyinstantiate 命令垄开;如果 to 文件存在且可讀琴许,則僅對 xmlgradle 才能執(zhí)行 merge 操作

  • settings.gradle 文件合并,指定 module 路徑錯誤原因

    解答:只允許每行開頭是 include 命令说榆,其他情況拋出異常

  • apt 語句消失原因

    解答:pullDependenciesIntoMap 方法僅處理 from 文件中 dependencies 中的 compile 子元素,其他如 apt寸认、provided 命令都是會被忽略掉签财。

  • apply 語句合并錯誤原因

    // 我們的工程模板文件內(nèi)容 - 對應(yīng) mergePsi 方法中 toRoot 參數(shù)
    apply plugin: 'com.neenbedankt.android-apt'
    
    // 源工程模板初始生成的 `buidl.gradle` 文件內(nèi)容 - 對應(yīng) mergePsi 方法中 fromRoot 參數(shù)
    apply plugin: 'com.android.application'
    
    // 期望合并結(jié)果
    apply plugin: 'com.neenbedankt.android-apt'
    apply plugin: 'com.android.application'
    
    // 實際合并結(jié)果
    apply plugin: 'com.neenbedankt.android-apt' plugin: 'com.android.application'
    
    image

    大概畫了執(zhí)行流程,里面的關(guān)鍵流程如下:

    1. 步驟 2: fromRoot 和 toRoot 不是 call 語句
    2. 步驟 5: 都能找到 apply 類型的子元素
    3. 步驟 6: 2 個 apply 的第一個子元素都不是 dependencies
    4. 步驟 11: fromRoot 中的 apply 子元素 plugin: 'com.android.application' 和 toRoot 中的 apply 子元素的 plugin: 'com.neenbedankt.android-apt' 不對應(yīng)
    5. 步驟12: 將 plugin: 'com.android.application' 添加到 toRoot 的 apply 子元素前面

根據(jù)上面的分析偏塞,看起來 apply 的這個合成結(jié)果是 google 工程模板的 bug唱蒸,是不是應(yīng)該提供對 apply 合并的特殊處理?

3.3 小結(jié)

到現(xiàn)在灸叼,我們建立了自己的工程模板神汹。原來編碼過程中碰到的問題,現(xiàn)在也已經(jīng)從源碼解析的角度做了解釋古今。一些問題屁魏,如 gradle 文件中,dependencies 元素合并忽略自定義模板文件中的非 compile 子元素捉腥;apply 元素合并不符合我們的需求氓拼。最后導(dǎo)致我們不得不放棄 apt 引入。這些問題 (或者說是限制),不知 Google 方面是出于什么考慮還是本身的 bug桃漾。

4 網(wǎng)絡(luò)請求代碼自動生成

對于 Android 工程模板安裝坏匪,我們提供的插件已經(jīng)實現(xiàn)了下載和安裝功能。

其次撬统,在當(dāng)前的工程當(dāng)中适滓,我們還需要有工具,能根據(jù) NEI 接口定義平臺 中定義的網(wǎng)絡(luò)接口恋追,自動生成我們的網(wǎng)絡(luò)請求相關(guān)代碼 (包括各個 Request 類和 JavaBean)凭迹。針對網(wǎng)絡(luò)請求代碼的自動生成,我們開發(fā)了 nei-toolkit几于,詳細(xì)安裝使用介紹可以查看 README.md

為了讓 Android 開發(fā)人員能更加方便的使用 nei-toolkit蕊苗,我們在插件中集成了 nei-toolkit 的下載、安裝沿彭、使用朽砰。

4.1 插件開發(fā)基礎(chǔ)

所有基于 IntelliJ Platform 的IDE,包括 Intellij Idea喉刘,Android Studio瞧柔,Web Storm 等等,都可以為其添加插件以實現(xiàn)一些額外的功能睦裳。插件可以從本地安裝造锅,也可以從 JetBrains Plugin Repository 安裝。Intellij 提供了一系列 API廉邑,使我們可以自定義插件哥蔚。

  1. 如何配置插件開發(fā)的環(huán)境,可以查看 Setting Up a Development Environment

    需要注意的是蛛蒙,配置 Project language level 為 Java 6糙箍,才能支持大部分的 Android Studio

  2. 插件開發(fā)的其他基礎(chǔ)知識,如設(shè)置按鈕牵祟,如何處理事件邏輯深夯,如何定義插件 id,名稱诺苹,版本號等內(nèi)容咕晋,可以查看 官方文檔

4.2 執(zhí)行終端命令

這里代碼生成功能最終也還是執(zhí)行了 nei-toolkit 中的命令來完成 http 代碼生成的,因此我們使用的是 Runtime 方法來執(zhí)行收奔。

Process proc = Runtime.getRuntime().exec(command);
// 指定調(diào)用程序的工作目錄
Process proc = Runtime.getRuntime().exec(cmd, null, new File(project.getBasePath()));
  • 執(zhí)行下載工程模板命令:

    git clone ${ht-template git 地址} 
        /Applications/Android\ Studio.app/Contents/plugins/android/lib/templates/activities/HTTemplate
    

    MacOS 平臺

  • 執(zhí)行代碼生成命令

    /usr/local/bin/node /usr/local/bin/nei mobile
        11321 --lang java --appPackage com.netease.test.httemplatetest
        --reqAbstract com.netease.hearttouch.http.BaseRequest
        --baseModelAbstract com.netease.hthttp.model.BaseModel 
        --resOut /app/src/main/hthttp-gen/ --doNotOverwrite
    

    MacOS 平臺

此外我們提供 NeiConsole 控制臺掌呜,顯示腳本執(zhí)行輸出

image

5 小結(jié)和后續(xù)工作

到此,基本上完成了我們原先期望實現(xiàn)的工程模板和網(wǎng)絡(luò)請求代碼自動生成的工作:

  1. 提供 ht-template 支持生成我們的模板工程

  2. 提供 Android Studio 插件 (NEIPlugin)

這里還是有一些因為 Android 工程模板自身的限制而無法完成的內(nèi)容點:

  1. 無法在 settings.gradle 指定 module 路徑

  2. 無法合并 proguard-rules.pro 文件站辉,暫時生成 proguard-rules.pro.template 文件

  3. 由于 build.gradleapply 命令合并會出錯和無法合并 dependencies 中的 apt 命令呢撞,所以無法在 build.gradle 中集成 ht-universalrouter

再次,除了網(wǎng)絡(luò)請求代碼編寫是機械性的饰剥,其他的基于我們的工程模板生成的初始工程殊霞,也存在一定的代碼編寫機械性:初始頁面代碼生成、RecycleView 中的各個 ViewHolder 類汰蓉、本地數(shù)據(jù)讀取保存等绷蹲,而這些工作將會是我們的后續(xù)工作。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末顾孽,一起剝皮案震驚了整個濱河市祝钢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌若厚,老刑警劉巖拦英,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異测秸,居然都是意外死亡疤估,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進(jìn)店門霎冯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來铃拇,“玉大人,你說我怎么就攤上這事沈撞】独螅” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵缠俺,是天一觀的道長显晶。 經(jīng)常有香客問我,道長壹士,這世上最難降的妖魔是什么磷雇? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮墓卦,結(jié)果婚禮上倦春,老公的妹妹穿的比我還像新娘户敬。我一直安慰自己落剪,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布尿庐。 她就那樣靜靜地躺著忠怖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪抄瑟。 梳的紋絲不亂的頭發(fā)上凡泣,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機與錄音,去河邊找鬼鞋拟。 笑死骂维,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的贺纲。 我是一名探鬼主播航闺,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼猴誊!你這毒婦竟也來了潦刃?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤懈叹,失蹤者是張志新(化名)和其女友劉穎乖杠,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體澄成,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡胧洒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了环揽。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片略荡。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖歉胶,靈堂內(nèi)的尸體忽然破棺而出汛兜,到底是詐尸還是另有隱情,我是刑警寧澤通今,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布粥谬,位于F島的核電站,受9級特大地震影響辫塌,放射性物質(zhì)發(fā)生泄漏漏策。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一臼氨、第九天 我趴在偏房一處隱蔽的房頂上張望掺喻。 院中可真熱鬧,春花似錦储矩、人聲如沸感耙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽即硼。三九已至,卻和暖如春屡拨,著一層夾襖步出監(jiān)牢的瞬間只酥,已是汗流浹背褥实。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留裂允,地道東北人损离。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像绝编,于是被迫代替她去往敵國和親草冈。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,446評論 2 348

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