Android build.gradle配置詳解

轉自:http://www.lxweimin.com/p/c11862136abf

Android Studio是采用gradle來構建項目的,gradle是基于groovy語言的,如果只是用它構建普通Android項目的話,是可以不去學groovy的。當我們創建一個Android項目時會包含兩個Android build.gradle配置詳解文件,如下圖:

build.gradle位置.png

一、Project的build.gradle文件:

對應的build.gradle代碼如下:

// Top-level build file where you can add configuration options common to all sub-projects/modules.buildscript {//這里是gradle腳本執行所需依賴,分別是對應的maven庫和插件repositories {? ? ? ? google()//從Android Studio3.0后新增了google()配置,可以引用google上的開源項目jcenter()//是一個類似于github的代碼托管倉庫,聲明了jcenter()配置,可以輕松引用 jcenter上的開源項目}? ? dependencies {? ? ? ? classpath'com.android.tools.build:gradle:3.0.0'////此處是android的插件gradle,gradle是一個強大的項目構建工具//NOTE:Do not place your application dependencies here; they belong// in the individual module build.gradle files}}allprojects {//這里是項目本身需要的依賴,比如項目所需的maven庫repositories {? ? ? ? google()? ? ? ? jcenter()? ? }}// 運行gradle clean時,執行此處定義的task任務。// 該任務繼承自Delete,刪除根目錄中的build目錄。// 相當于執行Delete.delete(rootProject.buildDir)。// gradle使用groovy語言,調用method時可以不用加()。taskclean(type: Delete){? ? delete rootProject.buildDir}

buildscript{}閉包里是gradle腳本執行所需依賴,分別是對應的maven庫和插件。

allprojects{}閉包里是項目本身需要的依賴,比如項目所需的maven庫。

task clean(type: Delete){}是運行gradle clean時,執行此處定義的task任務,該任務繼承自Delete,刪除根目錄中的build目錄。其中buildscript包含repositories閉包和dependencies閉包。

repositories{}閉包:配置遠程倉庫

該閉包中聲明了jcenter()和google()的配置,其中jcenter是一個代碼托管倉庫,上面托管了很多Android開源項目,在這里配置了jcenter后我們可以在項目中方便引用jcenter上的開源項目,從Android Studio3.0后新增了google()配置,可以引用google上的開源項目。

dependencies{}閉包:配置構建工具

該閉包使用classpath聲明了一個Gradle插件,由于Gradle并不只是用來構建Android項目,因此此處引入相關插件來構建Android項目,其中'3.0.0'為該插件的版本號,可以根據最新的版本號來調整。

二、Module的build.gradle文件:

從文件內容可以看出,主要分為三大部分,如下圖所示:

Module的build.gradle.png

1、apply plugin:

// 聲明是Android程序,//com.android.application 表示這是一個應用程序模塊//com.android.library 標識這是一個庫模塊//而這區別:前者可以直接運行,后著是依附別的應用程序運行apply plugin:'com.android.application'

文件中第一行使用apply plugin表示應用了一個插件,該插件一般有兩種值可選:

'com.android.application',表示該模塊為應用程序模塊,可以直接運行,打包得到的是.apk文件

'com.android.library',表示該模塊為庫模塊,只能作為代碼庫依附于別的應用程序模塊來運行,打包得到的是.aar文件

2、android{}閉包:

這個閉包主要為了配置項目構建的各種屬性:

2.1、添加signingConfigs{}閉包:

