Android Gradle配置快速入門

說明

本文主要介紹和Gradle關系密切、相對不容易理解的配置,偏重概念介紹。部分內容是Android特有的(例如ProductFlavor),其他內容則是所有Gradle工程都相同或類似的知識。

對于一些常規且相對簡單的配置項,例如簽名配置SigningConfig,不做具體介紹。而部分比較復雜且不太常用的內容,例如Manifest的具體合并規則,只做簡單說明,并給出深入學習的相關鏈接,讀者可自行閱讀。

文章主要基于Gradle V3.3 + Android Gradle Plugin V2.3 + Android Studio 2.3,在后續版本升級后部分內容可能會改變。

文中的內容,有些是根據源碼分析得到,有些是參考了官方文檔,還有些參考了網上的文章。如有不正確的地方,歡迎指正。

關于Gradle DSL的語法原理和開發相關知識,可參考我的另一篇文章

Gradle開發快速入門——DSL語法原理與常用API介紹
http://www.paincker.com/gradle-develop-basics

基本問題

開始看本文前,可以思考下面這些關于Gradle的基本問題。文中會對這些問題進行解釋。

  1. settings.gradle有什么作用?

  2. repositories {}語句塊的作用?

  3. buildscriptallprojects的區別?

  4. ProductFlavor和BuildType的區別?

  5. 依賴沖突的原因和常見解決思路?

  6. classpath 'com.android.tools.build:gradle:2.2.3',有什么作用?

  7. gradle/wrapper/gradle-wrapper.properties中的這句有什么作用?
    distributionUrl=https://services.gradle.org/distributions/gradle-2.14.1-all.zip

  8. 命令行中,gradle assemble、gradle assembleDebug、gradle build 的關系?

  9. Android Studio環境下,Gradle Sync操作做了什么工作?

Gradle

Gradle是一個基于Groovy語言的強大的構建系統,Groovy則是在Java基礎上擴展的、運行在JVM上的一種腳本語言。

通過豐富的插件擴展,Gradle可以支持Java、JavaWeb、Groovy、Android等工程的編譯,同時可以很方便的從Maven、Ant等遷移過來。

C系列語言也有相應的Gradle插件,但Gradle支持最好的還是Java系列語言。

Gradle也是一個命令行可執行程序,可從官網下載Gradle,可執行文件位于bin/gradle

執行Gradle任務的過程,主要就是在運行Java/Groovy代碼。編譯期間如果有代碼拋出了異常,就會中斷編譯過程。

在Android Studio中開發時,編譯就是基于Gradle實現的。Android Studio中內置了Gradle。

Gradle官網
https://gradle.org/

Gradle Wrapper

用IDEA/Android Studio創建基于Gradle的工程時,默認會在工程根目錄創建GradleWrapper,包括gradlew可執行腳本和gradle/wrapper文件夾,其中指定了和工程配套的gradle版本。

在工程根目錄下直接執行./gradlew,會自動將參數傳給wrapper指定版本的gradle,執行對應的命令;如果本機還沒有該版本的gradle,則會先自動下載。

工程配置和Gradle版本通常需要對應,不正確的Gradle版本可能無法正常編譯工程,因此推薦使用GradleWrapper執行Gradle命令。

gradle/wrapper/gradle-wrapper.properties文件,指定了gradle版本、下載地址、下載的文件存放位置(Mac系統中默認在~/.gradle/wrapper/dists目錄)。此文件內容示例:

distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip

在Android Studio/IDEA中,可通過Preferences - Build, Execution, Deployment - Gradle,設置Project-level settings中Use default gradle wrapper,指定Android Studio使用工程配置的Gradle Wrapper。

DSL、DSL Reference

Gradle使用Groovy語言封裝了一整套API,通常把這套API稱為DSL(Domain-Specific Languages,領域特定語言)。

通常在我們配置Gradle編譯參數時,所寫的gradle腳本從形式上來看,像是一門有著特殊語法格式的語言。

Gradle封裝的DSL,按照固定格式用很簡單的語法就能實現很復雜的配置,大大簡化了配置工作。另一方面,也正是由于封裝的非常完善,想深入學習Gradle,會感覺無從下手。

可以通過DSL Reference文檔查看Gradle DSL支持的語法配置項。例如:

Gradle DSL Reference(Gradle原生支持的DSL配置)
https://docs.gradle.org/current/dsl/

Android Plugin DSL Reference(Android的DSL配置)
http://google.github.io/android-gradle-dsl/current/

關于Gradle DSL的語法原理和開發相關知識,可參考我的另一篇文章

Gradle開發快速入門——DSL語法原理與常用API介紹
http://www.paincker.com/gradle-develop-basics

Project、RootProject、SubProject (Module)

Project是Gradle中的基本概念之一,即一個工程。一個工程可以包含多個SubProject,也稱為Module,最頂層的工程也稱為RootProject。

一個標準的Android工程,文件結構如下。

  • 每個build.gradle對應一個Project,最外層的是RootProject,里面的app是SubProject。

  • settings.gradle不是必須的,一般在包含子工程時就需要用這個文件指定,即我們通常所見的include ':app'腳本。

  • 這里的':app'就是子工程的名字,通常和文件夾名稱對應。

settings.gradle
build.gradle
app
    build.gradle

StartParameter

