Gradle用戶使用指南

轉(zhuǎn)載注明出處:http://www.itdecent.cn/p/5255b100930e

0. 前言

完全由個人翻譯,能力有限,有些細節(jié)地方翻譯不是很通順,大家可以參考Gradle Plugin User Guide英文版本閱讀,如果有問題,歡迎指正。

譯文git工程:https://github.com/Kyogirante/gradle_user_guide,歡迎star。

轉(zhuǎn)載請事先溝通,未經(jīng)允許,謝絕轉(zhuǎn)載。

1. 新工具介紹(Introduction)

  • 能夠復用代碼和資源
  • 能夠構(gòu)建幾種不同版本參數(shù)的應用
  • 能夠配置、擴展、自定義構(gòu)建過程

1.1 為什么選擇Gradle(Why Gradle?)

Gradle是一款具有優(yōu)勢的構(gòu)建工具,通過插件可以自定義構(gòu)建過程。主要優(yōu)勢如下:

  • 基于Groovy的領域特定語言(DSL),用于描述和操作構(gòu)建過程
  • 支持maven/lvy的依賴管理
  • 非常靈活,并不強迫用戶一定要使用最佳的構(gòu)建方式
  • 插件可以暴露自身的語言和接口api給構(gòu)建文件使用
  • 支持IDE集成

2.2 需求(Requirements)

  • Gradle 2.2(Gradle版本是2.2及以上,因為文檔中有些新特性)
  • SDK with Build Tools 19.0.0.

2. 工程基本配置(Basic Project Setup)

Gradle工程默認的配置文件名稱是build.gradle,在主工程的根目錄下。

2.1 配置文件示例(Simple build files)

下面是一個Android工程的最簡單配置文件的內(nèi)容。

buildscript {
    repositories { 
        jcenter()
    }

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

apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "23.1.0"
}

配置內(nèi)容主要分為三部分。

  • buildscript{},這個部分主要配置在構(gòu)建過程中的依賴。上面示例中,聲明使用jcenter依賴庫,聲明了一個maven庫的依賴com.android.tools.build:gradle:1.3.1,是指引入gradle集成工具,版本是1.3.1。(關于Android Gradle Plugin版本和Gradle版本關系,點這里
  • apply plugin,引用插件,com.android.application這個插件用于構(gòu)建Android工程
  • android {},這部分是配置構(gòu)建Android工程的參數(shù)。compileSdkVersionbuildToolsVersion是必須的

注意:主工程中只能引用com.android.application插件,如果引用java插件會報錯,參考這里,第一個插件說明這是個Android工程,第二個插件說明這是個Java工程,所以只能引用一個。

注意:用戶可以在local.properties文件中使用sdk.dir屬性配置本地的Android sdk位置,或者設置一個名為Android_HOME的環(huán)境變量,這兩種方法沒有什么區(qū)別。

示例local.properties:

sdk.dir=/path/to/Android/Sdk

2.2 工程結(jié)構(gòu)(Project Structure)

Android工程文件有默認的目錄結(jié)構(gòu)。Gradle遵循約定由于配置規(guī)則,提供合理的默認值。工程以兩個目錄為主,一個是工程代碼目錄,一個是測試代碼目錄。

  • src/main/
  • src/androidTest/

在每個目錄中都有一些子目錄,Java工程和Android工程共有的子目錄如下:

  • java/
  • resources/

Android工程中有一些獨有的目錄:

  • AndroidManifest.xml
  • res
  • assets
  • aidl
  • rs
  • jni
  • jniLibs

所有的java文件都在src/main/java目錄下,主要的配置文件目錄是src/main/AndroidManifest.xml。

src/main/AndroidManifest.xml是自動創(chuàng)建的,不需要手動創(chuàng)建

2.2.1 配置目錄結(jié)構(gòu)(Configuring the Structure)

默認的目錄結(jié)構(gòu)并不能完全適配所有情況,用戶可以配置目錄結(jié)構(gòu)。點擊這里查看Java工程師怎么配置目錄結(jié)構(gòu)的。

在Android工程中使用同樣的格式,但是因為Android工程中有獨有的一些目錄,所以配置信息需要寫在android {}這部分。下面示例中,工程代碼使用原來的目錄,修改測試代碼的目錄。

android {
    sourceSets {
        main {
            manifest.srcFile 'AndroidManifest.xml'
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            aidl.srcDirs = ['src']
            renderscript.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']
        }

        androidTest.setRoot('tests')
    }
}

注意:因為舊的目錄中包含所有的文件(java、AIDL、res等等),所以需要重設所有的目錄

注意:setRoot()重設目錄位置,沿用之前的目錄結(jié)構(gòu),這個是Android工程特有的

2.3 構(gòu)建任務(Build Tasks)

2.3.1 通用任務(General Tasks)

使用插件(包含Java和Android插件)去構(gòu)建工程會自動創(chuàng)建很多任務,通用的任務如下:

  • assemble,打包工程所產(chǎn)出的文件
  • check,運營工程中所有的check任務
  • build, 執(zhí)行assemble任務和check任務
  • clean,清除工程的產(chǎn)出的文件

assemblecheck、build這三個任務實際上并不做任何事,他們只是一個殼任務,實告訴Gradle去執(zhí)行那些的任務。

不管什么工程,依賴了什么插件,都可以反復去調(diào)用同一個任務。例如引用一個findBugs插件,會創(chuàng)建一個新的任務,讓check任務依賴這個新任務,這樣每次調(diào)用check任務時候,新建的任務也會執(zhí)行。

  • 使用gradle tasks指令獲取工程中所有的可執(zhí)行任務
  • 使用gradle tasks --all執(zhí)行獲取工程中所有可執(zhí)行任務簡介以及依賴關系

如果工程中未做任何修改,執(zhí)行build任務,每個任務描述后面都會加上UP-TO-DATE,這意味著這個任務不需要真正地執(zhí)行,因為工程沒有改動。這樣每個任務都可以依賴其他任務,而且不需要其他任務做構(gòu)建工作。

2.3.2 Java工程任務(Java project tasks)

引用Java插件時候,說明這個工程是個純Java工程,會額外添加兩個殼任務jartests。

  • assemble
    • jar 打包工程產(chǎn)出文件
  • check
    • tests 執(zhí)行所有測試

jar任務會直接或者間接的依賴任務classes,這個任務會編譯java源代碼;tests任務會依賴任務testClasses,但是很少會直接調(diào)用這個任務,因為tests任務依賴它,直接調(diào)用tests任務即可。

大體上,用戶可能只會調(diào)用assemblecheck任務,很少調(diào)用其他任務??梢?a target="_blank" rel="nofollow">點擊這里查看Java工程所有的任務和任務描述。

2.3.3 Android工程任務(Android tasks)

引用com.android.application插件,說明這個工程是Android工程,在通用任務基礎上會額外添加兩個殼任務。

  • connectedCheck,查看是否有設備連接
  • deviceCheck, 查看是否連接上設備

注意,build任務是不依賴connectedCheckdeviceCheck任務的。

一個Android工程至少有兩個構(gòu)建包,debug apk和release apk。每一個構(gòu)建包都有自己的殼任務。

  • assemble
    • assembleDebug
    • assembleRelease

這兩個任務會依賴其他一些任務,要構(gòu)建出一個安裝包,需要執(zhí)行好多步驟。assemble任務依賴這兩個任務,所以執(zhí)行assemble任務時候,會產(chǎn)出debug和release兩個apk。

注意:Gradle支持指令簡寫模式,例如gradle aRgradle assembleRelease意義是相同的,只需要保證沒有其他任務能簡寫成aR。

Android工程中check類任務有各自的依賴。

  • check
    • lint
  • connectedCheck
    • connectedAndroidTest
  • deviceCheck
    • 它依賴于那些擴展了tests通用任務的任務

最后,Android工程中,也會有對程序安裝和卸載的任務。

  • installDebug
  • installRelease
  • uninstallAll
    • uninstallDebug
    • uninstallRelease
    • uninstallDebugAndroidTest

2.4 自定義基本構(gòu)建(Basic Build Customization)

Android的插件提供了領域特定語言(DSL)來幫助用戶直接地自定義構(gòu)建過程。

2.4.1 清單內(nèi)容(Manifest entries)

通過DSL用戶可以設置一些構(gòu)建參數(shù),可設置內(nèi)容如下:

  • minSdkVersion
  • targetSdkVersion
  • versionCode
  • versionName
  • applicationId (最終有效的包名,點擊這里查看細節(jié))
  • testApplicationId (用于測試app)
  • testInstrumentationRunner

示例如下:

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.1"


    defaultConfig { 
        versionCode 12
        versionName "2.0"
        minSdkVersion 16
        targetSdkVersion 23
    }
}

