Gradle for Android

理解基本的Gradle

若你想创建一个Android Studio基于gradle,必须写一个构建脚本,该文件即build.gradle,gradle已经为我们提供了默认的配置以及通常的默认值。若你需要使用自己的配置,可以简单的重写它们即可。Gradle是一种基于Groovy的动态DSL,而Groovy语言是一种基于jvm的动态语言。

Project和tasks

在grade中的两大重要的概念,分别是project和tasks。每一次构建都是有至少一个project来完成,所以Android studio中的project和Gradle中的project不是一个概念。每个project有至少一个tasks。每一个build.grade文件代表着一个project。tasks在build.gradle中定义。当初始化构建进程,gradle会基于build文件,集合所有的project和tasks,一个tasks包含了一系列动作,然后它们将会按照顺序执行,一个动作就是一段被执行的代码。

构建的生命周期

一旦一个tasks被执行,那么它不会再次执行了,不包含依赖的Tasks总是优先执行,一次构建将会经历下列三个阶段:

1.初始化阶段:project实例在这儿创建,如果有多个模块,即有多个build.gradle文件,多个project将会被创建。

2.配置阶段:在该阶段,build.gradle脚本将会执行,为每个project创建和配置所有的tasks。

3.执行阶段:这一阶段,gradle会决定哪一个tasks会被执行,哪一个tasks会被执行完全依赖开始构建时传入的参数和当前所在的文件夹位置有关。

build.gradle的配置文件

基于grade构建的项目通常至少有一个build.gradle。

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

实际构建开始的地方(如下所示),在仓库地址中,我们使用了JCenter,JCenter类似maven库,不需要任何额外的配置,grade还支持其他几个仓库,不论是远程还是本地仓库。

构建脚本也定义了一个Android构建工具。Android plugin提供了所有需要去构建和测试的应用。每个Android应用都需要这么一个插件:

apply plugin: 'com.android.application'

插件用于扩展gradle脚本的能力,在一个项目中使用插件,这样该项目的构建脚本就可以定义该插件定义好的属性和使用它的tasks。

注意:当你在开发一个依赖库,那么你应该使用'com.android.library',并且你不能同时使用他们2个,这将导致构建失败,一个模块要么使用Android application或者Android library插件,而不是二者。

当使用Android 插件的时候,Android标签将可以被使用,如下所示:

android {
       compileSdkVersion 22
       buildToolsVersion "22.0.1"
}

项目结构

Android studio构建的结构:

 MyApp
   ├── build.gradle
   ├── settings.gradle
   └── app
       ├── build.gradle
       ├── build
       ├── libs
       └── src
           └── main
               ├── java
               │   └── com.package.myapp
               └── res
                   ├── drawable
                   ├── layout
                   └── etc.

grade项目通常在根文件夹中包含一个build.gradle,使用的代码在app这个文件夹中,这个文件夹也可以使用其他名字,而不必要定义为app,例如当你利用Android studio创建一个project针对一个手机应用和一个Android wear应用的时候,模块将被默认叫做application和wearable。

gradle使用了一个叫做source set的概念,官方解释:一个source set就是一系列资源文件,其将会被编译和执行。对于Android项目,main就是一个source set,其包含了所有的资源代码。当你开始编写测试用例的时候,你一般会把代码放在一个单独的source set,叫做androidTest,这个文件夹只包含测试。

开始使用Gradle Wrapper

grade只是一个构建工具,而新版本总是在更迭,所以使用Gradle Wrapper将会是一个好的选择去避免由于gradle版本更新导致的问题。Gradle Wrapper提供了一个windows的batch文件和其他系统的shell文件,当你使用这些脚本的时候,当前gradle版本将会被下载,并且会被自动用在项目的构建,所以每个开发者在构建自己app的时候只需要使用Wrapper。所以开发者不需要为你的电脑安装任何gradle版本,在mac上你只需要运行gradlew,而在windows上你只需要运行gradlew.bat。疑问:Gradle Wrapper如何指定下载的gradle版本?

你也可以利用命令行./gradlew -v来查看当前gradle版本。下列是wrapper的文件夹:

myapp/
   ├── gradlew
   ├── gradlew.bat
   └── gradle/wrapper/
       ├── gradle-wrapper.jar
       └── gradle-wrapper.properties

可以看到一个bat文件针对windows系统,一个shell脚本针对mac系统,一个jar文件,一个配置文件。配置文件包含以下信息:

#Sat May 30 17:41:49 CEST 2015
   distributionBase=GRADLE_USER_HOME
   distributionPath=wrapper/dists
   zipStoreBase=GRADLE_USER_HOME
   zipStorePath=wrapper/dists
   distributionUrl=https\://services.gradle.org/distributions/
   gradle-2.4-all.zip

你可以改变该url来改变你的gradle版本。

 

使用基本的构建命令

使用你的命令行,导航到你的项目,然后输入:

$ gradlew tasks

这一命令将会列出所以可运行的tasks,你也可以添加--all参数,来查看所有的task。
当你在开发的时候,构建项目,你需要运行assemble task通过debug配置:

$ gradlew assembleDebug

该任务将会创建一个debug版本的app,同时Android插件会将其保存在MyApp/app/build/ outputs/apk目录下。

除了assemble,还有三个基本的命令:

1.check 运行所以的checks,这意味着运行所有的tests在已连的设备或模拟器上。

2.build 是check和assemble的集合体。

3.clean 清楚项目的output文件。

保持旧的eclipse文件结构

关于如何将eclipse项目导入Android studio本文不再介绍。

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')
    } 
}

在grade文件中配置,将会保存eclipse目录结构,当然,如果你有任何依赖的jar包,你需要告诉gradle它在哪儿,假设jar包会在一个叫做libs的文件夹内,那么你应该这么配置:

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

该行意为:将libs文件夹中所有的jar文件视为依赖包。

理解Gradle脚本

当我们创建一个新的工程,Android studio会默认为我们创建三个gradle文件,两个build.gradle,一个settings.gradle,build.gradle分别放在了根目录和moudle目录下,下面是gradle文件的构成图:

 MyApp
   ├── build.gradle
   ├── settings.gradle
   └── app
       └── build.gradle

 

setting.gradle解析

当你的app只有一个模块的时候,你的setting.gradle将会是这样子的:

include ':app'

setting.gradle文件将会在初始化时期执行,关于初始化时期,可以查看上一篇博客,并且定义了哪一个模块将会被构建。举个例子,上述setting.gradle包含了app模块,setting.gradle是针对多模块操作的,所以单独的模块工程完全可以删除掉该文件。在这之后,Gradle会为我们创建一个Setting对象,并为其包含必要的方法.

根目录的build.gradle