Gradle執行時有一些稱為StartParameter的參數,StartParameter可在命令行設置,可通過gradle --help查看。例如:

  1. --quiet,執行過程中,只顯示Error級別的Log。
  2. --stacktrace,執行過程中,輸出所有Exception的stacktrace。
  3. --full-stacktrace,執行過程中,輸出所有Exception的完整stacktrace。
  4. --no-daemon,不使用Deamon。Deamon是用于加速Gradle執行的后臺進程,有些情況下使用Deamon會有問題(可參考 https://docs.gradle.org/current/userguide/gradle_daemon.html
  5. --offline,離線模式,不使用網絡資源。

在命令行可通過-P參數傳入projectProperties,并在Gradle腳本中獲取

# 命令行傳入projectProperties
./gradlew clean -Pkey=value
// gradle腳本中獲取projectProperties
print gradle.startParameter.projectProperties.get('key')

還可以通過-D參數傳入systemPropertiesArgs,并在Gradle腳本中獲取

# 命令行傳入systemPropertiesArgs
./gradlew clean -Dkey=value
// gradle腳本中獲取systemPropertiesArgs
print gradle.startParameter.systemPropertiesArgs.get('key')

gradle.properties

Properties文件格式可由java.util.Properties解析,包含若干鍵值對,類似HashMap<String,String>

Gradle運行時會自動讀取gradle.properties文件并引用其中的屬性。有多個位置可以放gradle.properties文件,按優先級從低到高如下:

  • Project所在目錄(即build.gradle所在目錄),包括RootProject和SubProject
  • GradleHome目錄(Mac中默認為~/gradle
  • 通過gradle命令行-D參數指定的Property

在gradle.properties文件中,一些保留Key可用于配置Gradle運行環境,例如org.gradle.daemon用于設置GradleDeamon,org.gradle.logging.level用于設置Gradle的Log級別等。

詳情可參考
https://docs.gradle.org/current/userguide/build_environment.html

除了保留Key以外,其他Key都可以作為變量用于配置工程。例如在Project目錄的gradle.properties中統一定義Support包的版本號,然后在build.gradle中使用定義的變量如下。

SUPPORT_LIBRARY_VERSION=23.4.0
dependencies {
    compile "com.android.support:support-v4:${SUPPORT_LIBRARY_VERSION}"
    compile "com.android.support:appcompat-v7:${SUPPORT_LIBRARY_VERSION}"
    compile "com.android.support:design:${SUPPORT_LIBRARY_VERSION}"
    compile "com.android.support:recyclerview-v7:${SUPPORT_LIBRARY_VERSION}"
}

Gradle Task

Gradle以Task(任務)的形式組織每一步操作,每個Task執行一個原子操作(例如把Java編譯成class文件、把class打成jar/dex文件、APK簽名等)。

每個Project包含若干Task,Task之間存在依賴關系,執行一個Task前,會先執行它所依賴的Task。

每個Task有自己的名字(例如'assemble'),結合其所屬Project的名字(例如':app'),可以組成完整名(例如':app:assemble')。

Gradle內建了一個名為tasks的Task,可以列舉Project中的所有Task。

執行Task,查看Project中的所有Task

執行Task時,直接把Task名稱傳給gradle即可。

如果下載了Gradle并配置了環境變量,則可在工程根目錄執行:

gradle tasks

更推薦的做法,是在工程根目錄下調用GradleWrapper執行:

./gradlew tasks

執行結果如下(省略了部分輸出):

$ ./gradlew tasks
:tasks

------------------------------------------------------------
All tasks runnable from root project
------------------------------------------------------------

Android tasks
-------------
androidDependencies - Displays the Android dependencies of the project.
signingReport - Displays the signing info for each variant.
sourceSets - Prints out all the source sets defined in this project.

Build tasks
-----------
assemble - Assembles all variants of all applications and secondary packages.
assembleAndroidTest - Assembles all the Test applications.
assembleDebug - Assembles all Debug builds.
assembleRelease - Assembles all Release builds.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
classes - Assembles main classes.
clean - Deletes the build directory.
cleanBuildCache - Deletes the build cache directory.
compileDebugAndroidTestSources
compileDebugSources
compileDebugUnitTestSources
compileReleaseSources
compileReleaseUnitTestSources
jar - Assembles a jar archive containing the main classes.
mockableAndroidJar - Creates a version of android.jar that's suitable for unit tests.
testClasses - Assembles test classes.

Build Setup tasks
-----------------
init - Initializes a new Gradle build. [incubating]
wrapper - Generates Gradle wrapper files. [incubating]

Documentation tasks
-------------------
javadoc - Generates Javadoc API documentation for the main source code.

Help tasks
----------
buildEnvironment - Displays all buildscript dependencies declared in root project 'AndroidLint'.
components - Displays the components produced by root project 'AndroidLint'. [incubating]
dependencies - Displays all dependencies declared in root project 'AndroidLint'.
dependencyInsight - Displays the insight into a specific dependency in root project 'AndroidLint'.
dependentComponents - Displays the dependent components of components in root project 'AndroidLint'. [incubating]
help - Displays a help message.
model - Displays the configuration model of root project 'AndroidLint'. [incubating]
projects - Displays the sub-projects of root project 'AndroidLint'.
properties - Displays the properties of root project 'AndroidLint'.
tasks - Displays the tasks runnable from root project 'AndroidLint' (some of the displayed tasks may belong to subprojects).

Install tasks
-------------
installDebug - Installs the Debug build.
installDebugAndroidTest - Installs the android (on device) tests for the Debug build.
uninstallAll - Uninstall all applications.
uninstallDebug - Uninstalls the Debug build.
uninstallDebugAndroidTest - Uninstalls the android (on device) tests for the Debug build.
uninstallRelease - Uninstalls the Release build.

Verification tasks
------------------
check - Runs all checks.
connectedAndroidTest - Installs and runs instrumentation tests for all flavors on connected devices.
connectedCheck - Runs all device checks on currently connected devices.
connectedDebugAndroidTest - Installs and runs the tests for debug on connected devices.
deviceAndroidTest - Installs and runs instrumentation tests using all Device Providers.
deviceCheck - Runs all device checks using Device Providers and Test Servers.
lint - Runs lint on all variants.
lintDebug - Runs lint on the Debug build.
lintRelease - Runs lint on the Release build.
test - Run unit tests for all variants.
testDebugUnitTest - Run unit tests for the debug build.
testReleaseUnitTest - Run unit tests for the release build.

To see all tasks and more detail, run gradlew tasks --all

To see more detail about a task, run gradlew help --task <task>

BUILD SUCCESSFUL

Total time: 1.367 secs

執行多個Task

如果需要先后執行多個Task,例如tasks和clean,將它們依次傳給gradle即可:

./gradlew tasks clean

排除特定Task

使用gradle的-x--exclude-task參數,可指定執行Task時排除特定Task。例如:

./gradlew build -x check

執行SubProject中的Task

如果想執行子工程':app'中的Task,可使用Task的完整名執行

./gradlew :app:tasks

也可以切換到子工程目錄執行,但切換當前目錄會影響Gradle腳本中的相對路徑,不推薦。

cd app
gradle tasks

Task參數、查看Task詳細信息

Task可以定義參數,可在執行時從命令行傳入。例如Gradle內建了一個叫“help”的Task,帶有一個--task參數,可以用于查看一個Task的詳細信息。

查看“tasks”這個Task的詳細信息,就可以執行命令如下。其中會顯示一個Task的名稱、類型、參數、詳細介紹、分組等。

$ ./gradlew help --task tasks
:help
Detailed task information for tasks

Path
     :tasks

Type
     TaskReportTask (org.gradle.api.tasks.diagnostics.TaskReportTask)

Options
     --all     Show additional tasks and detail.

Description
     Displays the tasks runnable from root project 'GradleStudy' (some of the displayed tasks may belong to subprojects).

Group
     help

BUILD SUCCESSFUL

Total time: 1.051 secs

一些常用GradleTask

  • clean: 清除build目錄編譯生成的文件 (Deletes the build directory.)
  • assemble:編譯工程 (Assembles the outputs of this project. [jar])
  • build:編譯并測試工程 (Assembles and tests this project. [assemble, check])
  • test:單元測試等 (Runs the unit tests. [classes, testClasses])
  • check:測試工程,包含test (Runs all checks. [test])
  • wrapper:生成GradleWrapper文件 (Generates Gradle wrapper files. [incubating])
  • help: 幫助信息 (Displays a help message.)
  • tasks:查看Project的所有Task (Displays the tasks runnable from root project 'Xxx'.)
  • dependencies:查看Project的依賴 (Displays all dependencies declared in root project 'Xxx'.)
  • projects: 查看SubProject (Displays the sub-projects of root project 'Xxx'.)

查看Task依賴樹

每個Task都會依賴若干Task,這些Task又會依賴別的Task,所有Task就會形成一個依賴樹。

為了更加直觀的學習,可以在app/build.gradle中添加如下的簡單腳本,讓Gradle輸出Task的依賴樹。

方法printDependencies通過遞歸的方式,輸出每個Task依賴的Task。afterEvaluate語句塊中,先找到assembleDebug這個Task,然后調用printDependencies輸出其依賴樹。由于Android中有大量Task依賴,打印出所有Task需要很久,所以這里限制了最大遞歸深度為3。

def printDependencies(Task task, String prefix, int depth, int maxDepth) {
    println prefix + task.project.name + ':' + task.name
    def tasks = task.getTaskDependencies().getDependencies(task)
    if (depth < maxDepth - 1) {
        tasks.each { t ->
            printDependencies(t, prefix + '\t', depth + 1, maxDepth)
        }
    } else {
        if (tasks.size() > 0) {
            println prefix + '\t' + "(${tasks.size()} child tasks...)"
        }
    }
}

afterEvaluate {
    println '==============================='
    def buildTask = tasks.findByName('assembleDebug')
    printDependencies(buildTask, '', 0, 3)
    println '==============================='
}

執行任意Gradle任務,例如clean,部分輸出內容如下。

$ ./gradlew clean
===============================
app:assembleDebug
    app:packageDebug
        app:processDebugResources
            (2 child tasks...)
        app:compileDebugJavaWithJavac
            (4 child tasks...)
        app:mergeDebugAssets
            (2 child tasks...)
        app:validateSigningDebug
        app:transformNativeLibsWithMergeJniLibsForDebug
            (3 child tasks...)
        app:transformResourcesWithMergeJavaResForDebug
            (2 child tasks...)
        app:transformClassesWithDexForDebug
            (2 child tasks...)
    app:compileDebugSources
        app:compileDebugNdk
            (1 child tasks...)
        app:compileDebugJavaWithJavac
            (4 child tasks...)
===============================
:clean
:app:clean

BUILD SUCCESSFUL

Total time: 0.975 secs

buildscript與allprojects

在RootProject的build.gradle中,經常會看到buildscript和allprojects兩個語句塊,并且里面都定義了一些相同的東西。

buildscript,顧名思義,是編譯腳本,也就是說編譯一個工程時需要的配置,例如常會看到下面這樣的腳本,表示編譯時要用到Android Gradle Plugin。

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.3'
    }
}