點擊這里查看可以配置的清單參數(shù)信息。

可以在.gradle文件中動態(tài)配置這些清單信息,例如,動態(tài)配置versionName參數(shù),示例如下:

def computeVersionName() {
    ...
}


android {
    compileSdkVersion 23
    buildToolsVersion "23.0.1"


    defaultConfig {
        versionCode 12 
        versionName computeVersionName()
        minSdkVersion 16
        targetSdkVersion 23
    }
}

注意:自定義時盡量不要使用gettter的方法名,防止沖突,例如,defaultConfig.getVersionName()會替換掉自定義的getVersionName()方法,也就是說每一個參數(shù)都有默認的getter方法

2.4.2 構(gòu)建類型(Build Types)

Android工程中默認的會有debug和release兩種構(gòu)建方式,主要區(qū)別在于調(diào)試程序的能力以及apk簽名細節(jié)。debug的版本為了防止在構(gòu)建過程中彈出提示,系統(tǒng)會根據(jù)明文的用戶名/密碼自動創(chuàng)建一個數(shù)字證書用于簽名,使用debug證書簽名的apk是無法上架銷售的。release版本在構(gòu)建過程中不進行簽名,將簽名放在之后的環(huán)節(jié)。

buildTypes中配置構(gòu)建類型信息,默認會創(chuàng)建兩種構(gòu)建方式,debug和release,在Android工程中允許自定義這兩種構(gòu)建方式的具體細節(jié)信息。示例如下:

android {
    buildTypes {
        debug {
            applicationIdSuffix ".debug"
        }


        jnidebug {
            initWith(buildTypes.debug)
            applicationIdSuffix ".jnidebug"
            jniDebuggable true
        }
    }
}

上面代碼作用:

  • 設置debug構(gòu)建類型的包名是<app appliationId>.debug,這樣一臺設備上面就可以同時安裝debug和release的包,不會出現(xiàn)包名沖突情況
  • 新建一個新的構(gòu)建類型,名為jnidebug,initWith(buildTypes.debug)表示buildTypes.debug構(gòu)建類型(Build Type)配置信息應用到這個構(gòu)建中
  • 重新設置包名同時設為jniDebuggable為true,開啟debug模式

buildTypes中新建一個新的構(gòu)建類型非常方便,可以使用initWith()復用其他構(gòu)建類型的構(gòu)建參數(shù)。點擊這個查看可配置的構(gòu)建參數(shù)。

除了修改構(gòu)建參數(shù)意外,buildTypes中還可以添加特定的代碼和資源。每一種構(gòu)建類型,默認都有一個特定資源目錄src/<build_type_name>/,例如src/debug/java目錄,只有構(gòu)建debug類型apk時候才會用到這個目錄下的資源。這就意味著構(gòu)建類型不能是mainandroidTest,這兩個目錄是工程的默認目錄,參考上面2.2提到的目錄結(jié)構(gòu)。

跟上文提到的sourceSet一樣,每一種構(gòu)建類型可以重設目錄,示例如下:

android {
    sourceSets.jnidebug.setRoot('foo/jnidebug')
}

另外,對于每一個構(gòu)建類型,都會有一個新的工程任務被創(chuàng)建,名為assemble<Build Type Name>,例如上文提到的assembleDebugassembleRelease, 這兩個任務也是來源于此。

根據(jù)這個規(guī)則,上面配置信息就會產(chǎn)生assembleJnidebug新任務,assemble任務像依賴assembleDebugassembleRelease任務一樣,也會依賴這個新任務。

可能新建構(gòu)建類型的場景:

  • 某些權(quán)限/模式在debug才開啟,release版本不開啟
  • 自定義debug調(diào)試實現(xiàn)
  • debug模式需要一些額外的資源

構(gòu)建中設置的代碼/資源主要用于以下幾點:

  • 合并到主清單
  • 代碼實現(xiàn)替換
  • 資源覆蓋

