Gradle從入門到了解

本文已授權微信公眾號 Android技術經驗分享 獨家發(fā)布

轉載請注明出處:Gradle從入門到了解

通過這篇文章,你可以了解到以下內容:

  1. 在自己的項目中使用腳本準備起飛寥殖。一次打包十幾個項目不嫌累。
  2. 面對下載下來的開源項目編譯報錯有一定處理能力。
  3. 水群吹牛逼直起腰板蘸朋。

Gradle概述

Gradle(英[g're?dl])是一個任務驅動型的構建工具伊履,是一個依賴管理工具韩容,更是一個編程框架。
它拋棄了基于XML的各種繁瑣配置唐瀑,取而代之的是一種基于Groovy的內部領域特定(DSL)語言群凶。
在android studio中,我們使用這個工具可以完成app的編譯打包等工作哄辣。

Goals of the new Build System(使用gradle的目的)

  • Make it easy to reuse code and resources
  • Make it easy to create several variants of an application, either for multi-apk distribution or for different flavors of an application
  • Make it easy to configure, extend and customize the build process
  • Good IDE integration

采用Gradle作為新構建系統(tǒng)的目標:

  • 讓重用代碼和資源變得更加容易请梢。
  • 讓創(chuàng)建同一應用程序的不同版本變得更加容易,無論是多個apk發(fā)布版本還是同一個應用的不同定制版本力穗。
  • 讓構建過程變得更加容易配置毅弧,擴展和定制。
  • 更好的IDE集成当窗。

Gradle Android插件用戶指南翻譯

Gradle Plugin User Guide 官方原文地址
http://tools.android.com/tech-docs/new-build-system/user-guide

中文版在線閱讀地址
http://avatarqing.github.io/Gradle-Plugin-User-Guide-Chinese-Verision

簡單來說有以下幾點:

  1. 獨立項目够坐,和Google無關
  2. Gradle基于Groovy。(Maven崖面、Ant基于xml)
    Groovy是拓展了Java語言的一種動態(tài)語言元咙,語法更簡潔,可以作為Java平臺的腳本語言使用 巫员,擁有類似Python庶香、Ruby和Smalltalk中的一些特性。
    Gradle是基于Groovy定義了一套DSL简识,所謂DSL(領域專用語言)赶掖,就是專門針對某一特定問題的計算機語言感猛。而Gradle我們可以認為是經過“定制”的Groovy,專門用于項目構建的語言倘零。
  3. Gradle兼容Maven唱遭、Ant
  4. Gradle 的推出主要以 Java 應用為主,當然還支持 Android呈驶、C拷泽、C++等。

Gradle基本組件


每一個build.gradle文件代表著一個Project袖瞻。Tasks在build.gradle中定義司致。當初始化構建進程時,gradle會基于build文件聋迎,集合所有的Project和Tasks,一個Tasks包含了一系列動作脂矫,然后它們將會按照順序執(zhí)行,一個動作就是一段被執(zhí)行的代碼霉晕,很像Java中的方法庭再。

  • Project
    每一個待編譯的工程(可以是一個jar包,一個web應用牺堰,或者一個android app等)都稱為一個Project拄轻。

  • Task
    每一個Project在構建的時候都包含一系列的Task。一個Task其實就是構建過程中一個原子性的操作伟葫。比如一個Android APK的編譯可能包含:Java源碼編譯Task恨搓、資源編譯Task、JNI編譯Task筏养、lint檢查Task斧抱、打包生成APK的Task、簽名Task等渐溶。

  • Plugin
    Gradle是一個框架辉浦,作為框架,它負責定義流程和規(guī)則掌猛。而具體的編譯工作則是通過插件的方式來完成的盏浙。比如編譯Java有Java插件,編譯Groovy有Groovy插件荔茬,編譯Android APP有Android APP插件废膘,編譯Android Library有Android Library插件。
    簡單來說慕蔚,插件就是一系列任務的集合丐黄,主要作用是把一些重復利用的邏輯打包,這樣就可以在不同的項目中可以重復的使用孔飒。
    要使用插件灌闺,可以通過引入依賴的方式添加艰争。

As如何依賴Gradle讓Gradle作為自身的構建工具呢?
  • 答:Google開發(fā)了一個Gradle插件桂对,讓As項目依賴這個插件甩卓,就相當于讓Gradle作為自身的的構建工具。

現(xiàn)在比如我們新建一個As項目蕉斜,打開項目的根目錄的gradle.build文件逾柿。有如下代碼:

buildscript {
    repositories {
        jcenter() //表示編譯過程中依賴的倉庫
    }
    dependencies {
      //依賴android開發(fā)使用的gradle插件
      classpath 'com.android.tools.build:gradle:2.2.0'    
    }
}

而要引入Android APP插件,就需要在build.gradle引用Android APP插件:

//申明使用插件宅此,表明要編譯的內容和產物
apply plugin: 'com.android.application'
 
//配置插件屬性
android {
    compileSdkVersion 24
    buildToolsVersion "24.0.1"
    defaultConfig {
        applicationId "zhj.gradledemo"
        minSdkVersion 15
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
     }
    buildTypes {
         release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

Android其實就是寫了兩個插件:

  • com.android.application和com.android.library机错。

應用這兩個插件就可以實現(xiàn)Android APP和Android Library的構建。

As項目中的Gradle


.gradle文件夾

.gradle文件夾 是gradle 運行以后生成的緩存文件夾父腕。

Project中的build.gradle文件

project下的build.gradle是基于整個project的配置弱匪,主要配置gradle 版本及 全局依賴倉庫、庫或者其他全部參數(shù)璧亮。

// Top-level build file where you can add configuration options common to all sub-projects/modules.
 
buildscript {
    repositories {
        //這里依賴的jcenter倉庫是gradle腳本自身需要的資源
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.0'
 
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}
 
allprojects {
    repositories {
        //這里依賴的jcenter倉庫是項目所有模塊需要的資源
        jcenter()
    }
}
 
task clean(type: Delete) {
    delete rootProject.buildDir
}

module中build.gradle文件

//申明使用插件萧诫,表明要編譯的內容和產物
apply plugin: 'com.android.application'
 
android {
    compileSdkVersion 24
    buildToolsVersion "24.0.1"
     //默認配置,會同時應用到debug和release版本上
    defaultConfig {
        applicationId "zhj.gradledemo"
        minSdkVersion 15
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled true  //是否混淆
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' //混淆文件的位置
        }
        debug {
            minifyEnabled false
        }
    }
    // 多渠道
    productFlavors {
        //可以設置不同渠道渠道號枝嘶,應用名稱
        pro {
        }
 
        fre {
        }
    }
}

//依賴第三方庫
dependencies {
    //編譯libs目錄下所以jar包
    compile fileTree(include: ['*.jar'], dir: 'libs')  //導入所有的jar包
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:24.2.0'
    compile 'com.android.support:design:24.2.0'
    testCompile 'junit:junit:4.12'
    proCompile 'com.android.support:recyclerview-v7:24.2.0'
}

Project中setting.gradle

這個文件是全局的項目配置文件财搁,里面主要聲明Project中所包括的所有module

//一個Project中所包括的所有module
include ':Gotobus', ':android-support-v7-appcompat'
include ':google-play-services_lib'
include ':TakeTours'
include ':Common'
include ':CompanyCommon'

Project中gradle.properties

gradle.properties為gradle的配置文件,里面可以定義一些常量供build.gradle使用躬络,比如可以配置簽名相關信息如keystore位置,密碼搭儒,keyalias等,build.gradle就可以直接引用
gradle 中的一些配置參數(shù)建議寫到gradle.properties

//編譯版本信息
APPLICATION_ID = com.jin.myAPP
COMPILE_SDK_VERSION = 23
BUILD_TOOLS_VERSION = 23.0.1
MIN_SDK_VERSION = 15
TARGET_SDK_VERSION = 1
VERSION_CODE = 1
VERSION_NAME = 1.0.0.0
 
//keystore信息
STORE_FILE = ../app/mykey.keystore
STORE_PASSWORD = your password
KEY_ALIAS = your alias
KEY_PASSWORD = your password

配置應用的簽名信息

在android.signingConfigs{}下定義一個或者多個簽名信息穷当,然后在buildTypes{}配置使用即可。比如這里

android {
 
    signingConfigs {
        release {
            storeFile file("release.keystore")
            keyAlias "release"
            keyPassword "123456"
            storePassword "123456"
        }
        debug {
            ...
        }
    }
 
    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
        debug {
            signingConfig signingConfigs.debug
        }
    }
  }
  • storeFile是簽名證書文件淹禾,keyAlias是別名馁菜,keyPassword是key的密碼,storePassword是證書的密碼铃岔。配置好相關信息即可在buildTypes配置使用汪疮。

一般重要的信息,例如簽名信息毁习,可以直接將信息寫到gradle.properties智嚷,然后在然后在build.gradle中引用即可。

  • buildTypes是指建構的類型纺且,一般只用兩種默認類型 debug 和 release 盏道,顧名思義 debug 用來配置開發(fā)過程中的一些內容;release 用來配置正式發(fā)布版本的內容载碌。有時我們需要發(fā)布介于debug與release之間的preview 版本猜嘱。

Build Variant 差異管理


比如app生成不同版本(免費衅枫,收費),適配特殊機型朗伶,多渠道等需要發(fā)多個包弦撩,最終能編譯出的apk的數(shù)量是由Product Flavor(產品種類)與Build Type(構建類型)決定的,
公式:Build Variant = Build Type x Product Flavor

  • BuildType(構建類型)
    默認有debug和release兩種论皆,標示編譯的類型益楼,通常在混淆代碼、可調式纯丸、資源壓縮上做一些區(qū)分偏形。

  • Product Flavor(產品種類)
    為了滿足“同一個project,根據(jù)一個很小的區(qū)分觉鼻,來打不同的包”這個需求俊扭。實現(xiàn)多渠道打包。注意:這里的Flavor名如果是數(shù)字開頭坠陈,必須用引號引起來萨惑。

調整module的目錄結構sourceSets


默認情況下,java文件和resource文件分別在src/main/java和src/main/res目錄下仇矾,在build.gradle文件的andorid{}里面添加下面的代碼庸蔼,便可以將java文件和resource文件放到src/java和src/resources目錄下。

sourceSets {
        main {
            manifest.srcFile 'AndroidManifest.xml'
            //設置java文件的位置
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            aidl.srcDirs = ['src']
            renderscript.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']
        }
}

全局變量定義及引用


可以在頂層build.gradle腳本中定義一些全局變量贮匕,提供給子腳本引用

ext {
    // global variables definition
    compileSdkVersion = 'Google Inc.:Google APIs:23'
    buildToolsVersion = "23.0.3"
    minSdkVersion = 14
    targetSdkVersion = 23
}

子腳本引用

android {
    compileSdkVersion rootProject.ext.compileSdkVersion
    buildToolsVersion rootProject.ext.buildToolsVersion
 
    defaultConfig {
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
    }
}

Gradle常用命令介紹


打開Android Studio內置的Terminal終端姐仅,輸入如下命令

執(zhí)行gradlew -v
指令代碼 指令功能
gradlew 下載更新gradle
gradlew -v 查詢版本號
gradlew clean 清楚項目的output文件
gradlew check 運行檢測和測試任務
gradlew build 運行check和assemble,檢查依賴并編譯打包(debug、release環(huán)境的包)
gradlew clean build 運行 clean 和 build 兩個 gradle task
gradlew assemble 編譯并打Debug和Release包
gradlew assembleDebug 編譯并把本項目下所有模塊所有渠道的Debug版本打包
gradlew assembleRelease 編譯并把本項目下所有模塊所有渠道的Release版本打包
gradlew assembleWandoujia 生成wandoujia渠道的Release和Debug版本
gradlew assembleWandoujiaRelease 打包wandoujia渠道的Release版本
gradlew assembleWandoujiaRelease -p app 打包app工程下wandoujia渠道的Release版本(使用-p選項刻盐,決定執(zhí)行哪個工程)
gradlew installRelease Release模式打包并安裝
gradlew uninstallRelease 卸載Release模式包

gradlew代表 gradle wrapper掏膏,意思是gradle的一層包裝,大家可以理解為在這個項目本地就封裝了gradle敦锌,即gradle wrapper馒疹。
在./gradle/wrapper/gralde-wrapper.properties文件中聲明了它指向的目錄和版本。只要下載成功即可用grdlew wrapper的命令代替全局的gradle命令乙墙。

assemble 命令創(chuàng)建task有如下語法:

  • 允許直接構建一個Variant版本颖变,例如assembleFlavor1Debug。
  • 允許構建指定Build Type的所有APK听想,例如assembleDebug將會構建Flavor1Debug和Flavor2Debug兩個Variant版本腥刹。
  • 允許構建指定flavor的所有APK,例如assembleFlavor1將會構建Flavor1Debug和Flavor1Release兩個Variant版本汉买。

批量修改生成的apk文件名


在我們打包發(fā)版的時候肛走,一次性打幾十個包,這時候我們就想讓生成的apk文件名有區(qū)分,比如一眼就能看出這個apk是哪個版本的朽色,哪個渠道的邻吞,是哪天打的包等等,這就需要我們在生成apk文件的時候動態(tài)修改生成的apk文件名達到這一目的葫男。代碼如下:

def buildTime() {
    def date = new Date()
    def formattedDate = date.format('yyyyMMdd')
    return formattedDate
}
 
android {
    buildTypes {
        release {
            applicationVariants.all { variant ->
                variant.outputs.each { output ->
                    if (output.outputFile != null && output.outputFile.name.endsWith('.apk')
                        &&'release'.equals(variant.buildType.name)) {
                        def apkFile = new File(
                                output.outputFile.getParent(),
                                "Gtobus_${variant.flavorName}_v${variant.versionName}_${buildTime()}.apk")
                        output.outputFile = apkFile
                    }
                }
            }
        }
    }
}

以baidu渠道為例抱冷,以上的代碼會生成一個名字為Gtobus__gotobus_v5.1.2_20161115.apk安裝包。

這里是循環(huán)處理每個applicationVariant梢褐,當他們的輸出文件名以apk結尾并且buildType是release時旺遮,重新設置新的輸出文件名,這樣就達到了我們批量修改生成的文件名的目的盈咳。

項目中的問題


android studio的編譯時屏蔽掉lint檢查耿眉,可以避免由于編譯條件太過嚴格而編譯不過的問題:

  lintOptions {
      abortOnError false
  }

如果遇到多個jar包中的某個文件沖突,可以在對應module下的build.gradle文件的android標簽下加上如下屬性:

  packagingOptions {
      exclude 'META-INF/NOTICE.txt'// 這里是具體的沖突文件全路徑
      exclude 'META-INF/LICENSE.txt'
  }
依賴版本沖突

依賴沖突是所以依賴管理中最頭痛的問題鱼响,這常常出現(xiàn)在傳遞依賴中鸣剪。Gradle對解決傳遞依賴提供了兩種策略,使用最新版本或者直接導致構建失敗丈积。默認的策略是使用最新版本筐骇。雖然這樣的策略能夠解決一些問題,但是還是不夠江滨。常見的一種情況是铛纬,NoSuchMethond或者ClassNotFound。這時候唬滑,你可能需要一些特殊手段告唆,比如排除不想要的傳遞依賴。

排除傳遞依賴

排除傳遞依賴有多種原因晶密,遠程倉庫中不存在悔详,運行時不需要,或者版本沖突惹挟。排除傳遞依賴的方式有兩種:1.直接在configuration中排除 2.在具體的某個dependency中排除

dependencies {
 
    compile 'com.android.support:support-v4:21.0.2'
    compile ('com.thoughtworks.xstream:xstream:1.4.7'){
        exclude group: 'xmlpull'
        exclude module: 'xpp3_min'
    }
 
}

錯誤:Execution failed for task ':app:transformClassesWithDexForDebug'.

com.android.build.api.transform.TransformException: java.lang.RuntimeException: com.android.ide.common.process.ProcessException: java.util.concurrent.ExecutionException: com.android.ide.common.process.ProcessException: org.gradle.process.internal.ExecException: Process 'command 'D:\Java\jdk1.8.0_31\bin\java.exe'' finished with non-zero exit value 1

上面都是扯淡,下面才是重點7觳怠A狻!


替換AndroidManifest中的占位符

manifestPlaceholders = [APP_LOGO:"@drawable/coachrun_icon"]

// 在AndroidManifest.xml里調用
 <application
        android:name="com.gotobus.main.GotoBusApplication"
        android:icon="${APP_LOGO}"
        android:label="@string/app_name">


運行時修改靜態(tài)變量

在build.gradle中配置buildConfigField參數(shù)用狱,編譯后會在..\app\build\generated\source\buildConfig文件夾下會自動生成對應版本對應module的BuildConfig.java运怖。
BuildConfig就會包含對應版本的配置信息。程序中可以直接引用這些數(shù)據(jù)夏伊。
需要注意的是BuildConfig定義的變量實在當前module下摇展,如果還有一個基礎module需要直接在基礎module的gradle下配置。

buildConfigField "String", "APPNAME_GOTOBUS", '"coachrun"'


//使用舉例
static{    
        APPNAME= BuildConfig.APPNAME_GOTOBUS;
}

運行時修改strings.xml數(shù)據(jù)

resValue "string", "app_name", "GotoBus"

// 在Activity里調用
getString(R.string.app_name) // 輸出GotoBus

運行時修改顏色

//使用和字符串相似
resValue "color", "flavor_color", "#0000ff"

資源文件和manifest的合并

在打包app之前溺忧,Android插件會合并main中的代碼和構建的代碼咏连。當然盯孙,依賴項目也可以提供額外的資源,它們也會被合并祟滴。你可能需要額外的Android權限針對debug變體振惰。舉個例子,你不想在main中申明這個權限垄懂,因為這可能導致一些問題骑晶,所以你可以添加一個額外的mainfest文件在debug的文件夾中,申明額外的權限草慧。

資源和mainfests的優(yōu)先級是這樣的:

優(yōu)先級

如果一個資源在main中和在flavor中定義了桶蛔,那么那個在flavor中的資源有更高的優(yōu)先級。這樣那個在flavor文件夾中的資源將會被打包到apk漫谷。而在依賴項目申明的資源總是擁有最低優(yōu)先級仔雷。

Gradle編譯優(yōu)化


  • 在添加依賴的時候盡量明確版本號,省去gradle查找最新版的時間
不要使用
compile ‘com.facebook.fresco:fresco:latest’ 
compile ‘com.facebook.fresco:fresco:1.+’抖剿,
  • 使用daemon
    構建初始化的很多工作是關于java虛擬機的啟動朽寞,加載虛擬機環(huán)境,加載class文件等斩郎,如果這些動作交給一個單獨的后臺進程去做脑融,那么,第一次初始化之后的修改代碼再構建是不是可以節(jié)省很多時間呢缩宜?答案是肯定的肘迎,通過在gradle.properties加入這樣一句來開啟,如果想讓修改全局所有項目都生效锻煌,那么修改這個文件~/.gradle/gradle.properties
org.gradle.daemon=true
  • 并行構建模塊化項目
    將你的項目拆分成多個子項目并開啟并行構建也是一個不錯的主意妓布,比如將相對獨立的模塊拆分成獨立的庫工程(Library projects),主工程(Application project)依賴這些庫工程宋梧,這樣的話匣沼,開啟并行構建才會發(fā)揮作用。并行構建開啟方式是修改文件gradle.properties捂龄,加入如下行:
org.gradle.parallel=true

參考
http://www.reibang.com/p/01281d1c3384
http://www.cnblogs.com/Bugtags2015/p/5563427.html
https://www.figotan.org/2016/04/01/gradle-on-android-best-practise/
http://www.reibang.com/p/9dcec4a14c52#

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末释涛,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子倦沧,更是在濱河造成了極大的恐慌唇撬,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件展融,死亡現(xiàn)場離奇詭異窖认,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門扑浸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來烧给,“玉大人,你說我怎么就攤上這事首装〈匆梗” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵仙逻,是天一觀的道長驰吓。 經常有香客問我,道長系奉,這世上最難降的妖魔是什么檬贰? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮缺亮,結果婚禮上翁涤,老公的妹妹穿的比我還像新娘。我一直安慰自己萌踱,他們只是感情好葵礼,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著并鸵,像睡著了一般鸳粉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上园担,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天届谈,我揣著相機與錄音,去河邊找鬼弯汰。 笑死艰山,一個胖子當著我的面吹牛,可吹牛的內容都是我干的咏闪。 我是一名探鬼主播曙搬,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼鸽嫂!你這毒婦竟也來了纵装?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤溪胶,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后稳诚,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哗脖,經...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了才避。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片橱夭。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖桑逝,靈堂內的尸體忽然破棺而出棘劣,到底是詐尸還是另有隱情,我是刑警寧澤楞遏,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布茬暇,位于F島的核電站,受9級特大地震影響寡喝,放射性物質發(fā)生泄漏糙俗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一预鬓、第九天 我趴在偏房一處隱蔽的房頂上張望巧骚。 院中可真熱鬧,春花似錦格二、人聲如沸劈彪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽沧奴。三九已至,卻和暖如春驶兜,著一層夾襖步出監(jiān)牢的瞬間扼仲,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工抄淑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留屠凶,地道東北人。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓肆资,卻偏偏與公主長得像矗愧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子郑原,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內容