allprojects,則用于配置所有project,包括SubProject,這里面的配置的東西,則是工程代碼需要的東西,例如依賴的各種開源庫等。

allprojects {
    repositories {
        jcenter()
    }
}

關于編譯與Groovy

編譯就是將程序源碼轉換成可執行文件或中間代碼的過程。具體到Java,就是將.java代碼文件變成.class或者進一步打包成.jar的過程。

Gradle基于Groovy,Groovy是在Java基礎上擴展的腳本語言。Groovy有和解釋型語言一樣的特性,可以直接從源碼運行而不需要提前編譯。但實際運行過程中,也是先轉換成Java的class文件,再運行在JVM上的。

在buildscript的dependencies中,通過classpath語句引用一些編譯好的jar包,Gradle在執行時就會將其下載并加入Java的classpath,其中的class在編譯時就可以被調用,運行在電腦或云主機上。

Gradle Plugin

Gradle之所以是個強大的構建系統,很重要的一點在于其完善的插件支持。

Gradle內建了Java、Groovy等插件,除此之外,還可以在Gradle提供的一整套API基礎上開發插件,實現各種編譯打包工作。

Gradle Android Plugin

在Android開發編譯時,會有很多Android相關的配置,這些都是由Gradle的Android插件實現的。

在buildscript中,通過dependencies引入了Gradle Android插件:

buildscript {
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.3'
    }
}

在app/build.gradle中,通過apply的方式,應用了Android插件:

// 對于Android Application(APK)
apply plugin: 'com.android.application'

// 對于Android Library(AAR)
apply plugin: 'com.android.library'

應用了Android插件后,即可在app/build.gradle中使用插件定義的Android相關DSL了:

android {
    compileSdkVersion 24
    buildToolsVersion '25.0.2'

    defaultConfig {
        applicationId "com.paincker.lint.demo"
        minSdkVersion 15
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
    }
}

Repositories

很多從Eclipse轉到Android Studio的開發者,剛開始都對Gradle自動下載依賴包的功能印象深刻。

Gradle的依賴管理完全兼容Maven和Ivy,常使用Maven倉庫來實現依賴管理,當Library打包完成后上傳到Maven倉庫,Gradle則會從Maven倉庫下載需要的依賴。

Repository就是用來指定所需要的Maven倉庫。除了常見的jcenter(),mavenCentral(),還可以指定本地搭建的Maven倉庫、指定URL的Maven倉庫等,例如國內一些Maven倉庫鏡像,以及很多公司內部私有的Maven倉庫等。

repositories {
    jcenter()
    mavenCentral()
    maven { url 'http://maven.oschina.net/content/groups/public/' }
    ivy { url "http://repo.mycompany.com/repo" }
    localRepository { dirs 'lib' }
    maven {
        url "sftp://repo.mycompany.com:22/maven2"
        credentials {
            username 'user'
            password 'password'
        }
    }
}

Dependencies

Gradle依賴管理官方文檔
https://docs.gradle.org/current/userguide/dependency_management.html

DependencyNotation

DependencyNotation用于描述要依賴的模塊。

外部依賴

通常用group:name:version:classifier@ext的形式表示。其中group通常用包名,name表示實際的名字,version表示版本,classifier在Android中是ProductFlavor和BuildType的組合(后面會介紹),ext則表示擴展名。

compile "org.gradle.test.classifiers:service:1.0:jdk15@jar"

compile group: 'org.gradle.test.classifiers', name: 'service', version: '1.0', classifier: 'jdk15'

Project依賴

compile project(':someProject')

文件依賴

dependencies {
  //declaring arbitrary files as dependencies
  compile files('hibernate.jar', 'libs/spring.jar')

  //putting all jars from 'libs' onto compile classpath
  compile fileTree('libs')
}

參考官方文檔
https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.dsl.DependencyHandler.html

依賴傳遞(transitive)

Gradle依賴項可配置transitive屬性,表示是否遞歸解析此模塊的依賴項,默認為true。

compile('org.hibernate:hibernate:3.0.5') {
    transitive = true
}

依賴樹查看

每個模塊都會依賴若干模塊,這些模塊又分別依賴其他模塊,形成一個依賴樹。Gradle提供了名為dependencies的Task,可查看Project的依賴樹,執行效果如下(省略了部分輸出)。

$ ./gradlew :app:dependencies

------------------------------------------------------------
Project :app
------------------------------------------------------------

compile - Classpath for compiling the main sources.
+--- com.android.support:appcompat-v7:24.0.0
|    +--- com.android.support:support-v4:24.0.0
|    |    \--- com.android.support:support-annotations:24.0.0
|    +--- com.android.support:support-vector-drawable:24.0.0
|    |    \--- com.android.support:support-v4:24.0.0 (*)
|    \--- com.android.support:animated-vector-drawable:24.0.0
|         \--- com.android.support:support-vector-drawable:24.0.0 (*)
\--- com.android.support.constraint:constraint-layout:1.0.2
     \--- com.android.support.constraint:constraint-layout-solver:1.0.2

Artifact

Artifact可以理解成一個模塊的具體實現。一個依賴項可以包含多個Artifact,例如依賴項com.demo:mymodule:library:1.0中可以包含多個不同格式、BuildType的Artifact:

  • library-1.0-debug.jar
  • library-1.0-release.jar
  • library-1.0-debug.aar
  • library-1.0-release.aar

Module Descriptor、POM文件

Gradle是如何獲取到一個模塊的依賴項的呢?

在Maven或Ivy倉庫中,模塊的依賴信息并不包含在Artifact文件中,而是通過ModuleDescriptor文件聲明的。

以阿里的Maven倉庫為例,用瀏覽器打開鏈接
http://maven.aliyun.com/nexus/content/groups/public/com/android/tools/build/gradle/2.3.0/

可以看到com.android.tools.build:gradle:2.3.0這個模塊所包含的文件。其中sources.jar為代碼源文件,pom文件則為ModuleDescriptor。

  • gradle-2.3.0-sources.jar
  • gradle-2.3.0-sources.jar.md5
  • gradle-2.3.0-sources.jar.sha1
  • gradle-2.3.0.jar
  • gradle-2.3.0.jar.md5
  • gradle-2.3.0.jar.sha1
  • gradle-2.3.0.pom
  • gradle-2.3.0.pom.md5
  • gradle-2.3.0.pom.sha1

在Maven倉庫中,模塊的POM文件可以指定默認的Artifact,并聲明其依賴項;不支持分別聲明多個Artifact的依賴項。AAR工程配置多版本發布的時候,需要考慮這一特性。

If you declare a module dependency, Gradle looks for a module descriptor file (pom.xml or ivy.xml) in the repositories. If such a module descriptor file exists, it is parsed and the artifacts of this module (e.g. hibernate-3.0.5.jar) as well as its dependencies (e.g. cglib) are downloaded. If no such module descriptor file exists, Gradle looks for a file called hibernate-3.0.5.jar to retrieve. In Maven, a module can have one and only one artifact. In Gradle and Ivy, a module can have multiple artifacts. Each artifact can have a different set of dependencies.

多Artifact的依賴處理、Artifact only notation

Gradle在處理依賴時,對于有多個Artifact的Maven模塊,可在DependencyNotation中聲明需要的Artifact,沒有聲明則使用POM文件指定的默認版本,POM中也沒有指定則默認使用和module名一致的jar包。

當使用“@”指定了所依賴模塊的Artifact,稱為Artifact only notation,此時Gradle只會下載對應的Artifact,而不會下載其依賴,此時可能就需要設置transitive屬性。

compile('com.facebook.fresco:fresco:0.10.0@aar') {
    transitive = true
}

If no module descriptor file can be found, Gradle by default downloads a jar with the name of the module. But sometimes, even if the repository contains module descriptors, you want to download only the artifact jar, without the dependencies. [11] And sometimes you want to download a zip from a repository, that does not have module descriptors. Gradle provides an artifact only notation for those use cases - simply prefix the extension that you want to be downloaded with '@' sign.