signingConfigs {// 自動化打包配置release {// 線上環境keyAlias'test'keyPassword'123456'storeFile file('test.keystore')? ? ? ? ? ? storePassword'123456'}? ? ? ? debug {// 開發環境keyAlias'test'keyPassword'123456'storeFile file('test.keystore')? ? ? ? ? ? storePassword'123456'}? ? }

可以手動添加簽名配置,也可以通過Project Structure 選中app,點擊Singing添加,具體步驟如下圖所示:

配置Singing.png

簽名配置完成后可以方便帶簽名打包,在module的Build Variants中有兩個Type,分別是debug和release,可以選擇任意一個類型進行打包,并且他們會利用各自配置的Key進行打包,執行 Run app或者Build->Build apk就會自動在module name/app/build/outputs/apk路徑下生成Apk文件。另一種打包方式是Build->Generate Signed APK填寫簽名信息生成Apk。

2.2、compileSdkVersion:設置編譯時用的Android版本

2.3、buildToolsVersion:設置編譯時使用的構建工具的版本,Android Studio3.0后去除此項配置

2.4、defaultConfig{}閉包:

compileSdkVersion27//設置編譯時用的Android版本defaultConfig {? ? ? ? applicationId"com.billy.myapplication"http://項目的包名minSdkVersion16//項目最低兼容的版本targetSdkVersion27//項目的目標版本versionCode1//版本號versionName"1.0"http://版本名稱testInstrumentationRunner"android.support.test.runner.AndroidJUnitRunner"http://表明要使用AndroidJUnitRunner進行單元測試}

applicationId:指定了項目的包名。

minSdkVersion:指定項目最低兼容的版本,如果設備小于這個版本或者大于maxSdkVersion(一般不用)將無法安裝這個應用,這里指定為16,表示最低兼容到Android 4.1系統。

targetSdkVersion:指定項目的目標版本,表示在該目標版本上已經做過充分測試,系統會為該應用啟動一些對應該目標系統的最新功能特性,Android系統平臺的行為變更,只有targetSdkVersion的屬性值被設置為大于或等于該系統平臺的API版本時,才會生效。例如,若指定targetSdkVersion值為22,則表示該程序最高只在Android5.1版本上做過充分測試,在Android6.0系統上(對應targetSdkVersion為23)擁有的新特性如系統運行時權限等功能就不會被啟用。

versionCode:表示版本號,一般每次打包上線時該值只能增加,打包后看不見。

versionName:表示版本名稱,展示在應用市場上。

testInstrumentationRunner?"android.support.test.runner.AndroidJUnitRunner"表明要使用AndroidJUnitRunner進行單元測試。

2.5、 buildTypes{}閉包:

這個閉包主要指定生成安裝文件的主要配置,一般包含兩個子閉包,一個是debug閉包,用于指定生成測試版安裝文件的配置,可以忽略不寫;另一個是release閉包,用于指定生成正式版安裝文件的配置。兩者能配置的參數相同,最大的區別默認屬性配置不一樣,兩種模式支持的屬性配置如下圖:

buildTypes配置.png

buildTypes {// 生產/測試環境配置release {// 生產環境buildConfigField("boolean","LOG_DEBUG","false")//配置Log日志buildConfigField("String","URL_PERFIX","\"https://release.cn/\"")// 配置URL前綴minifyEnabledfalse//是否對代碼進行混淆proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'//指定混淆的規則文件signingConfig signingConfigs.release//設置簽名信息pseudoLocalesEnabledfalse//是否在APK中生成偽語言環境,幫助國際化的東西,一般使用的不多zipAlignEnabledtrue//是否對APK包執行ZIP對齊優化,減小zip體積,增加運行效率applicationIdSuffix'test'//在applicationId 中添加了一個后綴,一般使用的不多versionNameSuffix'test'//在applicationId 中添加了一個后綴,一般使用的不多}? ? ? ? debug {// 測試環境buildConfigField("boolean","LOG_DEBUG","true")//配置Log日志buildConfigField("String","URL_PERFIX","\"https://test.com/\"")// 配置URL前綴minifyEnabledfalse//是否對代碼進行混淆proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'//指定混淆的規則文件signingConfig signingConfigs.debug//設置簽名信息debuggablefalse//是否支持斷點調試jniDebuggablefalse//是否可以調試NDK代碼renderscriptDebuggablefalse//是否開啟渲染腳本就是一些c寫的渲染方法zipAlignEnabledtrue//是否對APK包執行ZIP對齊優化,減小zip體積,增加運行效率pseudoLocalesEnabledfalse//是否在APK中生成偽語言環境,幫助國際化的東西,一般使用的不多applicationIdSuffix'test'//在applicationId 中添加了一個后綴,一般使用的不多versionNameSuffix'test'//在applicationId 中添加了一個后綴,一般使用的不多}? ? }

release{}閉包和debug{}閉包兩者能配置的參數相同,最大的區別默認屬性配置不一樣:

minifyEnabled:表明是否對代碼進行混淆,true表示對代碼進行混淆,false表示對代碼不進行混淆,默認的是false。

proguardFiles:指定混淆的規則文件,這里指定了proguard-android.txt文件和proguard-rules.pro文件兩個文件,proguard-android.txt文件為默認的混淆文件,里面定義了一些通用的混淆規則。proguard-rules.pro文件位于當前項目的根目錄下,可以在該文件中定義一些項目特有的混淆規則。

buildConfigField:用于解決Beta版本服務和Release版本服務地址不同或者一些Log打印需求控制的。例如:配置buildConfigField("boolean", "LOG_DEBUG", "true"),這個方法接收三個非空的參數,第一個:確定值的類型,第二個:指定key的名字,第三個:傳值,調用的時候BuildConfig.LOG_DEBUG即可調用。

debuggable:表示是否支持斷點調試,release默認為false,debug默認為true。

jniDebuggable:表示是否可以調試NDK代碼,使用lldb進行c和c++代碼調試,release默認為false

signingConfig:設置簽名信息,通過signingConfigs.release或者signingConfigs.debug,配置相應的簽名,但是添加此配置前必須先添加signingConfigs閉包,添加相應的簽名信息。

renderscriptDebuggable:表示是否開啟渲染腳本就是一些c寫的渲染方法,默認為false。

renderscriptOptimLevel:表示渲染等級,默認是3。

pseudoLocalesEnabled:是否在APK中生成偽語言環境,幫助國際化的東西,一般使用的不多。

applicationIdSuffix:和defaultConfig中配置是一的,這里是在applicationId 中添加了一個后綴,一般使用的不多。

versionNameSuffix:表示添加版本名稱的后綴,一般使用的不多。

zipAlignEnabled:表示是否對APK包執行ZIP對齊優化,減小zip體積,增加運行效率,release和debug默認都為true。

2.6、sourceSets{}閉包:配置目錄指向

sourceSets {//目錄指向配置main {? ? ? ? ? ? jniLibs.srcDirs = ['libs']//指定lib庫目錄}? ? }

配置 jniLibs.srcDirs = ['libs'],可以在Android studio的Android視圖下生成jniLibs文件夾,可以方便我們存放jar包和庫文件,其中Android視圖下的jniLibs和project視圖下的libs指向同一文件夾(app→libs),如下圖所示:

jniLibs.png

2.7、packagingOptions{}閉包:打包時的相關配置

當項目中依賴的第三方庫越來越多時,有可能會出現兩個依賴庫中存在同一個(名稱)文件。如果這樣,Gradle在打包時就會提示錯誤(警告)。那么就可以根據提示,然后使用以下方法將重復的文件剔除,比較常用的是通過exclude去除重復的文件,例如:

packagingOptions{//pickFirsts做用是 當有重復文件時 打包會報錯 這樣配置會使用第一個匹配的文件打包進入apk// 表示當apk中有重復的META-INF目錄下有重復的LICENSE文件時? 只用第一個 這樣打包就不會報錯pickFirsts = ['META-INF/LICENSE']//merges何必 當出現重復文件時 合并重復的文件 然后打包入apk//這個是有默認值得 merges = [] 這樣會把默默認值去掉? 所以我們用下面這種方式 在默認值后添加merge'META-INF/LICENSE'//這個是在同時使用butterknife、dagger2做的一個處理。同理,遇到類似的問題,只要根據gradle的提示,做類似處理即可。exclude'META-INF/services/javax.annotation.processing.Processor'}

2.8、productFlavors{}閉包:多個渠道配置

這個配置是經常會使用到的,通常在適配多個渠道的時候,需要為特定的渠道做部分特殊的處理,比如設置不同的包名、應用名等。場景:當我們使用友盟統計時,通常需要設置一個渠道ID,那么我們就可以利用productFlavors來生成對應渠道信息的包,如:

android {? ? ? productFlavors {? ? ? ? wandoujia {//豌豆莢渠道包配置manifestPlaceholders = [UMENG_CHANNEL_VALUE:"wandoujia"]//manifestPlaceholders的使用在后續章節(AndroidManifest里的占位符)中介紹}? ? ? ? xiaomi {? ? ? ? ? ? manifestPlaceholders = [UMENG_CHANNEL_VALUE:"xiaomi"]? ? ? ? ? ? applicationId"com.wiky.gradle.xiaomi"http://配置包名}? ? ? ? _360 {? ? ? ? ? ? manifestPlaceholders = [UMENG_CHANNEL_VALUE:"_360"]? ? ? ? }//...}? }

當然也有更簡潔的方式:

android {? ? ? productFlavors {? ? ? ? wandoujia {}? ? ? ? xiaomi {}? ? ? ? _360 {}//...}? ? ? productFlavors.all {//批量修改,類似一個循序遍歷flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]? ? }}

配置完之后,在命令行窗口中(Terminal)中輸入gradlew assembleRelease(windows)即可開始打包,在Mac系統中對應指令應該是./gradlew assembleRelease。當然,如果想要debug版本的包,將指令中assembleRelease改為assembleDebug即可。最后生成的包還是在app/build/outputs/apk中,默認命名格式如app-wandoujia-release-unsigned.apk,在module的Build Variants中可以選擇相應的渠道。

注:Android Studio3.0需在主app的build.gradle里面的

defaultConfig {

targetSdkVersion:***

minSdkVersion :***

versionCode:***

versionName :***

//版本名后面添加一句話,意思就是flavor dimension 它的維度就是該版本號,這樣維度就是都是統一的了

flavorDimensions "versionCode"

}

2.9、lintOptions{}閉包:代碼掃描分析

Lint 是Android Studio 提供的 代碼掃描分析工具,它可以幫助我們發現代碼結構/質量問題,同時提供一些解決方案,而且這個過程不需要我們手寫測試用例。

Lint 發現的每個問題都有描述信息和等級(和測試發現 bug 很相似),我們可以很方便地定位問題,同時按照嚴重程度進行解決。

//程序在編譯的時候會檢查lint,有任何錯誤提示會停止build,我們可以關閉這個開關lintOptions {? ? ? ? abortOnErrorfalse//即使報錯也不會停止打包checkReleaseBuildsfalse//打包release版本的時候進行檢測}

3、dependencies{}閉包:

該閉包定義了項目的依賴關系,一般項目都有三種依賴方式:本地依賴、庫依賴和遠程依賴。本地依賴可以對本地的jar包或目錄添加依賴關系,庫依賴可以對項目中的庫模塊添加依賴關系,遠程依賴可以對jcener庫上的開源項目添加依賴關系。從Android Studio3.0后compile引入庫不在使用,而是通過api和implementation,api完全等同于以前的compile,用api引入的庫整個項目都可以使用,用implementation引入的庫只有對應的Module能使用,其他Module不能使用,由于之前的項目統一用compile依賴,導致的情況就是模塊耦合性太高,不利于項目拆解,使用implementation之后雖然使用起來復雜了但是做到降低偶合興提高安全性。

dependencies {//項目的依賴關系implementation fileTree(include: ['*.jar'], dir:'libs')//本地jar包依賴implementation'com.android.support:appcompat-v7:27.1.1'//遠程依賴implementation'com.android.support.constraint:constraint-layout:1.1.2'testImplementation'junit:junit:4.12'//聲明測試用例庫androidTestImplementation'com.android.support.test:runner:1.0.2'androidTestImplementation'com.android.support.test.espresso:espresso-core:3.0.2'}

implementation?fileTree(include: ['*.jar'], dir: 'libs'):implementation fileTree是一個本地依賴聲明,表示將libs目錄下所有.jar后綴的文件都添加到項目的構建路徑當中。

implementation 'com.android.support:appcompat-v7:27.1.1':implementation語句為 遠程依賴聲明,'com.android.support:appcompat-v7:27.1.1'為一個標準的遠程依賴庫格式,其中com.android.support為域名部分,用于區分不同公司的庫;appcompat-v7為組件名稱,用于區分同一個公司的不同庫;27.1.1為版本號,用于區分同一個庫的不同版本。加上這句聲明后,Gradle在構建項目時會先檢查一下本地是否已經緩存過該庫,若沒有緩存則自動聯網下載,下載后自動添加到項目的構建路徑中去。

testImplementation和androidTestImplementation:表示聲明測試用例庫。

Module完整的build.gradle配置如下:

// 聲明是Android程序,//com.android.application 表示這是一個應用程序模塊//com.android.library 標識這是一個庫模塊//而這區別:前者可以直接運行,后著是依附別的應用程序運行apply plugin:'com.android.application'android {? ? signingConfigs {// 自動化打包配置release {// 線上環境keyAlias'test'keyPassword'123456'storeFile file('test.jks')? ? ? ? ? ? storePassword'123456'}? ? ? ? debug {// 開發環境keyAlias'test'keyPassword'123456'storeFile file('test.jks')? ? ? ? ? ? storePassword'123456'}? ? }? ? compileSdkVersion27//設置編譯時用的Android版本defaultConfig {? ? ? ? applicationId"com.billy.myapplication"http://項目的包名minSdkVersion16//項目最低兼容的版本targetSdkVersion27//項目的目標版本versionCode1//版本號versionName"1.0"http://版本名稱flavorDimensions"versionCode"testInstrumentationRunner"android.support.test.runner.AndroidJUnitRunner"http://表明要使用AndroidJUnitRunner進行單元測試}? ? buildTypes {// 生產/測試環境配置release {// 生產環境buildConfigField("boolean","LOG_DEBUG","false")//配置Log日志buildConfigField("String","URL_PERFIX","\"https://release.cn/\"")// 配置URL前綴minifyEnabledfalse//是否對代碼進行混淆proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'//指定混淆的規則文件signingConfig signingConfigs.release//設置簽名信息pseudoLocalesEnabledfalse//是否在APK中生成偽語言環境,幫助國際化的東西,一般使用的不多zipAlignEnabledtrue//是否對APK包執行ZIP對齊優化,減小zip體積,增加運行效率applicationIdSuffix'test'//在applicationId 中添加了一個后綴,一般使用的不多versionNameSuffix'test'//在applicationId 中添加了一個后綴,一般使用的不多}? ? ? ? debug {// 測試環境buildConfigField("boolean","LOG_DEBUG","true")//配置Log日志buildConfigField("String","URL_PERFIX","\"https://test.com/\"")// 配置URL前綴minifyEnabledfalse//是否對代碼進行混淆proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'//指定混淆的規則文件signingConfig signingConfigs.debug//設置簽名信息debuggablefalse//是否支持斷點調試jniDebuggablefalse//是否可以調試NDK代碼renderscriptDebuggablefalse//是否開啟渲染腳本就是一些c寫的渲染方法zipAlignEnabledtrue//是否對APK包執行ZIP對齊優化,減小zip體積,增加運行效率pseudoLocalesEnabledfalse//是否在APK中生成偽語言環境,幫助國際化的東西,一般使用的不多applicationIdSuffix'test'//在applicationId 中添加了一個后綴,一般使用的不多versionNameSuffix'test'//在applicationId 中添加了一個后綴,一般使用的不多}? ? }? ? sourceSets {//目錄指向配置main {? ? ? ? ? ? jniLibs.srcDirs = ['libs']//指定lib庫目錄}? ? }? ? packagingOptions{//打包時的相關配置//pickFirsts做用是 當有重復文件時 打包會報錯 這樣配置會使用第一個匹配的文件打包進入apk// 表示當apk中有重復的META-INF目錄下有重復的LICENSE文件時? 只用第一個 這樣打包就不會報錯pickFirsts = ['META-INF/LICENSE']//merges何必 當出現重復文件時 合并重復的文件 然后打包入apk//這個是有默認值得 merges = [] 這樣會把默默認值去掉? 所以我們用下面這種方式 在默認值后添加merge'META-INF/LICENSE'//這個是在同時使用butterknife、dagger2做的一個處理。同理,遇到類似的問題,只要根據gradle的提示,做類似處理即可。exclude'META-INF/services/javax.annotation.processing.Processor'}? ? productFlavors {? ? ? ? wandoujia {}? ? ? ? xiaomi {}? ? ? ? _360 {}? ? }? ? productFlavors.all {//批量修改,類似一個循序遍歷flavor -> flavor.manifestPlaceholders = [IFLYTEK_CHANNEL: name]? ? }//程序在編譯的時候會檢查lint,有任何錯誤提示會停止build,我們可以關閉這個開關lintOptions {? ? ? ? abortOnErrorfalse//即使報錯也不會停止打包checkReleaseBuildsfalse//打包release版本的時候進行檢測}}dependencies {//項目的依賴關系implementation fileTree(include: ['*.jar'], dir:'libs')//本地jar包依賴implementation'com.android.support:appcompat-v7:27.1.1'//遠程依賴implementation'com.android.support.constraint:constraint-layout:1.1.2'testImplementation'junit:junit:4.12'//聲明測試用例庫androidTestImplementation'com.android.support.test:runner:1.0.2'androidTestImplementation'com.android.support.test.espresso:espresso-core:3.0.2'}

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

推薦閱讀更多精彩內容