2.4.3 簽名信息配置(Signing Configurations)

應用簽名以下信息是必須的:

  • A keystore
  • A keystore password
  • A key alias name
  • A key password
  • The store type

點擊這里查看Android官方簽名細節(jié)及具體過程。

點擊這里查看可配置的簽名信息,這些信息是在signingConfigs{}模塊中配置的。

Android工程中,debug構(gòu)建會用通用的debug.keysotre和密碼、通用的key和密碼,keystore文件位于$HOME/.android/debug.keystore這個目錄。

具體示例如下:

android {
    signingConfigs {
        debug {
            storeFile file("debug.keystore")
        }


        myConfig {
            storeFile file("other.keystore")
            storePassword "android"
            keyAlias "androiddebugkey"
            keyPassword "android"
        }
    }


    buildTypes {
        foo {
            signingConfig signingConfigs.myConfig
        }
    }
}

上述示例中聲明了兩個簽名類型signingConfigs.debugsigningConfigs.myConfig,兩者都將設置了keystore位置,位于工程根目錄,同時
myCondig設置了其他必需信息,debug使用通用信息,不用配置。

注意:只有debug類型簽名的keystore位于默認位置,系統(tǒng)才會自動創(chuàng)建,如果重設了keystore的位置,就不會自動創(chuàng)建。新建簽名類型會自動使用默認的keystore,如果沒有,系統(tǒng)會自動創(chuàng)建,也就是說,系統(tǒng)是否自動創(chuàng)建keystore,是跟簽名類型的storeFile的位置有關系,跟簽名類型的名稱沒有關系。

注意:storeFile所以的目錄在工程的根目錄,是個相對目錄,當然也可以設置為絕對目錄,但是不推薦這樣做

注意:如果要根據(jù)具體情況來控制簽名參數(shù),就不能直接將key和密碼等信息直接寫在signingConfigs中,可以在gradle.properties文件中設置簽名具體細節(jié),然后在signingConfigs引用,具體點擊這里查看

3. 工程依賴/Android庫/多工程設置(Dependencies, Android Libraries and Multi-project setup)

Gradle工程可以依賴其他組件,這些組件可能是外部jar包也可能是一個Gradle工程。

3.1 依賴jar包(Dependencies on binary packages)

3.1.1 本庫jar包(Local packages)

依賴外部jar包,需要在.gradle文件中使用compile進行配置,示例如下:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
}


android {
    ...
}

注意:dependencies屬于標準Gradle API的DSL屬性,并不是屬于andorid{}的元素

compile屬性用于編譯整個工程,compile的庫都會被添加到編譯路徑中,最終也會打包到最終的apk中。依賴類型分為以下幾種:

  • compile,主工程
  • androidTestCompile,測試工程
  • debugCompile, debug構(gòu)建類型
  • releaseCompile, release構(gòu)建類型

因為構(gòu)建一個apk不可能沒有構(gòu)建類型,所以一般至少有兩個compile類型配置(compile<buildtype>Compile)甚至更多。每創(chuàng)建一個新的構(gòu)建類型,系統(tǒng)都會自動基于構(gòu)建類型的名稱創(chuàng)建一個新的compile類型,名為<buildtype>Compile。這在構(gòu)建打包過程非常有用,例如debug版本需要某個外部庫而release版本不需要,又比如不同的構(gòu)建打包對同一個外部庫依賴的版本不同。點擊這里查看解決jar包版本沖突的具體信息。

3.1.2 遠程依賴(Remote artifacts)

Gradle支持從Maven/Lvy倉庫拉取依賴。首先聲明倉庫,然后要在聲明具體的依賴。示例如下:

repositories {
     jcenter()
}


dependencies {
    compile 'com.google.guava:guava:18.0'
}


android {
    ...
}

注意:jcenter()是一個倉庫URL的縮寫。Gradle支持遠端和本地倉庫。

注意:Gradle支持依賴傳遞,也就是說A工程依賴B,B依賴C,那么A工程也會依賴C,A工程會從倉庫中獲取C。

點擊這里查看更多的依賴設置細節(jié);點擊這里查看具體依賴設置語言示例。

3.2 多工程設置(Multi project setup)

通過多工程依賴設置,Gradle工程可以依賴其他的Gradle工程。多工程設置是通過將其他被依賴的Gradle工程放入主工程的子目錄中,如下結(jié)構(gòu):

MyProject/
 + app/
 + libraries/
    + lib1/
    + lib2/

這里面有三個Gradle工程,通過以下的方式能夠引用到具體的工程:

  • :app
  • :libraries:lib1
  • :libraries:lib2

每一個工程擁有自己的build.gradle文件,配置該工程的構(gòu)建細節(jié),目錄結(jié)構(gòu)如下:

MyProject/
 | settings.gradle
 + app/
    | build.gradle
 + libraries/
    + lib1/
       | build.gradle
    + lib2/
       | build.gradle

另外,在上面結(jié)構(gòu)中可以看見在主工程的根目錄中有一個settings.gradle文件,這個文件是用來定義那些目錄是Gradle工程,settings.gradle示例內(nèi)容如下,里面定義了三個Gradle工程目錄:

include ':app', ':libraries:lib1', ':libraries:lib2'

主工程:app想要依賴其他的Gradle工程,只需要在它自身的build.gradle文件中添加依賴關系:

dependencies {
     compile project(':libraries:lib1')
}

android {
      ...
}

點擊這里查看更多多工程設置細節(jié)。

3.3 庫工程(Library projects)

上面所提到的多工程設置,:libraries:lib1, :libraries:lib2可以是Java工程,:app使用它們產(chǎn)生的jar包。但是,如果你想共享那些使用Android APIs或者使用Android-style的資源文件的代碼,就不能使用上述的普通的Java工程,必須是Android庫工程。

3.3.1 創(chuàng)建庫工程(Creating a Library Project)

庫工程和平常的Android工程很類似,有一些細小的區(qū)別。構(gòu)建庫工程(Library)和構(gòu)建一個應用工程(Application)是不同的,所以需要引用另一個插件'com.android.library',和com.android.application插件一樣,都是由com.android.tools.build.gradlejar包提供。下面是庫工程build.gradle文件的示例。