依賴沖突分解

依賴項很多時,依賴項之間經常會發生沖突。例如多個SDK分別依賴了不同版本的AppCompat,就可能導致沖突。Gradle提供了一些API可以用來處理依賴沖突。

常見的依賴沖突解決思路可參考:

Gradle依賴項學習總結,dependencies、transitive、force、exclude的使用與依賴沖突解決
http://www.paincker.com/gradle-dependencies

ProductFlavor、BuildType與Build Variant

Android中定義了ProductFlavor和BuildType的DSL。

ProductFlavor

ProductFlavor可以實現一套代碼編譯成不同的版本,版本之間差異比較小,例如開發版本、測試版本、線上版本;或是發布到某些應用市場的定制版本(例如需要修改一些資源文件)等。

ProductFlavor中包含了一些應用相關的配置,例如minSdkVersion,versionCode等。下面的代碼,就是在對默認的ProductFlavor做配置。

android {
    defaultConfig {
        applicationId "com.paincker.gradle.demoapplication"
        minSdkVersion 15
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
    }
}

可以在ProductFlavors中定義新的Flavor并進行配置,覆蓋DefaultProductFlavor中的相應配置。

android {
    productFlavors {
        myflavor {
            minSdkVersion 20
        }
    }
}

ProductFlavor還支持多維度(Multi-flavors),每個緯度之間可以進行組合。例如下面的示例,flavor有abi和version兩個緯度,最后就會有6種組合:

  • x86-freeapp
  • arm-freeapp
  • mips-freeapp
  • x86-paidapp
  • arm-paidapp
  • mips-paidapp
android {
    ...
    flavorGroups "abi", "version"
    productFlavors {
        freeapp {
            flavorGroup "version"
            ...
        }
        paidapp {
            flavorGroup "version"
            ...
        }

        x86 {
            flavorGroup "abi"
            ...
        }
        arm {
            flavorGroup "abi"
            ...
        }
        mips {
            flavorGroup "abi"
            ...
        }
    }
}

參考:多定制的變種版本
https://flyouting.gitbooks.io/gradle-plugin-user-guide-cn/content/multi-flavor_variants.html

BuildType

BuildType本身是軟件開發中的通用概念,表示編譯版本。

Android中定義了自己的BuildType接口,其中包含了一些編譯相關的配置,例如debuggable(是否可調試)、minifyEnable(是否開啟Proguard)等。

可以在buildTypes中配置支持的BuildType如下。即使不做任何配置,默認也會有Debug和Release兩個BuildType,且分別包含了一套默認值,例如Debug的debuggable參數默認為true,而Release的debuggable參數默認為false。