该gradle文件是定义在这个工程下的所有模块的公共属性,它默认包含二个方法:

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

buildscript方法是定义了全局的相关属性,repositories定义了jcenter作为仓库。一个仓库代表着你的依赖包的来源,例如maven仓库。dependencies用来定义构建过程。这意味着你不应该在该方法体内定义子模块的依赖包,你仅仅需要定义默认的Android插件就可以了,因为该插件可以让你执行相关Android的tasks。

allprojects方法可以用来定义各个模块的默认属性,你可以不仅仅局限于默认的配置,未来你可以自己创造tasks在allprojects方法体内,这些tasks将会在所有模块中可见。

模块内的build.gradle

模块内的gradle文件只对该模块起作用,而且其可以重写任何的参数来自于根目录下的gradle文件。该模块文件应该是这样:

 apply plugin: 'com.android.application'
   android {
       compileSdkVersion 22
       buildToolsVersion "22.0.1"
       defaultConfig {
           applicationId "com.gradleforandroid.gettingstarted"
           minSdkVersion 14
           targetSdkVersion 22
           versionCode 1
           versionName "1.0"
       }
       buildTypes {
           release {
               minifyEnabled false
               proguardFiles getDefaultProguardFile
                ('proguard-android.txt'), 'proguard-rules.pro'
           }
        } 
    }
    dependencies {
       compile fileTree(dir: 'libs', include: ['*.jar'])
       compile 'com.android.support:appcompat-v7:22.2.0'
     }

插件

该文件的第一行是Android应用插件,该插件我们在上一篇博客已经介绍过,其是google的Android开发团队编写的插件,能够提供所有关于Android应用和依赖库的构建,打包和测试。

Android

该方法包含了所有的Android属性,而唯一必须得属性为compileSdkVersion和buildToolsVersion:

1.compileSdkVersion:编译该app时候,你想使用到的api版本。

2.buildToolsVersion:构建工具的版本号。

构建工具包含了很多实用的命令行命令,例如aapt,zipalign,dx等,这些命令能够被用来产生多种多样的应用程序。你可以通过sdk manager来下载这些构建工具。

defaultConfig方法包含了该app的核心属性,该属性会重写在AndroidManifest.xml中的对应属性。

defaultConfig {
       applicationId "com.gradleforandroid.gettingstarted"
       minSdkVersion 14
       targetSdkVersion 22
       versionCode 1
       versionName "1.0"
}

第一个属性是applicationId,该属性复写了AndroidManifest文件中的包名package name,但是关于applicationId和package name有一些不同。在gradle被用来作为Android构建工具之前,package name在AndroidManifest.xml有两个作用:其作为一个app的唯一标示,并且其被用在了R资源文件的包名。

Gradle能够很轻松的构建不同版本的app,使用构建变种。举个例子,其能够很轻松的创建一个免费版本和付费版本的app。这两个版本需要分隔的标示码,所以他们能够以不同的app出现在各大应用商店,当然他们也能够同时安装在一个手机中。资源代码和R文件必须拥有相同的包名,否则你的资源代码将需要改变,这就是为什么Android开发团队要将package name的两大功能拆分开。在AndroidManifest文件中定义的package name依然被用来作为包名和R文件的包名。而applicationid将被用在设备和各大应用商店中作为唯一的标示。

接下来将是minSdkVersion和targetSdkVersion。这两个和AndroidManifest中的<uses-sdk>很像。minSdkVersion定义为最小支持api。versionCode将会作为版本号标示,而versionName毫无作用。所有的属性都是重写了AndroidManifest文件中的属性,所以你没必要在AndroidManifest中定义这些属性了。buildTypes方法定义了如何构建不同版本的app.

依赖包

依赖模块作为gradle默认的属性之一(这也是为什么其放在了Android的外面),为你的app定义了所有的依赖包。默认情况下,我们依赖了所有在libs文件下的jar文件,同时包含了AppCompat这个aar文件。

基本的tasks

android插件依赖于Java插件,而Java插件依赖于base插件。

base插件有基本的tasks生命周期和一些通用的属性。

base插件定义了例如assemble和clean任务,Java插件定义了check和build任务,这两个任务不在base插件中定义。

这些tasks的约定含义:

assemble: 集合所有的output

clean: 清除所有的output

check: 执行所有的checks检查,通常是unit测试和instrumentation测试

build: 执行所有的assemble和check

Java插件同时也添加了source sets的概念。

Android tasks

android插件继承了这些基本tasks,并且实现了他们自己的行为:

assemble 针对每个版本创建一个apk

clean 删除所有的构建任务,包含apk文件

check 执行Lint检查并且能够在Lint检测到错误后停止执行脚本

build 执行assemble和check

默认情况下assemble tasks定义了assembleDebug和assembleRelease,当然你还可以定义更多构建版本。除了这些tasks,android 插件也提供了一些新的tasks:

connectedCheck 在测试机上执行所有测试任务

deviceCheck 执行所有的测试在远程设备上

installDebug和installRelease 在设备上安装一个特殊的版本

所有的install task对应有uninstall 任务

build task依赖于check任务,但是不依赖于connectedCheck或者deviceCheck,执行check任务的使用Lint会产生一些相关文件,这些报告可以在app/build/outputs中查看:

android studio的tasks

你根本不必要去执行gradle脚本在命令行中,Android studio有其对应的工具:

在这个界面,你要做的就是双击了。当然你也可以在Android studio中打开命令行,执行相关命令,具体操作就不介绍了。

自定义构建

当你在Android studio中自定义了gradle文件,需要更新project:

其实该按钮,执行了generateDebugSources tasks,该任务会生成所有必要的classes文件。

BuildConfig和resources