buildscript {
    repositories {
        jcenter()
    }


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


apply plugin: 'com.android.library'


android {
    compileSdkVersion 23
    buildToolsVersion "23.0.1"
}

這個庫工程使用sdk編譯版本是23,SourceSet、buildTypesdependencies都沿用他們所在的主工程,當然,也可以在庫工程自定義這些構(gòu)建信息。

3.3.2 庫工程和應用工程(主工程)的區(qū)別(Differences between a Project and a Library Project)

庫工程主要產(chǎn)出一個代表Android庫的aar包,里面包括編譯后的代碼(jar包和.so文件)和一些資源文件(manifest、res、assets);庫工程也可以構(gòu)建出一個測試apk用于測試庫工程,這個測試apk是獨立于主工程apk的,庫工程有assembleDebugassembleRelease殼任務,所以用指令構(gòu)建庫工程和構(gòu)建主工程是沒有區(qū)別的。其余地方,庫功臣和主工程是相同的。他們都有構(gòu)建類型buildTypes和定制版本product flavors(后續(xù)會講解),可以產(chǎn)出多個版本的aar包。注意buildTypes中大多數(shù)構(gòu)建參數(shù)不適用于庫工程,同時,可以通過更改sourceSet更改庫工程的內(nèi)容,這取決于庫工程是被主工程使用,還是用于測試。

3.3.3 引用庫工程(Referencing a Library)

引用庫工程示例如下:

dependencies {
    compile project(':libraries:lib1')
    compile project(':libraries:lib2')
}

android {
      ...
}

3.3.4 發(fā)布庫工程(Library Publication)

庫工程會默認發(fā)布release版本,這個版本可以被其他所有的工程引用,與這些工程的構(gòu)建版本無關??梢酝ㄟ^設置參數(shù)控制庫工程發(fā)布的版本,示例如下:

android {
    defaultPublishConfig "debug"
}

注意defaultPublishConfig內(nèi)容是構(gòu)建版本全名,releasedebug是系統(tǒng)默認的構(gòu)建版本名,我們也可以改成我們自定義的構(gòu)建版本全名,示例如下:

android {
    defaultPublishConfig "flavor1Debug"
}

當然,也可以發(fā)布所有版本的庫工程,示例如下:

android {
    publishNonDefault true
}

發(fā)布多個庫工程版本意味著會產(chǎn)生多個aar包,而不是一個aar包包含多個版本,每個aar包都是一個獨立的版本。

不同的構(gòu)建可以依賴同一個庫工程的不同版本,示例如下:

dependencies {
    flavor1Compile project(path: ':lib1', configuration: 'flavor1Release')
    flavor2Compile project(path: ':lib1', configuration: 'flavor2Release')
}

注意:發(fā)布版本的defaultPublishConfig變量的內(nèi)容必須是構(gòu)建版本的完整名

注意:一旦設置了publishNonDefault true,會將所有版本的aar包都上傳到統(tǒng)一maven倉庫,但是,這種做法是不合理的,一個maven倉庫目錄應該僅對應一個系列版本的aar包,例如debugrelease版本的aar包分別在不同的maven倉庫目錄中,或者保證不同版本的依賴僅僅發(fā)生在工程內(nèi)部,不上傳到maven倉庫。

4. 測試(Testing)

可以建立一個測試工程集成到主工程當中,不需要單獨新建一個測試工程。

4.1 單元測試(Unit testing)

Gradle 1.1版本之后就支持單元測試,點擊這里查看詳情。本章所提及的真機測試instrumentation tests,是指需要單獨構(gòu)建一個測試apk,運行在真機或者模擬器上的一種測試。

4.2 基本配置(Basics and Configuration)

上文中提到Android工程中默認有兩個目錄src/main/src/androidTest/。使用src/androidTest/這個目錄中的資源會構(gòu)建一個使用Android測試框架,并且布署到真機(或測試機)上的測試apk來測試應用程序。Android測試框架包含單元測試、真機測試、UI自動化測試。測試apk的清單配置中的<instrumentation>節(jié)點會自動生成,同時用戶也可以在src/androidTest/AndroidManifest.xml中添加額外模塊用于測試。

下面列出測試apk中可能用到的屬性,點擊這里查看詳情:

  • testApplicationId
  • testInstrumentationRunner
  • testHandleProfiling
  • testFunctionalTest

這些屬性是在andorid.defaultConfig中配置的,示例如下:

android {
    defaultConfig {
        testApplicationId "com.test.foo"
        testInstrumentationRunner "android.test.InstrumentationTestRunner"
        testHandleProfiling true 
        testFunctionalTest true
    }
}

在測試程序的清單配置(manifest)中,<instrumentation>節(jié)點中的targetPackage屬性會根據(jù)被測試的應用程式包名自動生成,這個屬性不受自定義的defaultConfig配置和buildType配置所影響。這也是manifest文件需要自動生成的一個原因。

另外,androidTest可以有自己的依賴配置,默認情況下,應用程序和它的依賴都會自動添加到測試應用的classpath中,也可以通過手動拓展測試的依賴,示例如下:

dependencies {
    androidTestCompile 'com.google.guava:guava:11.0.2'
}

使用assembleAndroidTest任務來構(gòu)建測試apk,這個任務不依賴于主工程的assemble任務,當設置要執(zhí)行測試時候,這個任務會自動執(zhí)行。

默認只有一個buildType會被測試,debug的構(gòu)建類型,但是可以自定義修改被測試的buildType,示例如下:

android {
    ...
    testBuildType "staging"
}

4.3 解決沖突(Resolving conflicts between main and test APK)

當啟動真機測試的時候,主apk和測試apk會共享同一個classpath,一旦兩個apk使用了同一個庫,但是使用的是不同版本,gralde構(gòu)建就會失敗。如果Gradle沒有捕獲這種情況,應用程式在測試和實際使用中可能表現(xiàn)不同(崩潰只是其中一種表現(xiàn))。

為了促使構(gòu)建成功,只需要讓所有的apk使用同一個版本的庫。如果這個沖突是發(fā)生在簡介依賴中(沒有直接在build.gradle中引入的庫),只需要在build.gradle中引入這個庫最新的版本即可,使用compile或者androidTestCompile。詳情查看這里Gradle's resolution strategy mechanism。可以通過./gradlew :app:dependencies and ./gradlew :app:androidDependencies查看工程的依賴樹。

4.4 執(zhí)行測試(Running tests)

上文中提到check類的任務(需要連接設備)是通過connectedCheck殼任務被喚起的,這個過程依賴connectedDebugAndroidTest任務,因此執(zhí)行測試,需要執(zhí)行connectedDebugAndroidTest任務,它會有以下操作:

  • 確認主應用和測試應用都被構(gòu)建(依賴assembleDebugassembleDebugAndroidTest任務)
  • 安裝主應用和測試應用
  • 運行測試
  • 卸載主應用和測試應用

如果有多個設備連接,所有的測試會并行在所有設備上運行,其中任何一個設備測試失敗,測試就失敗。

4.5 測試Andorid庫(Testing Android Libraries)

測試Andriod庫和測試Android程序是相同的。不同的是Android庫作為依賴直接繼承到測試應用中,這樣測試apk不僅包含測試的代碼還包含這個庫以及這個庫的依賴。Android庫的清單配置會合并到測試程序的清單配置中。androidTest任務改為只安裝測試應用(沒有其他應用),其他都是相同的。

4.6 測試報告(Test reports)

當執(zhí)行單元測試后,Gradle會生成一份HTML報告方便查看測試結(jié)果。Andorid插件是在此基礎上擴展了HTML報告,聚合了所有連接設備的測試結(jié)果。所有的測試結(jié)果以XML形式儲存在build/reports/androidTests/目錄下,這個目錄也是可配的,示例如下:

android {
    ...

    testOptions {
        resultsDir = "${project.buildDir}/foo/results"
    }
}

4.6.1 多工程測試報告(Multi-projects reports)

在配置了多工程或者多依賴的工程中,當同時運行所有測試時候,針對所有的測試只生成一份測試報告是非常有用的。

為了到達這個目的,需要使用另一個插件,這個插件是Android插件中自帶的,示例如下:

buildscript {
    repositories {
        jcenter()
    }


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


apply plugin: 'android-reporting'

這必須添加到工程的根目錄下,例如和settings.gradle同目錄的build.gralde中,然后在根目錄中使用使用一下指令運行所有測試,同時合并所有測試報告:

gradle deviceCheck mergeAndroidReports --continue

注意:--continue是為了保證所有測試都執(zhí)行,即使其中子項目中任何一個測試失敗。如果沒有這個選項,當有測試失敗時候,整個測試過程就會中斷。

4.7 Lint支持(Lint support)

注:lint是一種檢查Android項目的工具

可以針對某一個構(gòu)建版本執(zhí)行l(wèi)int,例如, ./gradlew lintRelease,或者針對所有版本./gradlew lint,lint會生成一個記錄被檢查版本問題的報告??梢酝ㄟ^配置lintOption來設置lint細節(jié),點擊這里查看詳情,示例如下:

android {
    lintOptions {
        // turn off checking the given issue id's
        disable 'TypographyFractions','TypographyQuotes'

        // turn on the given issue id's
        enable 'RtlHardcoded','RtlCompat', 'RtlEnabled'

        // check *only* the given issue id's
        check 'NewApi', 'InlinedApi'
    }
}

5. 構(gòu)建版本(Build Variants)

使用新構(gòu)建工具的目的之一是面對同一個工程,能夠編譯出不同的版本。

有兩個場景會用到:

  • 同一個應用有多個版本,例如,免費版本和付費版本
  • 同一個應用針對不同的設備有多個版本,比如說手機版和pad版,點擊這里查看詳情
  • 1和2綜合

針對同一個工程,可以編譯出不同版本的apks,而不是為了編譯出不同版本的apks,使用多個工程。

5.1 Product flavors

product flavor可自定義應用的版本,同一個工程中可以有多個不同的product flavor。

product flavor用來告訴構(gòu)建系統(tǒng)不同版本之間的細小區(qū)別,product flavor的聲明示例如下:

android {
    ....

    productFlavors {
        flavor1 {
            ...
        }

        flavor2 {
            ...
        }
    }
}

里面創(chuàng)建了兩個product flavor,分別是flavor1flavor2

注意:product flavor的名稱不能和構(gòu)建類型(buildType)的名稱相同,也不能是androidTesttest

5.2 Build Type + Product Flavor = Build Variant

構(gòu)建類型 + 定制版本 = 構(gòu)建版本(應用版本)

就像上文提到的,每一個構(gòu)建類型(buildType)都可以構(gòu)建一個apk,同樣的,每一個定制版本(productFlavor)都可以構(gòu)建一個apk,這樣的話,構(gòu)建類型(buildType)和定制版本(productFlavor)結(jié)合就會形成一個新的apk,也就是構(gòu)建版本。默認有兩種構(gòu)建類型debugrelease,再加上上文定義的flavor1flavor2,就會形成四種組合,代表四種不同的構(gòu)建版本:

  • Flavor1 - debug
  • Flavor1 - release
  • Flavor2 - debug
  • Flavor2 - release

一個有沒有定義productFlavors的工程也有構(gòu)建版本,使用缺省的productFlavors,也就是沒有product flavor名稱,那么工程所有的構(gòu)建版本和構(gòu)建類型是一樣的。

5.3 配置定制版本(Product Flavor Configuration)

productFlavors配置示例如下:

android {
    ...

    defaultConfig {
        minSdkVersion 8
        versionCode 10
    }

    productFlavors {
        flavor1 {
            applicationId "com.example.flavor1"
            versionCode 20
         }

         flavor2 {
             applicationId "com.example.flavor2"
             minSdkVersion 14
         }
    }
}

注意,android.productFlavors.*android.defaultConfig中可配置的參數(shù)是相同的,也就是說他們共享這些參數(shù)。

defaultConfig為所有productFlavor提供一些基本配置參數(shù),每一個productFlavors自定義一些額外的配置參數(shù),也可以覆蓋defaultConfig中配置的參數(shù)。在上面的示例中,productFlavors配置信息如下:

  • flavor1
    • applicationId: com.example.flavor1
    • minSdkVersion: 8
    • versionCode: 20
  • flavor2
    • applicationId: com.example.flavor2
    • minSdkVersion: 14
    • versionCode: 10

通常,構(gòu)建類型(buildType)也會修改一些配置信息,比如說buildType中的applicationIdSuffix變量拼接在productFlavorapplicationId之后的。但是有些配置參數(shù)是以productFlavor為主的,比如signingConfig,所有的release版本的構(gòu)建都會使用android.buildTypes.release.signingConfig中配置的簽名信息,如果設置了productFlavor,所有的release版本的構(gòu)建都會使用android.productFlavors.*.signingConfig中配置的簽名信息。

5.4 資源目錄和依賴(Sourcesets and Dependencies)

和構(gòu)建類型相似,productFlavor也有自己的資源目錄,示例如下:

  • android.sourceSets.flavor1,資源目錄是src/flavor1/
  • android.sourceSets.flavor2,資源目錄是src/flavor2/
  • android.sourceSets.androidTestFlavor1,資源目錄是src/androidTestFlavor1/
  • android.sourceSets.androidTestFlavor2,資源目錄是src/androidTestFlavor2/

這些資源目錄中的資源會用于構(gòu)建apk,構(gòu)建apk資源的來源是android.sourceSets.main主工程的資源和構(gòu)建類型的資源目錄(或者productFlavor的資源目錄)。下面是多個資源目錄構(gòu)建規(guī)則:

  • 所有的資源代碼(src/*/java)最終都會合并到最后輸出包中
  • 所有的Manifests.xml也會合并,根據(jù)buildTypesproductFlavor,每個不同的構(gòu)建包apk,會有不同的組件和權(quán)限
  • 所有的資源(包括res和assets)都會做合并,資源會做合并,資源優(yōu)先級 buildType > productFlavor > 主工程
  • 每一個構(gòu)建版本都有唯一的一個R.class,不和其他構(gòu)建版本共享

最后,和構(gòu)建類型(buildType)一樣,每一個productFlavor都有自己的依賴。例如flavor1需要依賴廣告組件和支付組件,而flavor2僅僅依賴廣告組件,配置文件如下:

dependencies {
    flavor1Compile "ads.sdk"
    flavor1Compile "pay.sdk"
    
    flavor2Compile "ads.sdk"
}

另外每一個構(gòu)建版本都有一個資源目錄,示例如下:

  • android.sourceSets.flavor1Debug,資源目錄src/flavor1Debug/
  • android.sourceSets.flavor1Release,資源目錄src/flavor1Release/
  • android.sourceSets.flavor2Debug,資源目錄src/flavor2Debug/
  • android.sourceSets.flavor2Release,資源目錄src/flavor2Release/

構(gòu)建版本目錄資源的優(yōu)先級高于構(gòu)建類型資源目錄。

現(xiàn)在基本知道,buildTypes、productFlavor、buildVariants都有自己的資源目錄,資源優(yōu)先級是:

buildVariants > buildType > productFlavor > 主工程。

5.5 構(gòu)建任務(Building and Tasks)

上文中提到,每新建一種構(gòu)建類型buildType,都會自動創(chuàng)建一個名為assemble<Build Type Name>的新任務。

而每新建一種productFlavor,會自動創(chuàng)建多個新任務:

  1. assemble<Variant Name>,直接構(gòu)建一個最終版本的apk,例如assembleFlavor1Debug任務
  2. assemble<Build Type Name>,構(gòu)建所有buildType類型的apks,例如,assembleDebug任務會構(gòu)建出Flavor1DebugFlavor2Debug版本的apk
  3. assemble<Product Flavor Name>,構(gòu)建所有productFlavor的apks,例如,assembleFlavor1任務會構(gòu)建出Flavor1DebugFlavor1Release版本的apk.

assemble會構(gòu)建所有版本的apk。

5.6 多flavor構(gòu)建(Multi-flavor variants)

注:原文中dimension of Product Flavors,統(tǒng)一翻譯為productFlavor類型,在某些文檔中也翻譯成維度

在某些場景下,一個應用可能需要基于多個標準創(chuàng)建多個版本。例如,Google Play的multi-apk支持四個不同的過濾器,這些用于創(chuàng)建不同apk的過濾器需要使用多個類型的ProductFlavor。

例如,一個游戲有免費版本和付費版本,同時在multi-apk中需要支持ABI過濾器(ABI,二進制接口,可以讓編譯好的目標代碼在所有支持該ABI的系統(tǒng)上運行,而無需對程序進行修改)。一個擁有兩個版本和三個ABI過濾器的工程,需要創(chuàng)建六個apks(不考慮構(gòu)建類型buildType),但是它們使用的源代碼都是相同的,所以沒有必要創(chuàng)建六個productFlavor。相反,只需要創(chuàng)建兩個類型的flavor,就可以構(gòu)建出所有的可能的版本組合。

使用flavorDimensions數(shù)組來實現(xiàn)多個類型的flavor,每一個productFlavor被分到不同的類型,示例如下:

android {
    ...


    flavorDimensions "abi", "version"


    productFlavors {
        freeapp {
            dimension "version"
            ...
        }

        paidapp {
            dimension "version"
            ...
        }


        arm {
            dimension "abi"
            ...
        }

        mips {
            dimension "abi"
            ...
        }

        x86 {
            dimension "abi"
            ...
        }
    }
}

android.flavorDimensions數(shù)組按順序定義了可能使用到的flavor類型,每一個productFlavor聲明自身的flavor類型。

上面例子中,將productFlavor分為兩個類型,abi類型[arm, mips, x86]和version類型[freeapp, paidapp],加上默認的[debug, release]構(gòu)建類型,將會組合出以下這些構(gòu)建版本(Build Variant):

  • x86-freeapp-debug
  • x86-freeapp-release
  • arm-freeapp-debug
  • arm-freeapp-release
  • mips-freeapp-debug
  • mips-freeapp-release
  • x86-paidapp-debug
  • x86-paidapp-release
  • arm-paidapp-debug
  • arm-paidapp-release
  • mips-paidapp-debug
  • mips-paidapp-release

android.flavorDimensions數(shù)組定義的flavor類型順序非常重要。

上述每一個構(gòu)建版本名稱都由以下幾個屬性構(gòu)成:

  • android.defaultConfig
  • abi類型
  • version類型

flavor工程也有自身的資源目錄,和構(gòu)建版本目錄相似但是目錄名稱不包含構(gòu)建類型,例如:

  • android.sourceSets.x86Freeapp,資源目錄是src/x86Freeapp/
  • android.sourceSets.armPaidapp,資源目錄是src/armPaidapp/

flavor的資源目錄優(yōu)先級高于productFlavor資源目錄,但是低于構(gòu)建類型資源目錄優(yōu)先級。

那么就可以列出整個工程資源優(yōu)先級,資源優(yōu)先級是:

buildVariants > buildType > 多flavor > productFlavor > 主工程

5.7 測試(Testing)

測試多flavor項目和測試一般項目類似。

androidTest的目錄適用于所有flavor的測試,每一個flavor也有單獨的測試資源目錄,例如:

  • android.sourceSets.androidTestFlavor1,資源目錄src/androidTestFlavor1/
  • android.sourceSets.androidTestFlavor2,資源目錄src/androidTestFlavor2/

類似的,每個flavor也有自己的依賴配置,示例如下:

dependencies {
    androidTestFlavor1Compile "..."
}

通過deviceCheck任務或者主工程的androidTest任務會執(zhí)行androidTestFlavor1Compile任務。

每個flavor也有自己任務用于執(zhí)行測試,androidTest<VariantName>,例如:

  • androidTestFlavor1Debug
  • androidTestFlavor2Debug

類似的,測試apk的構(gòu)建、安裝、卸載任務:

  • assembleFlavor1Test
  • installFlavor1Debug
  • installFlavor1Test
  • uninstallFlavor1Debug
  • ...

最終,會根據(jù)flavor生成HTML測試報告,也會生成集成測試報告。測試報告的目錄示例如下:

  • build/androidTest-results/flavors/<FlavorName>,單個flavor測試報告的目錄
  • build/androidTest-results/all/,合并flavor的測試報告
  • build/reports/androidTests/flavors<FlavorName>,單個flavor測試報告的
  • build/reports/androidTests/all/,合并flavor的測試報告

即使自定義目錄,也只會改變根目錄,里面的具體子目錄不會改變。

5.8 構(gòu)建配置(BuildConfig)

在編譯時,Android Studio會生成一個類BuildConfig,這個類包含構(gòu)建特定版本時用到的一些常量,用戶可以根據(jù)這些常量執(zhí)行不同的操作行為。例如:

private void javaCode() {
    if (BuildConfig.FLAVOR.equals("paidapp")) {
        doIt();
    else {
        showOnlyInPaidAppDialog();
    }
}

下面是BuildConfig類包含的一些常量:

  • boolean DEBUG – if the build is debuggable.
  • int VERSION_CODE
  • String VERSION_NAME
  • String APPLICATION_ID
  • String BUILD_TYPE – 構(gòu)建類型,例如: "release"
  • String FLAVOR – productFlavor名稱,例如: "paidapp"

如果工程中使用了flavorDimensions多類型flavor,會自動生成額外的變量。以上述的配置文件為例:

  • String FLAVOR = "armFreeapp"
  • String FLAVOR_abi = "arm"
  • String FLAVOR_version = "freeapp"

5.9 過濾構(gòu)建版本(Filtering Variants)

當添加productFlavor或者使用flavorDimensions設置多類型flavor,可能有些構(gòu)建版本并不需要。例如,用戶定義了兩個productFlavor,一個是正常版本,另一個仿造數(shù)據(jù)用于測試。第二個productFlavor僅僅是在開發(fā)過程中有用,在構(gòu)建發(fā)布包時不需要這個productFlavor,可以通過使用variantFilter過濾器移除不需要的構(gòu)建版本。示例如下:

android {
    productFlavors {
        realData
        fakeData
    }

    variantFilter { variant ->
        def names = variant.flavors*.name

        if (names.contains("fakeData") && variant.buildType.name == "release") {
            variant.ignore = true
        }
    }
}

使用以上配置后,工程就只有以下的構(gòu)建版本:

  • realDataDebug
  • realDataRelease
  • fakeDataDebug

點擊這里查看可以過濾的構(gòu)建屬性。

6. 構(gòu)建定制進階(Advanced Build Customization)

6.1 混淆(Running ProGuard)

ProGuard插件是Android插件中自帶的,如果構(gòu)建任務(Build Type)中通過設置minifyEnabled為true(意為使用混淆),混淆任務會自動創(chuàng)建。示例如下:

android {
    buildTypes {
        release {
            minifyEnabled true
            proguardFile getDefaultProguardFile('proguard-android.txt')
        }
    }

    productFlavors {
        flavor1 {
        }
        flavor2 {
            proguardFile 'some-other-rules.txt'
        }
    }
}

構(gòu)建版本同時使用構(gòu)建類型(Build Type)和productFlavor中設置的混淆規(guī)則文件。

默認有兩個混淆規(guī)則文件:

  • proguard-android.txt
  • proguard-android-optimize.txt

它們位于Android SDK中,使用getDefaultProguardFile(fileName)獲取它們的絕對路徑名,差別是第二個文件會啟用優(yōu)化。

6.2 忽略資源(Shrinking Resources)

這個配置設置為true,在構(gòu)建打包時候,會自動忽略沒有被使用到的文件。點擊這里查看詳細信息。示例如下:

android {
 buildTypes {
        release {
            shrinkResources true
              ...
        }
    }
}

6.3 操作任務(Manipulating tasks)

Java工程使用固定的任務一起協(xié)作最終打包工程。其中classes任務是用來編譯Java源代碼的,可以在build.gradle使用classes,它是project.tasks.classes的縮寫。

在Android工程中,如果要構(gòu)建打包,可能會復雜一些,因為Android工程中有大量名字相同的任務,而且它們的名字是基于buildTypeproductFlavor生成的。

為了解決這個問題,Android對象有兩個屬性:

  • applicationVariants,只適用于com.android.application插件
  • libraryVariants,只適用于com.android.library插件
  • testVariants,兩種插件都適用

這三個屬性分別返回一個ApplicationVariant、LibraryVariant和TestVariant對象的DomainObjectCollection

注意,適用這三個collection中的任意一個,都會生成所有相對應的任務,也就是說使用collection后,就不需要再更改配置。

DomainObjectCollection可以直接訪問所有對象,或者通過過濾器進行篩選。

android.applicationVariants.all { variant ->
   ....
}

這三個Variant類共享下面這些屬性:

屬性名 屬性類型 描述
name String BuildVariant名稱,必須保證唯一
description String BuildVariant的描述說明
dirName String BuildVariant的子文件名,必須是唯一的可能會有多個,例如:debug/flavor1
baseName String BuildVariant構(gòu)建包的基礎名字,必須唯一
outputFile File BuildVariant的輸出文件,是個可讀寫的屬性
processManifest ProcessManifest 處理清單Manifest的任務
aidlCompile AidlCompile 編譯AIDL文件的任務
renderscriptCompile RenderscriptCompile 處理Renderscript文件的任務
mergeResources MergeResources 合并資源的任務
mergeAssets MergeAssets 合并assets資源的任務
processResources ProcessAndroidResources 處理和編譯資源文件的任務
generateBuildConfig GenerateBuildConfig 生成BuildConfig的任務
javaCompile JavaCompile 編譯Java源代碼的任務
processJavaResources Copy 處理Java資源的任務
assemble DefaultTask BuildVariant構(gòu)建殼任務

ApplicationVariant類還有以下附加屬性:

屬性名 屬性類型 描述
buildType BuildType BuildVariant的構(gòu)建類型
productFlavors List<ProductFlavor> BuildVariant的productFlavor,不會為null但可以為空
mergedFlavor ProductFlavor 合并android.defaultConfigvariant.productFlavors的任務
signingConfig SigningConfig BuildVariant使用的簽名
isSigningReady boolean true表示BuildVariant配置了簽名所需要的信息
testVariant BuildVariant 用于測試這個BuildVariant的BuildVariant
dex Dex 將代碼打包成dex的任務,如果是庫工程,那么這個任務不能為null
packageApplication PackageApplication 打包最終apk的任務,如果是個庫工程,這個任務可以為null
zipAlign ZipAlign zip壓縮apk的任務,如果是個庫工程或者apk不被簽名,這個任務可以為null
install DefaultTask 安裝apk任務,不能為null
uninstall DefaultTask 卸載apk任務

LibraryVariant類有以下附加屬性:

屬性名 屬性類型 描述
buildType BuildType BuildVariant的構(gòu)建類型
mergedFlavor ProductFlavor The defaultConfig values
testVariant BuildVariant 用于測試這個BuildVariant的BuildVariant
packageLibrary Zip 用于打包aar的任務,如果是庫工程,不能為null

TestVariant類有以下附加屬性:

屬性名 屬性類型 描述
buildType BuildType BuildVariant的構(gòu)建類型
productFlavors List<ProductFlavor> BuildVariant的productFlavor,不會為null但可以為空
mergedFlavor ProductFlavor 合并android.defaultConfigvariant.productFlavors的任務
signingConfig SigningConfig BuildVariant使用的簽名
isSigningReady boolean true表示BuildVariant配置了簽名所需要的信息
testedVariant BaseVariant 被TestVariant測試的BaseVariant
dex Dex 將代碼打包成dex的任務,如果是庫工程,那么這個任務不能為null
packageApplication PackageApplication 打包最終apk的任務,如果是個庫工程,這個任務可以為null
zipAlign ZipAlign zip壓縮apk的任務,如果是個庫工程或者apk不被簽名,這個任務可以為null
install DefaultTask 安裝apk任務,不能為null
uninstall DefaultTask 卸載apk任務
connectedAndroidTest DefaultTask 在連接設備上執(zhí)行Android測試的任務
providerAndroidTest DefaultTask 使用拓展API執(zhí)行Android測試的任務

Android特有任務類型的API:

  • ProcessManifest
    • File manifestOutputFile
  • AidlCompile
    • File sourceOutputDir
  • RenderscriptCompile
    • File sourceOutputDir
    • File resOutputDir
  • MergeResources
    • File outputDir
  • MergeAssets
    • File outputDir
  • ProcessAndroidResources
    • File manifestFile
    • File resDir
    • File assetsDir
    • File sourceOutputDir
    • File textSymbolOutputDir
    • File packageOutputFile
    • File proguardOutputFile
  • GenerateBuildConfig
    • File sourceOutputDir
  • Dex
    • File outputFolder
  • PackageApplication
    • File resourceFile
    • File dexFile
    • File javaResourceDir
    • File jniDir
    • File outputFile
      • 在Variant對象中修改outputFile屬性可以改變最終輸出的文件夾.
  • ZipAlign
    • File inputFile
    • File outputFile
      • 在Variant對象中修改outputFile屬性可以改變最終輸出的文件夾.

每一個任務類型的API由于Gradle的工作方式以及Android插件配置方式而受到限制。首先,Gradle任務只能被配置輸入和輸出的目錄以及一些可能使用到的常量,其次大多數(shù)任務的輸出都不是固定單一的,一般都混合了sourceSet、Build Type和Product Flavor中的值。這都是為了保證構(gòu)建文件的簡單和可讀性,讓開發(fā)者通過DSL語言去修改構(gòu)建過程,而不是深入修改任務并改變構(gòu)建過程。

同時,除了ZipAlign任務,其他類型的任務都需要設置私有數(shù)據(jù)讓它們運行,這就意味著無法手動創(chuàng)建這些類型的新任務。

這些API也有可能被更改,目前大部分API都是圍繞著給定任務的輸入輸出來添加外的處理。

6.4 配置JDK版本(Setting language level)

默認會根據(jù)compileSdkVersion來選擇JDK版本,可以通過compileOptions設置編譯時使用的JDK版本。示例如下:

android {
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_6
        targetCompatibility JavaVersion.VERSION_1_6
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,954評論 25 709
  • 1.介紹 如果你正在查閱build.gradle文件的所有可選項,請點擊這里進行查閱:DSL參考 1.1新構(gòu)建系統(tǒng)...
    Chuckiefan閱讀 12,357評論 8 72
  • 本文原作者為:kale2010 .blog地址:http://www.cnblogs.com/tianzhijie...
    NoValue閱讀 3,719評論 0 11
  • 來到簡書的第一天 還沒倒飭清楚軟件功能 還沒有一個好友 還沒來的及欣賞優(yōu)質(zhì)創(chuàng)作 但...... 特意挑選了一張啤酒...
    小米崽閱讀 238評論 0 1
  • 我覺得真正愛你的那個我已經(jīng)死了 死在日復一日疲累的堅持中 死在與你無休止地拉鋸戰(zhàn)中 那時候的我還帶著患得患失的珍惜...
    SayEstrus閱讀 1,229評論 0 0

友情鏈接更多精彩內(nèi)容