android {
    buildTypes {
        debug {
        }
        develop {
            debuggable false
            minifyEnabled false
        }
        release {
            debuggable false
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

BuildVariant

ProductFlavor與BuildType組合,就成為BuildVariant。在前面多緯度Flavor的示例基礎上,6個Flavor組合2個BuildType,總共就會有12種BuildVariant。

每個Build Variant包含了一個ProductFlavor和一個BuildType。BuildType和ProductFlavor中某些配置是重疊的,組合成BuildVariant后,通常會按優先級處理(一般BuildType優先級更高),或做合并處理。

例如兩者都可以配置簽名signingConfigs,合并成BuildVariant后,會優先取取BuildType中定義的;而對于配置項proguardFiles,則會采取合并處理。

具體的合并邏輯,可以參考這個類中的實現
com.android.builder.core.VariantConfiguration

BuildConfig

Android開發常用到BuildConfig類,這個類可在編譯時生成,用于在代碼中獲取一些編譯相關的參數,包括是否可以Debug、當前BuildType和Flavor名字、VersionCode和VersionName等,例如常會用BuildConfig.DEBUG判斷是否輸出Log信息。

BuildType配置中提供了一個buildConfigField方法,可以往BuildConfig中添加自定義字段。

int xxLibraryVersion = 192
android {
    buildTypes {
        debug {
            buildConfigField "int", "XX_LIBRARY_VERSION", xxLibraryVersion
        }
        release {
            buildConfigField "int", "XX_LIBRARY_VERSION", xxLibraryVersion
        }
    }
}
dependencies {
    compile "com.xxx:xx:1.0.${xxLibraryVersion}"
}
public final class BuildConfig {
  public static final boolean DEBUG = Boolean.parseBoolean("true");
  public static final String BUILD_TYPE = "debug";
  // Fields from build type: debug
  public static final int XX_LIBRARY_VERSION = 192;
}

private void showLibraryInfo() {
    String msg = "xx library version is " + BuildConfig.XX_LIBRARY_VERSION;
    Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
}

Configurations

在Gradle中,一個Project可以有多個Configuration,每個Configuration有不同的依賴配置。

例如在dependencies中,經常用到compile xxx,這里的compile就是一個Configuration。一些常用的Configuration例如:

  • compile:最常用,參與編譯并打包到APK中
  • testCompile:用于單元測試
  • androidTestCompile:用于Android自動化測試
  • provided:參與編譯但不打包到APK中,類似eclipse中的external-libs
  • apk:打包到APK中但不參與編譯,不能在代碼中調用

考慮到BuildType和ProductFlavor,又會和上述Configuration組合成新的Configuration,例如:

  • compile
  • debugCompile
  • myflavorCompile
  • myflavorDebugCompile
  • testCompile
  • testDebugCompile
  • testMyflavorCompile
  • testMyflavorDebugCompile

在運行gradle dependencies時,也會分別顯示每個Configuration對應的依賴樹,如下(省略了部分輸出)。

./gradlew :app:dependencies

:app:dependencies

------------------------------------------------------------
Project :app
------------------------------------------------------------

androidJacocoAgent - The Jacoco agent to use to get coverage data.
\--- org.jacoco:org.jacoco.agent:0.7.5.201505241946

androidJacocoAnt - The Jacoco ant tasks to use to get execute Gradle tasks.
\--- org.jacoco:org.jacoco.ant:0.7.5.201505241946
     +--- org.jacoco:org.jacoco.core:0.7.5.201505241946
     |    \--- org.ow2.asm:asm-debug-all:5.0.1
     +--- org.jacoco:org.jacoco.report:0.7.5.201505241946
     |    +--- org.jacoco:org.jacoco.core:0.7.5.201505241946 (*)
     |    \--- org.ow2.asm:asm-debug-all:5.0.1
     \--- org.jacoco:org.jacoco.agent:0.7.5.201505241946

androidTestAnnotationProcessor - Classpath for the annotation processor for 'androidTest'.
No dependencies

androidTestApk - Classpath packaged with the compiled 'androidTest' classes.
No dependencies

androidTestCompile - Classpath for compiling the androidTest sources.
No dependencies

androidTestJackPlugin - Classpath for the 'androidTest' Jack plugins.
No dependencies

androidTestProvided - Classpath for only compiling the androidTest sources.
No dependencies

androidTestWearApp - Link to a wear app to embed for object 'androidTest'.
No dependencies

annotationProcessor - Classpath for the annotation processor for 'main'.
No dependencies

apk - Classpath packaged with the compiled 'main' classes.
No dependencies

archives - Configuration for archive artifacts.
No dependencies

compile - Classpath for compiling the main sources.
+--- com.android.support:appcompat-v7:24.0.0
|    +--- com.android.support:support-v4:24.0.0
|    |    \--- com.android.support:support-annotations:24.0.0
|    +--- com.android.support:support-vector-drawable:24.0.0
|    |    \--- com.android.support:support-v4:24.0.0 (*)
|    \--- com.android.support:animated-vector-drawable:24.0.0
|         \--- com.android.support:support-vector-drawable:24.0.0 (*)
\--- com.android.support.constraint:constraint-layout:1.0.2
     \--- com.android.support.constraint:constraint-layout-solver:1.0.2

debugAnnotationProcessor - Classpath for the annotation processor for 'debug'.
No dependencies

debugApk - Classpath packaged with the compiled 'debug' classes.
No dependencies

debugCompile - Classpath for compiling the debug sources.
No dependencies

debugJackPlugin - Classpath for the 'debug' Jack plugins.
No dependencies

debugProvided - Classpath for only compiling the debug sources.
No dependencies

debugWearApp - Link to a wear app to embed for object 'debug'.
No dependencies

default - Configuration for default artifacts.
No dependencies

default-mapping - Configuration for default mapping artifacts.
No dependencies

default-metadata - Metadata for the produced APKs.
No dependencies

jackPlugin - Classpath for the 'main' Jack plugins.
No dependencies

provided - Classpath for only compiling the main sources.
No dependencies

...

SourceSet

Gradle中使用SourceSet管理Java源碼;Android定義了自己的SourceSet,其用法和Gradle類似。

Gradle的SourceSet官方文檔:
https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceSet.html

Android的SourceSet官方文檔:
http://google.github.io/android-gradle-dsl/current/com.android.build.gradle.api.AndroidSourceSet.html

默認的SourceSet即main,根目錄位于src/main,其中又包括多個子目錄,例如:

  • src/main/java,Java源碼
  • src/main/resources,Java資源文件
  • src/main/res,Android資源文件
  • src/main/assets,Android assets文件

ProductFlavor、BuildType、BuildVariant、test/androidTest,也會按照一定的形式組合產生SourceSet。

可以配置某個SourceSet的根目錄,或者指定具體的子目錄(支持多個目錄),如下:

android {
    sourceSets {
        myflavor {
            res.srcDirs = ['src/main/res', 'src/main/res2']
        }
        debug.setRoot('src/main')
    }
}

使用Gradle的sourceSet可以查看項目中所有的SourceSet。

$ ./gradlew :app:sourceSets
:app:sourceSets

------------------------------------------------------------
Project :app
------------------------------------------------------------

androidTest
-----------
Compile configuration: androidTestCompile
build.gradle name: android.sourceSets.androidTest
Java sources: [app/src/androidTest/java]
Manifest file: app/src/androidTest/AndroidManifest.xml
Android resources: [app/src/androidTest/res]
Assets: [app/src/androidTest/assets]
AIDL sources: [app/src/androidTest/aidl]
RenderScript sources: [app/src/androidTest/rs]
JNI sources: [app/src/androidTest/jni]
JNI libraries: [app/src/androidTest/jniLibs]
Java-style resources: [app/src/androidTest/resources]

androidTestMyflavor
-------------------
Compile configuration: androidTestMyflavorCompile
build.gradle name: android.sourceSets.androidTestMyflavor
Java sources: [app/src/androidTestMyflavor/java]
Manifest file: app/src/androidTestMyflavor/AndroidManifest.xml
Android resources: [app/src/androidTestMyflavor/res]
Assets: [app/src/androidTestMyflavor/assets]
AIDL sources: [app/src/androidTestMyflavor/aidl]
RenderScript sources: [app/src/androidTestMyflavor/rs]
JNI sources: [app/src/androidTestMyflavor/jni]
JNI libraries: [app/src/androidTestMyflavor/jniLibs]
Java-style resources: [app/src/androidTestMyflavor/resources]

debug
-----
Compile configuration: debugCompile
build.gradle name: android.sourceSets.debug
Java sources: [app/src/debug/java]
Manifest file: app/src/debug/AndroidManifest.xml
Android resources: [app/src/debug/res]
Assets: [app/src/debug/assets]
AIDL sources: [app/src/debug/aidl]
RenderScript sources: [app/src/debug/rs]
JNI sources: [app/src/debug/jni]
JNI libraries: [app/src/debug/jniLibs]
Java-style resources: [app/src/debug/resources]

main
----
Compile configuration: compile
build.gradle name: android.sourceSets.main
Java sources: [app/src/main/java]
Manifest file: app/src/main/AndroidManifest.xml
Android resources: [app/src/main/res]
Assets: [app/src/main/assets]
AIDL sources: [app/src/main/aidl]
RenderScript sources: [app/src/main/rs]
JNI sources: [app/src/main/jni]
JNI libraries: [app/src/main/jniLibs]
Java-style resources: [app/src/main/resources]

myflavor
--------
Compile configuration: myflavorCompile
build.gradle name: android.sourceSets.myflavor
Java sources: [app/src/myflavor/java]
Manifest file: app/src/myflavor/AndroidManifest.xml
Android resources: [app/src/myflavor/res]
Assets: [app/src/myflavor/assets]
AIDL sources: [app/src/myflavor/aidl]
RenderScript sources: [app/src/myflavor/rs]
JNI sources: [app/src/myflavor/jni]
JNI libraries: [app/src/myflavor/jniLibs]
Java-style resources: [app/src/myflavor/resources]

myflavorDebug
-------------
Compile configuration: myflavorDebugCompile
build.gradle name: android.sourceSets.myflavorDebug
Java sources: [app/src/myflavorDebug/java]
Manifest file: app/src/myflavorDebug/AndroidManifest.xml
Android resources: [app/src/myflavorDebug/res]
Assets: [app/src/myflavorDebug/assets]
AIDL sources: [app/src/myflavorDebug/aidl]
RenderScript sources: [app/src/myflavorDebug/rs]
JNI sources: [app/src/myflavorDebug/jni]
JNI libraries: [app/src/myflavorDebug/jniLibs]
Java-style resources: [app/src/myflavorDebug/resources]

myflavorRelease
---------------
Compile configuration: myflavorReleaseCompile
build.gradle name: android.sourceSets.myflavorRelease
Java sources: [app/src/myflavorRelease/java]
Manifest file: app/src/myflavorRelease/AndroidManifest.xml
Android resources: [app/src/myflavorRelease/res]
Assets: [app/src/myflavorRelease/assets]
AIDL sources: [app/src/myflavorRelease/aidl]
RenderScript sources: [app/src/myflavorRelease/rs]
JNI sources: [app/src/myflavorRelease/jni]
JNI libraries: [app/src/myflavorRelease/jniLibs]
Java-style resources: [app/src/myflavorRelease/resources]

release
-------
Compile configuration: releaseCompile
build.gradle name: android.sourceSets.release
Java sources: [app/src/release/java]
Manifest file: app/src/release/AndroidManifest.xml
Android resources: [app/src/release/res]
Assets: [app/src/release/assets]
AIDL sources: [app/src/release/aidl]
RenderScript sources: [app/src/release/rs]
JNI sources: [app/src/release/jni]
JNI libraries: [app/src/release/jniLibs]
Java-style resources: [app/src/release/resources]

test
----
Compile configuration: testCompile
build.gradle name: android.sourceSets.test
Java sources: [app/src/test/java]
Java-style resources: [app/src/test/resources]

testDebug
---------
Compile configuration: testDebugCompile
build.gradle name: android.sourceSets.testDebug
Java sources: [app/src/testDebug/java]
Java-style resources: [app/src/testDebug/resources]

testMyflavor
------------
Compile configuration: testMyflavorCompile
build.gradle name: android.sourceSets.testMyflavor
Java sources: [app/src/testMyflavor/java]
Java-style resources: [app/src/testMyflavor/resources]

testMyflavorDebug
-----------------
Compile configuration: testMyflavorDebugCompile
build.gradle name: android.sourceSets.testMyflavorDebug
Java sources: [app/src/testMyflavorDebug/java]
Java-style resources: [app/src/testMyflavorDebug/resources]

testMyflavorRelease
-------------------
Compile configuration: testMyflavorReleaseCompile
build.gradle name: android.sourceSets.testMyflavorRelease
Java sources: [app/src/testMyflavorRelease/java]
Java-style resources: [app/src/testMyflavorRelease/resources]

testRelease
-----------
Compile configuration: testReleaseCompile
build.gradle name: android.sourceSets.testRelease
Java sources: [app/src/testRelease/java]
Java-style resources: [app/src/testRelease/resources]


BUILD SUCCESSFUL

Total time: 1.041 secs

SourceSet的合并、Manifest合并

對于某個具體的編譯任務中某一類源文件,會根據一定的優先級與合并規則,對多個SourceSet中的同類源文件進行合并得到。

一般情況下,優先級從高到底分別是:

  • BuildVariant(src/armFreeappDebug)
  • BuildType(src/debug)
  • MultiFlavor(src/armFreeapp)
  • 每個單獨的Flavor(src/arm, src/freeapp)
  • sourceSet.main(src/main)
  • 依賴中的對應文件(JAR、AAR或SubProject中解析出來的源文件)

對于Java類、drawable目錄下的圖片資源文件、layout等,合并措施一般就是同名文件直接覆蓋。

對于values目錄下的標簽類型資源,合并時會以每個標簽為最小單元進行覆蓋。

<!-- 多個SourceSet都定義了同名style標簽,則高優先級會直接覆蓋低優先級 -->
<!-- src/debug/res/values/xxx.xml  高優先級 -->
<style name="txt_main">
    <item name="android:textSize">12sp</item>
</style>

<!-- src/main/res/values/yyy.xml  低優先級 -->
<style name="txt_main">
    <item name="android:textSize">14sp</item>
    <item name="android:textColor">@android:color/white</item>
</style>

<!-- 合并后 -->
<style name="txt_main">
    <item name="android:textSize">12sp</item>
</style>

而Manifest文件的合并規則更復雜一些。具體可參考官方文檔:

https://developer.android.com/studio/build/manifest-merge.html

Android Gradle Task

Android Gradle環境下,有下面幾個常見的Task。

  • assemble:Gradle內建的編譯任務(生成APK / JAR / AAR)
  • test:Gradle內建的測試任務
  • lint:Android定義的Lint檢查任務
  • check:Gradle內建的檢查任務,依賴lint、test
  • build:Gradle內建的Build任務,依賴assemble、check

結合ProductFlavor、BuildType、BuildVariant,又會組合成很多Task。

這些Task的依賴關系示例如下:

app:build
        app:check
                app:lint
                app:test
                        app:testMyflavor2DebugUnitTest
                        app:testMyflavor2ReleaseUnitTest
                        app:testMyflavor1DebugUnitTest
                        app:testMyflavor1ReleaseUnitTest
        app:assemble
                app:assembleDebug
                        app:assembleMyflavor2Debug
                        app:assembleMyflavor1Debug
                app:assembleRelease
                        app:assembleMyflavor1Release
                        app:assembleMyflavor2Release

Android Studio/IDEA相關

在Android Studio/IDEA中開發時,會在Build Variants窗口選擇指定的Build參數,例如myflavor1Debug。

Sync Project With Gradle Files

當修改了gradle文件等情況,會提示:

Gradle files have changed since last project sync. A project sync may be necessary for the IDE to work properly.

此時點擊提示欄中的Sync,會觸發Gradle同步操作。剛打開工程,或者手動選擇菜單Tools-Android-Sync Project With Gradle Files或工具欄中的同步按鈕,也會觸發同步操作。

在同步過程中,Gradle會執行很多任務,包括解析并下載所有依賴項,解壓AAR、合并SourceSet、生成BuildConfig、R文件(結果會輸出到build目錄)等,這樣Android Studio就能加載所有引用的class、jar文件,對源碼進行語法解析,從而代碼也可以正常的跳轉了。

通過GradleConsole窗口,可以看到同步過程中Gradle所執行的Task,其中最主要的是generateMyflavor1DebugSources,即生成源碼的過程,依賴了prepareMyflavor1DebugDependencies、generateMyflavor1DebugBuildConfig、generateMyflavor1DebugResValues等Task。

:app:preBuild UP-TO-DATE
:app:preMyflavor1DebugBuild UP-TO-DATE
:app:checkMyflavor1DebugManifest
:app:preMyflavor1ReleaseBuild UP-TO-DATE
:app:preMyflavor2DebugBuild UP-TO-DATE
:app:preMyflavor2ReleaseBuild UP-TO-DATE
:app:prepareComAndroidSupportAnimatedVectorDrawable2400Library
:app:prepareComAndroidSupportAppcompatV72400Library
:app:prepareComAndroidSupportConstraintConstraintLayout102Library
:app:prepareComAndroidSupportSupportV42400Library
:app:prepareComAndroidSupportSupportVectorDrawable2400Library
:app:prepareComFacebookFrescoDrawee0100Library
:app:prepareComFacebookFrescoFbcore0100Library
:app:prepareComFacebookFrescoFresco0100Library
:app:prepareComFacebookFrescoImagepipeline0100Library
:app:prepareComFacebookFrescoImagepipelineBase0100Library
:app:prepareMyflavor1DebugDependencies
:app:compileMyflavor1DebugAidl
:app:compileMyflavor1DebugRenderscript
:app:generateMyflavor1DebugBuildConfig
:app:generateMyflavor1DebugResValues
:app:generateMyflavor1DebugResources
:app:mergeMyflavor1DebugResources
:app:processMyflavor1DebugManifest
:app:processMyflavor1DebugResources
:app:generateMyflavor1DebugSources
:app:preMyflavor1DebugAndroidTestBuild UP-TO-DATE
:app:prepareMyflavor1DebugAndroidTestDependencies
:app:compileMyflavor1DebugAndroidTestAidl
:app:processMyflavor1DebugAndroidTestManifest
:app:compileMyflavor1DebugAndroidTestRenderscript
:app:generateMyflavor1DebugAndroidTestBuildConfig
:app:generateMyflavor1DebugAndroidTestResValues
:app:generateMyflavor1DebugAndroidTestResources
:app:mergeMyflavor1DebugAndroidTestResources
:app:processMyflavor1DebugAndroidTestResources
:app:generateMyflavor1DebugAndroidTestSources
:app:mockableAndroidJar UP-TO-DATE
:app:preMyflavor1DebugUnitTestBuild UP-TO-DATE
:app:prepareMyflavor1DebugUnitTestDependencies

BUILD SUCCESSFUL

Total time: 6.425 secs

Run

點擊三角形箭頭時,除了執行assembleMyflavor1Debug,還會執行install(將APK安裝至設備)等Task。

在Android Studio的Run/Debug Configuration窗口中,還可以指定點擊三角箭頭時要執行的Gradle Task。例如可以添加一個clean操作,每次編譯前先用clean清理build目錄。

AAR多版本發布

Android開發經常會用到AAR,有時候希望AAR能支持發布多個版本,并在不同的情況下依賴不同版本(包括不同的BuildType和ProductFlavor)。例如主工程依賴xxLibrary,希望Debug版本APK依賴Debug版本的AAR,而Release版本APK依賴Release版本的AAR。

在Android中依賴SubProject或AAR時,如果沒有特殊配置,AAR的發布和模塊依賴默認均為Release版本。

實際嘗試主工程依賴子工程,子工程中讀取BuildConfig.DEBUG的值始終是false,修改Android Studio中子模塊的BuildVariant配置也沒有效果。

dependencies {
    compile project(':xxx_library')
    compile 'com.xxx:xxx:1.0.5@aar' {
        transitive = true
    }
}

可用如下方式配置子模塊或者獨立AAR工程發布所有版本:

apply plugin: 'com.android.library'

android {
    // 發布非默認版本
    publishNonDefault true
    // 指定默認版本,發布AAR到Maven時會從默認版本生成POM依賴配置。
    // 不指定會導致POM無法正確生成,從而依賴不能傳遞。
    defaultPublishConfig "falvorARelease"

    productFlavors {
        flavorA { }
        flavorB { }
    }
}

用下面的方式依賴子模塊或已經發布的AAR:

dependencies {

    // Debug、Release版本APK,分別依賴Debug、Release版本子模塊
    debugCompile project(path: ':xxx', configuration: 'flavorADebug')
    releaseCompile project(path: ':xxx', configuration: 'flavorARelease')

    // 指定依賴aar版本,并設置transitive為true
    debugCompile('com.xxx:library:1.0.5:flavorADebug@aar') {
        transitive = true
    }
    releaseCompile('com.xxx:library:1.0.5:flavorARelease@aar') {
        transitive = true
    }
}

本文首發自我的個人博客 http://www.paincker.com/android-gradle-basics

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

推薦閱讀更多精彩內容