android {
    buildTypes {
        debug {
            buildConfigField "String", "API_URL",
               "\"http://test.example.com/api\""
               buildConfigField "boolean", "LOG_HTTP_CALLS", "true"
     }
       release {
            buildConfigField "String", "API_URL",
                "\"http://example.com/api\""
               buildConfigField "boolean", "LOG_HTTP_CALLS","false"
     } 
 }

类似这些定义的常量,当定义了这些属性后,你完全可以在代码中使用:BuildConfig.API_URL和BuildConfig.LOG_HTTP

最近,Android tools team也让其里面定义string变为可能:

android {
       buildTypes {
           debug {
               resValue "string", "app_name", "Example DEBUG"
           }
           release {
               resValue "string", "app_name", "Example"
            } 
       }
}

你可以在代码中使用这些string。其中“”不是必须得。

全局设置

如果你有很多模块在一个工程下,你可以这么定义你的project文件。

allprojects {
       apply plugin: 'com.android.application'
       android {
           compileSdkVersion 22
           buildToolsVersion "22.0.1"
       }
 }

这只会在你的所有模块都是Android app应用的时候有效。你需要添加Android 插件才能访问Android的tasks。更好的做法是你在全局的gradle文件中定义一些属性,然后再模块中运用它们。比如你可以在根目录下这么定义:

ext {
       compileSdkVersion = 22
       buildToolsVersion = "22.0.1"
}  

那么你在子模块中就可以使用这些属性了:

android {
       compileSdkVersion rootProject.ext.compileSdkVersion
       buildToolsVersion rootProject.ext.buildToolsVersion
 }
 

Project properties文件

上述方法是一种办法,当然还有很多办法:

ext方法

gradle.properties文件

-p参数

ext {
     local = 'Hello from build.gradle'
}
   task printProperties << {
     println local        // Local extra property
     println propertiesFile        // Property from file
     if (project.hasProperty('cmd')) {
       println cmd        // Command line property
     }
}

当然你可以在gradle.properties中定义:

propertiesFile = Hello from gradle.properties

你也可以输入命令行:

$ gradlew printProperties -Pcmd='Hello from the command line'
:printProperties
Hello from build.gradle
Hello from gradle.properties
Hello from the command line

依赖管理

依赖管理是Gradle最闪耀的地方,最好的情景是,你仅仅只需添加一行代码在你的build文件,Gradle会自动从远程仓库为你下载相关的jar包,并且保证你能够正确使用它们。Gradle甚至可以为你做的更多,包括当你在你的工程里添加了多个相同的依赖,gradle会为你排除掉相同的jar包。

仓库

当我们讨论依赖的时候,我们通常说的是远程仓库,就像那些依赖库专门用来提供给其他开发者使用的依赖库。手动管理依赖将会为你带来很大麻烦。你必须定位到该依赖文件位置,然后下载jar文件,复制该文件到你的项目,然后引用它们。通常这些jar文件还没有具体的版本号,所以你还必须去记忆它们的版本号,这样当需要更新的时候,你才会知道需要替换成哪个版本。你同时必须将该依赖包放在svn或者git上,这样你的其他同事才可以不用手动去下载这些依赖jar。

使用远程仓库可以解决这些问题,一个仓库可以被视为一些文件的集合体。Gradle不会默认为你的项目添加任何仓库。所以你需要把它们添加到repositories方法体内。如果是使用的是Android studio,那么工具已经为你准备好了这一切:

repositories {
    jcenter()
}

Gradle支持三种不同的仓库,分别是:Maven和Ivy以及文件夹。依赖包会在你执行build构建的时候从这些远程仓库下载,当然Gradle会为你在本地保留缓存,所以一个特定版本的依赖包只需要下载一次。

一个依赖需要定义三个元素:group,name和version。group意味着创建该library的组织名,通常这会是包名,name是该library的唯一标示。version是该library的版本号,我们来看看如何申明依赖:

dependencies {
       compile 'com.google.code.gson:gson:2.3'
       compile 'com.squareup.retrofit:retrofit:1.9.0'
}

上述的代码是基于groovy语法的,所以其完整的表述应该是这样的:

dependencies {
      compile group: 'com.google.code.gson', name: 'gson', version:'2.3'
      compile group: 'com.squareup.retrofit', name: 'retrofit'
           version: '1.9.0'
     }

为你的仓库预定义

为了方便,Gradle会默认预定义三个maven仓库:Jcenter和mavenCentral以及本地maven仓库。疑问:预定义仓库用途?你可以同时申明它们:

repositories {
       mavenCentral()
       jcenter()
       mavenLocal()
   }
   

Maven和Jcenter仓库是很出名的两大仓库。我们没必要同时使用他们,在这里我建议你们使用jcenter,jcenter是maven中心库的一个分支,这样你可以任意去切换这两个仓库。当然jcenter也支持了https,而maven仓库并没有。

本地maven库是你曾使用过的所有依赖包的集合,当然你也可以添加自己的依赖包。默认情况下,你可以在你的home文件下找到.m2的文件夹。除了这些仓库外,你还可以使用其他的公有的甚至是私有仓库。

远程仓库

有些组织,创建了一些有意思的插件或者library,他们更愿意把这些放在自己的maven库,而不是maven中心库或jcenter。那么当你需要是要这些仓库的时候,你只需要在maven方法中加入url地址就好:

repositories {
       maven {
           url "http://repo.acmecorp.com/maven2"
       }
}

同样的,Ivy仓库也可以这么做。Apache Ivy在ant世界里是一个很出名的依赖管理工具。如果你的公司有自己的仓库,如果他们需要权限才能访问,你可以这么编写:

repositories {
       maven {
           url "http://repo.acmecorp.com/maven2"
           credentials {
               username 'user'
               password 'secretpassword'
           }
        } 
   }

注意:这不是一个好主意,最好的方式是把这些验证放在Gradle properties文件里。

本地依赖

可能有些情况,你需要手动下载jar包,或者你想创建自己的library,这样你就可以复用在不同的项目,而不必将该library publish到公有或者私有库。在上述情况下,可能你不需要网络资源,接下来我将介绍如何是使用这些jar依赖,以及如何导入so包,如何为你的项目添加依赖项目。

文件依赖

如果你想为你的工程添加jar文件作为依赖,你可以这样:

dependencies {
       compile files('libs/domoarigato.jar')
}

如果你这么做,那会很愚蠢,因为当你有很多这样的jar包时,你可以改写为:

dependencies {
       compile fileTree('libs')
 }
 

默认情况下,新建的Android项目会有一个lib文件夹,并且会在依赖中这么定义(即添加所有在libs文件夹中的jar):

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

这也意味着,在任何一个Android项目中,你都可以把一个jar文件放在到libs文件夹下,其会自动的将其添加到编译路径以及最后的APK文件。

native包(so包)

用c或者c++写的library会被叫做so包,Android插件默认情况下支持native包,你需要把.so文件放在对应的文件夹中:

app
   ├── AndroidManifest.xml
   └── jniLibs
       ├── armeabi
       │   └── nativelib.so
       ├── armeabi-v7a
       │   └── nativelib.so
       ├── mips
       │   └── nativelib.so
       └── x86
           └── nativelib.so
           

aar文件

如果你想分享一个library,该依赖包使用了Android api,或者包含了Android 资源文件,那么aar文件适合你。依赖库和应用工程是一样的,你可以使用相同的tasks来构建和测试你的依赖工程,当然他们也可以有不同的构建版本。应用工程和依赖工程的区别在于输出文件,应用工程会生成APK文件,并且其可以安装在Android设备上,而依赖工程会生成.aar文件。该文件可以被Android应用工程当做依赖来使用。

创建和使用依赖工程模块

不同的是,你需要加不同的插件:

 apply plugin: 'com.android.library'

我们有两种方式去使用一个依赖工程。一个就是在你的工程里面,直接将其作为一个模块,另外一个就是创建一个aar文件,这样其他的应用也就可以复用了。

如果你把其作为模块,那你需要在settings.gradle文件中添加其为模块:

   include ':app', ':library'

  我们就把它叫做library吧,如果你想使用该模块,你需要在你的依赖里面添加它,就像这样:

  dependencies {
       compile project(':library')
  }

使用aar文件

如果你想复用你的library,那么你就可以创建一个aar文件,并将其作为你的工程依赖。当你构建你的library项目,aar文件将会在 build/output/aar/下生成。把该文件作为你的依赖包,你需要创建一个文件夹来放置它,我们就叫它aars文件夹吧,然后把它拷贝到该文件夹里面,然后添加该文件夹作为依赖库:

repositories {
    flatDir {
        dirs 'aars' 
    }
}

这样你就可以把该文件夹下的所有aar文件作为依赖,同时你可以这么干

 dependencies {
       compile(name:'libraryname', ext:'aar')
}

这个会告诉Gradle,在aars文件夹下,添加一个叫做libraryname的文件,且其后缀是aar的作为依赖。

依赖的概念

配置

有些时候,你可能需要和sdk协调工作。为了能顺利编译你的代码,你需要添加SDK到你的编译环境。你不需要将sdk包含在你的APK中,因为它早已经存在于设备中,所以配置来啦,我们会有5个不同的配置:

  • compile

  • apk

  • provided

  • testCompile

  • androidTestCompile

compile是默认的那个,其含义是包含所有的依赖包,即在APK里,compile的依赖会存在

apk的意思是apk中存在,但是不会加入编译中,这个貌似用的比较少。

provided的意思是提供编译支持,但是不会写入apk

testCompile和androidTestCompile添加额外的library支持针对测试

这些配置将会被用在测试相关的tasks中,这会对添加测试框架例如JUnit或者Espresso非常有用,因为你只是想让这些框架们能够出现在测试apk中,而不是生产apk中。

除了这些特定的配置外,Android插件还为每个构建变体提供了配置,这让debugCompile或者releaseProvided等配置成为可能。如果你想针对你的debug版本添加一个logging框架,这将很有用。

动态版本

在一些情形中,你可能想使用最新的依赖包在构建你的app或者library的时候。实现他的最好方式是使用动态版本。我现在给你们展示几种不同的动态控制版本方式:

dependencies {
       compile 'com.android.support:support-v4:22.2.+'
       compile 'com.android.support:appcompat-v7:22.2+'
       compile 'com.android.support:recyclerview-v7:+'
}

第一行,我们告诉gradle,得到最新的生产版本。第二行,我们告诉gradle,我们想得到最新的minor版本,并且其最小的版本号是2. 第三行,我们告诉gradle,得到最新的library。

你应该小心去使用动态版本,如果当你允许gradle去挑选最新版本,可能导致挑选的依赖版本并不是稳定版,这将会对构建产生很多问题,更糟糕的是你可能在你的服务器和私人pc上得到不同的依赖版本,这直接导致你的应用不同步。

如果你在你的build.gradle中使用了动态版本,Android studio将会警告你关于动态版本的潜在问题,就像你下面看到的这样:

Android studio UI操作依赖库

在使用Android studio中,最简单的添加新依赖包的方法是使用工程结构弹框。从文件按钮中打开界面,导航到依赖包导航栏,然后你就可以看到你当前的依赖包了:

当你想添加新的依赖包的时候,可以点击绿色的小按钮,你可以添加其他模块,文件,甚至是上网搜索。

使用Android studio的界面让你能够很简单的浏览你项目中的所有依赖,并且添加新的依赖包。你不必在build.gradle中手动的添加代码了,并且你可以直接搜索JCenter库中的依赖资源。

当你在开发一个app,通常你会有几个版本。大多数情况是你需要一个开发版本,用来测试app和弄清它的质量,然后还需要一个生产版本。这些版本通常有不同的设置,例如不同的URL地址。更可能的是你可能需要一个免费版和收费版本。基于上述情况,你需要处理不同的版本:开发免费版,开发付费版本,生产免费版,生产付费版,而针对不同的版本不同的配置,这极大增加的管理难度。

Gradle有一些方便的方法来管理这些问题。我们很早之前谈过debug和release版本,现在我们谈到另外一个概念,不同的产品版本。构建版本和生产版本通常可以合并,构建版本和生产版本的合并版叫做构建变种。

我们遵循如下规则:

  • Build types

  • Product flavors

  • Build variants

  • Signing configurations

构建版本

在Gradle的Android插件中,一个构建版本意味着定义一个app或者依赖库如何被构建。每个构建版本都要特殊的一面,比如是否需要debug,application id是什么,是否不需要的资源被删除等等。你可以定义一个构建版本通过buildTypes方法。例如:

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

这个文件定义了该模块是release版本,然后定义了proguard的位置。该release版本不是唯一的构建版本,默认情况下,还有个debug版本。Android studio把它视为默认构建版本。

创建自己的构建版本

当默认的构建版本不够用的时候,创建版本也是很容易的一件事,创建构建版本你只需要在buildTypes写入自己的版本。如下所示:

android {
    buildTypes {
        staging {
            applicationIdSuffix ".staging"
            versionNameSuffix "-staging"
            buildConfigField "String", "API_URL",
            "\"http://staging.example.com/api\""
         }
    }
}

我们定义了一个staging版本,该版本定义了一个新的application id,这让其与debug和release版本的applicationID不同。假设你使用了默认的配置,那么applicationID将会是这样的:

  • Debug: com.package

  • Release: com.package

  • Staging: com.package.staging

这意味着你可以在你的设备上安装staging版本和release版本。staging版本也有自己的版本号。buildConfigField定义了一个新的URL地址。你不必事事都去创建,所以最可能的方式是去继承已有的版本。

android {
       buildTypes {
           staging.initWith(buildTypes.debug)
           staging {
               applicationIdSuffix ".staging"
               versionNameSuffix "-staging"
               debuggable = false
           } 
        }
}

initWith()方法创建了一个新的版本的同时,复制所有存在的构建版本,类似继承。我们也可以复写该存在版本的所有属性

Source sets

当你创建了一个新的构建版本,Gradle也创建了新的source set。默认情况下,该文件夹不会自动为你创建,所有你需要手工创建。

app
└── src
├── debug
│ ├── java
       │   │   └── com.package
 │ │
│ ├── res
│ │ └── layout
│   │       └── activity_main.xml
│   └── AndroidManifest.xml
├── main
│ ├── java
│   │   └── com.package
│ │
│ ├── res
└── MainActivity.java
└── Constants.java
│ │
│ │
│ │
│   └── AndroidManifest.xml
├── staging
│ ├── java
│   │   └── com.package
├── drawable
└── layout
└── activity_main.xml
│ │
│ ├── res
│ │ └── layout
│   │       └── activity_main.xml
│   └── AndroidManifest.xml
└── release
    ├── java
    │   └── com.package
    │       └── Constants.java
    └── AndroidManifest.xml
    

注意:当你添加一个Java类的时候,你需要知道以下过程,当你添加了CustomLogic.java到staging版本,你可以添加相同的类到debug和release版本,但是不能添加到main版本。如果你添加了,会抛出异常.

当使用不同的source sets的时候,资源文件的处理需要特殊的方式。Drawables和layout文件将会复写在main中的重名文件,但是values文件下的资源不会。gradle将会把这些资源(values文件下的资源)连同main里面的资源一起合并。

举个例子,当你在main中创建了一个srings.xml的时候:

<resources>
       <string name="app_name">TypesAndFlavors</string>
       <string name="hello_world">Hello world!</string>
</resources>

当你在你的staing版本也添加了rings.xml:

<resources>
       <string name="app_name">TypesAndFlavors STAGING</string>
</resources>

然后合并的strings.xml将会是这样的:

<resources>
       <string name="app_name">TypesAndFlavors STAGING</string>
       <string name="hello_world">Hello world!</string>
</resources>

当你创建一个新的构建版本而不是staging,最终的strings.xml将会是main目录下的strings.xml。

manifest也和value文件下的文件一样。如果你为你的构建版本创建了一个manifest文件,那么你不必要去拷贝在main文件下的manifest文件,你需要做的是添加标签。Android插件将会为你合并它们。

依赖包

每一个构建版本都有自己的依赖包,gradle自动为每一个构建的版本创建不同的依赖配置。如果你想为debug版本添加一个logging框架,你可以这么做:

dependencies {
   compile fileTree(dir: 'libs', include: ['*.jar'])
   compile 'com.android.support:appcompat-v7:22.2.0'
   debugCompile 'de.mindpipe.android:android-logging-log4j:1.0.3'
}

你可以结合不同的构建版本着不同的构建配置,就像这种方式,这让你的不同版本的不同依赖包成为可能。

product flavors

和构建版本不同,product flavors用来为一个app创建不同版本。典型的例子是,一个app有付费和免费版。product flavors极大简化了基于相同的代码构建不同版本的app。

如果你不确定你是否需要一个新的构建版本或者product flavors,你应该问你自己,你是否需要内部使用和外部使用的apk。如果你需要一个完全新的app去发布,和之前的版本完全隔离开,那么你需要product flavors。否则你只是需要构建版本。

创建product flavors

创建product flavors非常的容易。你可以在productFlavors中添加代码:

android {
    productFlavors {
        red {
             applicationId 'com.gradleforandroid.red'
             versionCode 3
        }
        blue {
             applicationId 'com.gradleforandroid.blue'
             minSdkVersion 14
             versionCode 4
        }
    }
}

product flavors和构建版本的配置不同。因为product flavors有自己的ProductFlavor类,就像defaultConfig,这意味着你的所有productFlavors都分享一样的属性。

Source sets

就像构建版本一样,product Flavors也有自己的代码文件夹。创建一个特殊的版本就像创建一个文件夹那么简单。举个例子,当你有的生产版本的blue flavors有一个不同的app图标,该文件夹需要被叫做blueRelease。

多个flavors构建变体

在一些例子中,你可能需要创建一些product flavors的合并版本。举个例子,client A和client B可能都想要一个free和paid的版本,而他们又都是基于一样的代码,但是有不一样的颜色等。创建四个不同的flavors意味着有重复的配置。合并flavors最简单的做法可能是使用flavor dimensions,就像这样:

 android {
       flavorDimensions "color", "price"
       productFlavors {
           red {
               flavorDimension "color"
           }
           blue {
               flavorDimension "color"
           }
           free {
               flavorDimension "price"
           }
           paid {
               flavorDimension "price"
           }
       }
}

当你添加了flavor dimensions,你就需要为每个flavor添加flavorDimension,否则会提示错误。flavorDimensions定义了不同的dimensions,当然其顺序也很重要。当你合并二个不同的flavors时,他们可能有一样的配置和资源。例如上例:

  • blueFreeDebug and blueFreeRelease

  • bluePaidDebug and bluePaidRelease

  • redFreeDebug and redFreeRelease

  • redPaidDebug and redPaidRelease

构建变体

构建变体是构建版本和生产版本的结合体。当你创建了一个构建版本或者生产版本,同样的,新的变体也会被创建。举个例子,当你有debug和release版本,你创建了red和blue的生产版本,那么变体将会有四个:

你可以在Android studio的左下角找到它,或者通过VIEW|Tool Windows|Build Variants打开它。该视图列出了所有的变体,并且允许你去切换它们。改变他们将会影响到你按Run按钮。

如果你没有product flavors,那么变体只是简单的包含构建版本,就算你没有定义任何构建版本,Android studio也会默认为你创建debug版本的。

tasks

android插件回味每一个变体创建不同的配置。一个新的Android项目会有debug和release版本,所有你可以使用assembleDebug和assembleRelease,当然当你使用assemble命令,会二者都执行。当你添加了一个新的构建版本,新的task也会被创建。例如:

  • assembleBlue uses the blue flavor configuration and assembles both BlueRelease and BlueDebug.

  • assembleBlueDebug combines the flavor configuration with the build type configuration, and the flavor settings override the build type settings

Source sets

构建变体也可以有自己的资源文件夹,举个例子,你可以有src/blueFreeDebug/java/。

资源文件和manifest的合并

在打包app之前,Android插件会合并main中的代码和构建的代码。当然,依赖项目也可以提供额外的资源,它们也会被合并。你可能需要额外的Android权限针对debug变体。举个例子,你不想在main中申明这个权限,因为这可能导致一些问题,所以你可以添加一个额外的mainfest文件在debug的文件夹中,申明额外的权限。

资源和mainfests的优先级是这样的:

如果一个资源在main中和在flavor中定义了,那么那个在flavor中的资源有更高的优先级。这样那个在flavor文件夹中的资源将会被打包到apk。而在依赖项目申明的资源总是拥有最低优先级。

创建构建变体

gradle让处理构建变体变得容易。

android {
       buildTypes {
           debug {
               buildConfigField "String", "API_URL",
               "\"http://test.example.com/api\""
        }
           staging.initWith(android.buildTypes.debug)
           staging {
               buildConfigField "String", "API_URL",
                 "\"http://staging.example.com/api\""
               applicationIdSuffix ".staging"
           }
       }
       productFlavors {
           red {
               applicationId "com.gradleforandroid.red"
               resValue "color", "flavor_color", "#ff0000"
           }
           blue {
               applicationId "com.gradleforandroid.blue"
               resValue "color", "flavor_color", "#0000ff"
           } 
     }
}

在这个例子中,我们创建了4个变体,分别是blueDebug,blueStaging,redDebug,redStaging。每一个变体都有其不同的api url以及颜色。例如:

变体过滤器

忽略某个变体也是可行的。这样你可以加速你的构建当使用assemble的时候,这样你列出的tasks将不会执行那么你不需要的变体。你可以使用过滤器,在build.gradle中添加代码如下所示:

android.variantFilter { variant ->
       if(variant.buildType.name.equals('release')) {
           variant.getFlavors().each() { flavor ->
               if (flavor.name.equals('blue')) { variant.setIgnore(true);
            }
        }
    }
}

在这个例子中,我们检查下:

你可以看到blueFreeRelease和bluePaidRelease被排除在外,如果你运行gradlew tasks,你会发现所有的关于上述变体的tasks不再存在。

签名配置

在你发布你的应用之前,你需要为你的app私钥签名。如果你有付费版和免费版,你需要有不同的key去签名不同的变体。这就是配置签名的好处。配置签名可以这样定义:

 android {
       signingConfigs {
           staging.initWith(signingConfigs.debug)
           release {
               storeFile file("release.keystore")
               storePassword"secretpassword"
               keyAlias "gradleforandroid"
               keyPassword "secretpassword"
           }
      }
}

在这个例子中,我们创建了2个不同的签名配置。debug配置是as默认的,其使用了公共的keystore和password,所以没有必要为debug版本创建签名配置了。staging配置使用了initWith()方法,其会复制其他的签名配置。这意味着staging和debug的key是一样的。

release配置使用了storeFile,定义了key alias和密码。当然这不是一个好的选择,你需要在 Gradle properties文件中配置。

当你定义了签名配置后,你需要应用它们。构建版本都有一个属性叫做signingConfig,你可以这么干:

android {
       buildTypes {
           release {
               signingConfig signingConfigs.release
           } 
       }
}

上例使用了buildTypes,但是你可能需要对每个版本生成不同的验证,你可以这么定义:

android {
       productFlavors {
           blue {
               signingConfig signingConfigs.release
           }
       }
}

当然,你在flavor中定义这些,最好会被重写,所以最好的做法是:

android {
       buildTypes {
           release {
               productFlavors.red.signingConfig signingConfigs.red
               productFlavors.blue.signingConfig signingConfigs.blue
           }
       }
}

Android studio不仅允许你为你的app和依赖库创建模块,同时也可为Android wear,Android TV,Google App Engine等创建模块,而这些单独的模块又可以在一个单独的项目中使用。举个栗子,在你的app开发后期阶段,你可能需要用到Google Clound或者Android Wear。这种情况下,你可以在你的工程下拥有三个模块:分别是app,google cloud,Android Wear整合。

多模块构建的结构

通常情况下,一个工程包含多模块,这些模块会在一个父目录文件夹下。为了告诉gradle,该项目的结构以及哪一个子文件夹包含模块,你需要提供一个settings.gradle文件。每个模块可以提供其独立的build.gradle文件。

这是多模块项目的结构图:

 project
   ├─── setting.gradle
   ├─── build.gradle
   ├─── app
   │    └─── build.gradle
   └─── library
        └─── build.gradle
        

setting.gradle文件申明了该项目下的所有模块,它应该是这样:

include ':app', ':library'

这保证了app和library模块都会包含在构建配置中。你需要做的仅仅只是为你的模块添加子文件夹。

为了在你的app模块中添加library模块做为其依赖包,你需要在app的build.gradle文件中添加以下内容:

dependencies {
      compile project(':library') 
}

为了给app添加一个模块作为依赖,你需要使用project()方法,该方法的参数为模块路径

如果在你的模块中还包含了子模块,gradle可以满足你得要求。举个栗子,你可以把你的目录结构定义为这样:

project
├─── setting.gradle
├─── build.grade
├─── app
│    └─── build.gradle
└─── libraries
     ├─── library1
     │    └─── build.gradle
     └─── library2
          └─── build.gradle

该app模块依然位于根目录,但是现在项目有2个不同的依赖包。这些依赖模块不位于项目的根目录,而是在特定的依赖文件夹内。根据这一结构,你需要在settings.xml中这么定义:

include ':app', ':libraries:library1', ':libraries:library2'

你会注意到在子目录下申明模块也非常的容易。所有的路径都是围绕着根目录,即当你添加一个位于子文件夹下的模块作为另外一个模块的依赖包得实惠,你应该将路径定为根目录。这意味着如果在上例中app模块想要依赖library1,build.gradle文件需要这么申明:

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

如果你在子目录下申明了依赖,所有的路径都应该与根目录相关。这是因为gradle是根据你的项目的根目录来定义你的依赖包的。

构建生命周期

初始化阶段,gradle会寻找到settings.grade文件。如果该文件不存在,那么gradle就会假定你只有一个单独的构建模块。如果你有多个模块,settings.gradle文件定义了这些模块的位置。如果这些子目录包含了其自己的build.gradle文件,gradle将会运行它们,并且将他们合并到构建任务中。这就解释了为什么你需要申明在一个模块中申明的依赖是相对于根目录。

你可以配置所有的模块在根目录下的build.gradle。这让你能够简单的浏览到整个项目的配置,但是这将会变得一团乱麻,特别是当你的模块需要不同的插件的时候。另外一种方式是将每个模块的配置分隔开,这一策略保证了每个模块之间的互不干扰。这也让你跟踪构建的改变变得容易,因为你不需要指出哪个改变导致了哪个模块出现错误等。

gradle的最大策略是混合。你可以在根目录下定义一个build文件去定义所有模块相同的属性,然后在每个模块中的build文件去配置只属于该模块的参数。Android studio遵循了该原则,其创建了一个build.gradle文件在根目录,然后再每个模块文件夹下创建了另外一个build文件。

模块tasks

当你在你的项目中有多个模块的时候,你需要在运行任务之前想一想。当你在命令行界面运行一个task的时候,gradle将会找到哪个模块将会执行这个任务。举个例子,当你有个mobile app模块和一个Android Wear模块,你运行了gradlew assembleDebug任务。当你改变其中一个模块的文件夹位置,gradle将只会运行哪个特殊的模块,纵使你使用了gradle wrapper在根目录。举个例子,当你运行../gradlew assembleDebug在Android wear模块的目录下,其只会构建Android wear模块。

切换不同的文件夹去执行不同的任务会让人很不爽,幸运的是,我们有其他的办法。你可以准备一个特别的task来执行你的模块。举个栗子,为了只构建Android Wear模块,你仅仅只需在根目录下运行 gradlew :wear:assembleDebug。

为你的项目添加模块

在Android studio中添加新模块是很容易的一件事,该视图同时也会为你创建build文件。如下图所示:

添加Java依赖库

当你新建了一个Java模块,build.grade文件会是这样:

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

Java模块使用了Java插件,这意味着很多Android特性在这儿不能使用,因为你不需要。

build文件也有基本的库管理,你可以添加jar文件在libs文件夹下。你可以添加更多的依赖库。

dependencies {
       compile project(':javalib')
}

这告诉了gradle去引入一个叫做javelin的模块吧,如果你为你的app模块添加了这个依赖,那么javalib模块将会总是在你的app模块构建之前构建。

添加Android依赖库

同样的,我们利用Android studio的图形化界面创建Android模块,然后其构建文件如下:

apply plugin: 'com.android.library'

记住:Android依赖库不仅仅包含了Java代码,同样也会包含Android资源,像manifest和strings,layout文件,在你引入该模块后,你可以使用该模块的所有类和资源文件。

在Android studio中运行模块tasks

当你有多个模块,Android studio会分析出来,并且展示在cradle中:

grade图形化让你运行模块间的任务变得简单,但是其没有为所有模块同时运行一个任务,所以如果你希望这么做,最快的方式是使用命令行。

加速你的多模块构建

当你构建你的多模块项目,gradle会依次执行所有的模块。当你的电脑内存够大的时候,让你的构建过程多线程将会更快。该特性在gradle早已存在,但是其默认关闭。

所以如果你希望启动parallel构建,你需要在grade.properties文件中配置如下属性:

org.gradle.parallel=true

gradle会选择尽可能多的线程去执行你的构建过程,每个线程都会执行一个模块。parallel执行的是独立的模块,即你的模块是独立的。

模块耦合

即你可以在一个模块中引用其他模块的属性,但是我不建议你们这么做,我们完全可以在根目录下的build文件中定义这些属性。

我们现在不仅可以在Android studio中运行这些测试,甚至可以用gradle通过命令行直接执行。

单元测试

Android studio和gradle android插件默认支持单元测试,但是在你使用它之前,你仍需配置一下。

JUnit

JUnit测试界非常流行,其使得测试代码容易编写和维护,但是记住,JUnit只能测试逻辑代码,针对和Android SDK相关的代码其会报相应的错误。

在你开始编写junit测试之前,你需要为其新建一个目录。通常呢,这个会被叫做test,其会和你的main文件夹平级。

app
└─── src
     ├─── main
          ├─── java
          │    └─── com.example.app
          └─── res
     └─── test
          └─── java
               └─── com.example.app
                  

你可以在test目录下创建测试类。

我建议你使用JUnit 4,你可以将其作为依赖添加到你的依赖库。

dependencies {
       testCompile 'junit:junit:4.12'
}

注意到你使用了testCompile,这意味着该jar包只会在你测试的时候导入apk。

如果你有其他的构建版本呢,而你又只是想为特定版本添加该jar,你只需要这么做:

dependencies {
       testPaidCompile 'junit:junit:4.12'
 }
 

下面是简单的测试代码:

import org.junit.Test;
   import static org.junit.Assert.assertEquals;
   public class LogicTest {
       @Test
       public void addingNegativeNumberShouldSubtract() {
           Logic logic = new Logic();
           assertEquals("6 + -2 must be 4", 4, logic.add(6, -2));
           assertEquals("2 + -5 must be -3", -3, logic.add(2, -5));
       }
}

那么如何跑起来呢,也很简单,运行gradlew test。如果你只是想再特定版本中跑呢,那就加一个呗gradlew testDebug。如果测试失败,gradle将会打印相关错误,如果所有测试成功通过,那么会显示BUILD SUCCESSFUL 。

可能你会说,单个测试用例导致整个测试失败,这样不好,如果你想把整个测试案例都跑一遍,那也很简单啊:

$ gradlew test --continue

执行测试任务不仅仅是跑完所有的测试,而且其还会为你创建一份测试报告,你可以找到它app/build/reports/tests/debug/index.html。这份报告让你能够更快的发现问题,我觉得最重要的是当你将你的测试自动化后,这会非常有用,gradle会为每个构建版本都创建一份测试报告。如果你执行测试成功,你的测试报告会是这个样子:

说了这么多原始的做法,那么看看Android studio怎么运行测试的吧。右键项目或者选择开始按钮。。这个太基础不多说了,运行成功是这个样子:

好了,junit测试讲完了,是不是很简单。

如果你想测试你的关联Android sdk代码怎么办,单元测试不是一个好主意,幸运的是,有多个依赖包供你选择,其中最出名的是Robolectric,其可以让你更方便的测试Android功能,并且还不用在设备或者模拟器上运行。

Robolectric

通过使用Robolectrie,你可以编写测试类,这些类可以使用Android SDK和资源文件,当然其还是跑在jvm上,这会让你测试app更加迅速。

在开始使用Robolectrie之前,你需要添加依赖。注意除了Robolectric依赖,你需要添加JUnit包。

apply plugin: 'org.robolectric'
   dependencies {
       compile fileTree(dir: 'libs', include: ['*.jar'])
       compile 'com.android.support:appcompat-v7:22.2.0'
       testCompile 'junit:junit:4.12'
       testCompile'org.robolectric:robolectric:3.0'
       testCompile'org.robolectric:shadows-support:3.0'
}

Robolectrie测试类也需要写在test文件夹下,举个例子:

 @RunWith(RobolectricTestRunner.class)
   @Config(manifest = "app/src/main/AndroidManifest.xml", sdk = 18)
   public class MainActivityTest {
       @Test
       public void clickingButtonShouldChangeText() {
           AppCompatActivity activity = Robolectric.buildActivity
             (MainActivity.class).create().get();
           Button button = (Button)
             activity.findViewById(R.id.button);
           TextView textView = (TextView)
             activity.findViewById(R.id.label);
           button.performClick();
           assertThat(textView.getText().toString(), equalTo
             (activity.getString(R.string.hello_robolectric)));
        } 
    }

功能测试

神马是功能测试,其是用来测试一个app的多个模块是否能够正常工作。举个例子,你可以创建一个功能测试来确保你点击某一按钮后是否会有一个新的activity。依然,我们会有很多框架。但是在这里,我推荐Espresso。

Espresso

google创建Espresso的目的就是在于简化开发人员编写功能测试用例。这个包是由Android support repository提供,所以你可以通过SDK Manager使用它。

在运行测试用例之前,你需要定义一个runner。google提供了AndroidJUnitRunner测试runner,这将帮助你在手机上运行Unit测试。测试runner可以帮你安装apk以及一个测试apk,执行所有测试,生成测试报告。

假设你下载了support library包,那么你需要这么定义:

defaultConfig {
       testInstrumentationRunner
         "android.support.test.runner.AndroidJUnitRunner"
   }

当然你需要添加一些依赖包:

 dependencies {
       compile fileTree(dir: 'libs', include: ['*.jar'])
       compile 'com.android.support:appcompat-v7:22.2.0'
       androidTestCompile 'com.android.support.test:runner:0.3'
       androidTestCompile 'com.android.support.test:rules:0.3'
       androidTestCompile
         'com.android.support.test.espresso:espresso-core:2.2'
       androidTestCompile
         'com.android.support.test.espresso:espresso-contrib:2.2'
   }

注意到这些依赖包使用了androidTestCompile,其不同于testCompile。当你直接运行时,会报错:

Error: duplicate files during packaging of APK app-androidTest.apk
     Path in archive: LICENSE.txt
     Origin 1: ...\hamcrest-library-1.1.jar
     Origin 2: ...\junit-dep-4.10.jar
     

其意思也很清楚,因为多个文件导致,你可以简单处理下:

 android {
     packagingOptions {
     exclude 'LICENSE.txt'
  }
}

注意:功能测试需要放在AndroidTest目录下,下面是测试用例:

@RunWith(AndroidJUnit4.class)
   @SmallTest
   public class TestingEspressoMainActivityTest {
       @Rule
       public ActivityTestRule<MainActivity> mActivityRule = new
         ActivityTestRule<>(MainActivity.class);
       @Test
       public void testHelloWorldIsShown() {
           onView(withText("Hello world!")).check
             (matches(isDisplayed()));
        } 
    }

功能测试也有测试报告,当正确执行后,应该是这样的:

最后可能有朋友问,在Android studio中执行测试,那就附图吧:最后可能有朋友问,在Android studio中执行测试,那就附图吧:

测试覆盖率

一旦你在你的项目中使用到了测试,那么你肯定想知道你的测试覆盖量。很真实,依然有很多测试覆盖率工具,我推荐的是Jacoco。

Jacoco

有一份覆盖率报告,很简单。你只需要配置一下:

buildTypes {
     debug {
       testCoverageEnabled = true
     }
}

当你执行完构建,你可以在app/build/ outputs/reports/coverage/debug/index.html中找到,每个版本都会有一个报告。测试覆盖率会是这样的:

你甚至可以通过点击查看更多信息,可以看到哪一行代码被测试。

理解Groovy

大部分Android开发者也是名Java开发者,和Java对比,Groovy也是运行在JVM上。当然,如果你是名Java开发者,那么Groovy将会变得容易阅读,但是在编写你自己的Groovy代码之前,你必须对Groovy有一个全面的了解。

简介

Groovy起源于Java,其运行在JVM上。其目标是创造更简单,更直接的语言,不仅仅在脚本语言上或者编译语言上运行。

在Java中,打印一天String应该是这样的:

System.out.println("Hello, world!");

在Groovy中,你可以这么写:

println 'Hello, world!'

你应该主要到几点不同之处:

  • 没有了System.out

  • 没有了方括号

  • 列结尾没有了;

这个例子同样使用了单引号,你可以使用双引号或者单引号,但是他们有不同的用法。双引号可以包含插入语句。插入是计算一个字符串包含placeholders的过程,并将placeholders的值替换,这些placeholder可以是变量甚至是方法。Placeholders必须包含一个方法或者变量,并且其被{}包围,且其前面有修饰。如果其只有一个单一的变量,可以只需要。下面是一些基本的用法:

def name = 'Andy'
def greeting = "Hello, $name!"
def name_size "Your name is ${name.size()} characters long."

greeting应该是“ Hello,Andy”,并且 name_size 为 Your name is 4 characters long.string的插入可以让你更好的动态执行代码。比如

 def method = 'toString'
 new Date()."$method"()

这在Java中看起来很奇怪,但是这在groovy里是合法的。

Classes和members

Groovy里面创建类和Java类似,举个例子:

class MyGroovyClass {
       String greeting
       String getGreeting() {
           return 'Hello!'
        } 
}

注意到不论是类名还是成员变量都没有修饰符。其默认的修饰符是类和方法为public,成员变量为private。

当你想使用MyGroovyClass,你可以这样实例化:

def instance = new MyGroovyClass()
instance.setGreeting 'Hello, Groovy!'
instance.getGreeting()   

你可以利用def去创建变量,一旦你为你的类创建了实例,你就可以操作其成员变量了。get/set方法groovy默认为你添加 。你甚至可以覆写它。

如果你想直接使用一个成员变量,你可以这么干:

 println instance.getGreeting()
 println instance.greeting

而这二种方式都是可行的。

方法

和变量一样,你不必定义为你的方法定义返回类型。举个例子,先看java:

public int square(int num) {
       return num * num;
} 
square(2);

你需要将该方法定义为public,需要定义返回类型,以及入参,最后你需要返回值。

我们再看下Groovy的写法:

 def square(def num) {
       num * num
 }
 square 4

没有了返回类型,没有了入参的定义。def代替了修饰符,方法体内没有了return关键字。然而我还是建议你使用return关键字。当你调用该方法时,你不需要括号和分号。

我们设置可以写的更简单点:

def square = { num ->
       num * num
}
square 8

###闭包

闭包是一段匿名的方法体,其可以接受参数和返回值。它们可以定义变量或者可以将参数传给方法。

你可以简单的使用方括号来定义闭包,如果你想详细点,你也可以这么定义:

Closure square = {
       it * it
}
square 16

添加了Closure,让其更加清晰。注意,当你没有显式的为闭包添加一个参数,groovy会默认为你添加一个叫做it。你可以在所有的闭包中使用it,如果调用者没有定义任何参数,那么it将会是null,这会使得你的代码更加简洁。

在grade中,我们经常使用闭包,例如Android代码体和dependencies也是。

Collections

在groovy中,有二个重要的容器分别是lists和maps。

创建一个list很容易,我们不必初始化:

List list = [1, 2, 3, 4, 5]

为list迭代也很简单,你可以使用each方法:

list.each() { element ->
       println element
}

你甚至可以使得你的代码更加简洁,使用it:

list.each() {
       println it
}

map和list差不多:

Map pizzaPrices = [margherita:10, pepperoni:12]

如果你想取出map中的元素,可以使用get方法:

pizzaPrices.get('pepperoni')
pizzaPrices['pepperoni']

同样的groovy有更简单的方式:

pizzaPrices.pepperoni

 


 


 

 

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值