Gradle脚本指南
LorenAXVJ
8年前
<ul> <li>便于复用代码和资源</li> <li>便于创建应用中的多种变量, 用于多渠道分发apk</li> <li>便于配置, 扩展和自定义构建过程</li> <li>良好的IDE整合性</li> </ul> <h2>为什么选用Gradle?</h2> <p>Gradle是一种高级构建系统, 同时也是一个允许通过插件创建自定义构建逻辑的构建工具. 以下是选择的原因:</p> <ul> <li>基于Groovy的Domain Specifc Language(DSL), 用于描述和操作构建逻辑</li> <li>构建文件是基于Groovy的, 并允许通过DSL混合声明元素, 并使用代码操作DSL元素来提供自定义逻辑.</li> <li>通过Maven或Ivy内置依赖管理</li> <li>非常弹性. 允许使用最佳实践但不强制</li> <li>插件提供了DSL和API, 可供构建文件使用</li> <li>允许IDE整合和工具API</li> </ul> <h2>要求</h2> <ul> <li>Gradle 2.2</li> <li>带有Build Tools 19.0.0的SDK. 某些功能要求最新版本</li> </ul> <h2>基本项目配置</h2> <p>Gradle使用项目根目录下的 <em>build.gradle</em> 文件描述构建过程. (参见 <a href="/misc/goto?guid=4958966551741763510" rel="nofollow,noindex">Gradle User Guide</a> )</p> <h3>简单的构建文件</h3> <p>最简单的Android项目有以下 build.gradle :</p> <pre> <code class="language-xml">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" } </code></pre> <p>其中有3个主要部分:</p> <p>buildscript {...} 配置了驱动构建的代码. 在本例中, 它内部生命了所使用的jCenter仓库, 和一个Maven artifact依赖的classpath. 该artifact是包含用于Android的1.3.1版本的Gradle插件的库. 注意: 这只会影响执行构建的代码, 而不会影响项目. 该项目需要声明自身的仓库和依赖. 后面会讲到.</p> <p>然后, 应用了 com.android.application . 这是构建Android应用的插件.</p> <p>最后, android {...} 配置了android构建的所有参数. 这是Android DSL的入口. 默认情况下, 只需要编译目标和构建工具的版本. 这通过 compileSdkVersion 和 buildtoolsVersion 属性来完成. 编译目标与 <em>project.properties</em> 中的 target 属性一致. 该属性可以赋值为int(api级别)或与 target 相同的字符串.</p> <p>重要:你应该只使用 com.android.application 插件. 使用 java 插件会导致构建错误.</p> <p>注意:你还需要一个 <em>local.properties</em> 文件来设置SDK的路径, 使用 sdk.dir 属性.</p> <p>或者你可以设置一个环境变量, 命名为 ANDROID_HOME .</p> <p>以上两种方法没有本质区别, 你可以使用任意一种. <em>local.properties</em> 示例文件:</p> <pre> <code class="language-xml">sdk.dir=/path/to/Android/Sdk </code></pre> <h3>项目结构</h3> <p>上面的基本构建文件用于默认的目录结构. Gradle遵循约定由于配置理念, 在可能的情况下会提供默认选项值. 基本项目会以两个叫做”source sets”的组件开始, 一个用于主要的源代码, 另一个用于测试代码. 目录如下:</p> <ul> <li>src/main/</li> <li>src/androidTest/</li> </ul> <p>每个目录内部还包含子目录. 对于java和Android插件, Java源代码和资源在如下目录:</p> <ul> <li>java/</li> <li>resources/</li> </ul> <p>对于Android插件, 会有如下额外的文件:</p> <ul> <li>AndroidManifest.xml</li> <li>res/</li> <li>assets/</li> <li>aidl/</li> <li>rs/</li> <li>jni/</li> <li>jniLibs/</li> </ul> <p>*.java 文件都位于 src/main/java , 手册文件位于 src/main/AndroidManifest.xml</p> <p>注意: src/androidTest/AndroidManifest.xml 会自动生成</p> <h3>配置项目结构</h3> <p>当默认项目结构不足以使用时, 可以对其进行配置. </p> <p>Android插件使用类似的语法, 但由于它使用了自己的 sourceSets , 因此需要在 android 代码块中配置. 以下是示例, 使用老的项目结构(在Eclipse中的)保存主要代码, 然后将 androidTest 的 sourceSet 重新映射到 tests 目录:</p> <pre> <code class="language-xml">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') } } </code></pre> <p>注意:由于老的项目结构将所有的源代码文件(Java, AIDL和RenderScript)都放在相同的目录, 我们需要将 sourceSet 中的所有新元素重新映射到相同的 src 目录下.</p> <p>注意: setRoot() 将整个 sourceSet (及其子目录)移动到了一个新的目录. 即将 src/androidTest/* 移动到了 test/* . 这是Android的标准, 不能用于Java sourceSets.</p> <h2>构建任务</h2> <h3>通用任务</h3> <p>在构建文件中配置一个插件可以自动生成一系列的构建任务. Java插件和Android插件都可以. 任务的约定如下:</p> <ul> <li>assemble<br> 组装项目的输出</li> <li>check<br> 运行所有检查</li> <li>build<br> 同时执行 assemble 和 check</li> <li>clean<br> 清空项目的输出</li> </ul> <p>assemble , check 和 build 实际不做任何事. 他们只是插件的”锚点”任务, 并添加了独立的任务来执行实际任务.</p> <p>这允许你总是调用相同的任务, 不论项目是何种类型或使用何种插件. 例如, 使用 findbugs 插件会创建一个新的任务来运行 check , 每次执行 check 任务时都会调用.</p> <p>对于命令行来说, 你可以运行如下命令来执行高级别任务:</p> <pre> <code class="language-xml">gradle tasks </code></pre> <p>如果想看完整任务列表和依赖, 可以运行如下命令:</p> <pre> <code class="language-xml">gradle tasks --all </code></pre> <p>注意:Gradle会自动监控任务声明的输入和输出</p> <p>在项目没有任何变化的情况下, 运行两次 build 任务时, Gradle会报告所有任务都是 UP-TO-DATE 的, 意味着不需要执行任何任务.</p> <h3>Java项目任务</h3> <p>以下是 java 插件两个最重要的任务:</p> <ul> <li>assemble <ul> <li>jar<br> 该任务创建输出</li> </ul> </li> <li>check <ul> <li>test<br> 该任务运行测试</li> </ul> </li> </ul> <p>jar 任务本身直接或间接的依赖于其他任务: 例如, classes 会编译java代码. 该测试使用 testClasses 进行编译, 但该命令没有什么用处, 因为 test 依赖于它(和 classes )</p> <p>总的来说, 你可能只需要调用 assemble 或 check , 并忽略其他任务. 你可以在 <a href="/misc/goto?guid=4959630029401106583" rel="nofollow,noindex">这里</a> 看到完整的Java插件任务描述</p> <h3>Android任务</h3> <p>Android插件使用相同的约定来保持同其他插件的兼容, 并增加了额外的锚点任务:</p> <ul> <li>assemble<br> 该任务组装项目的输出</li> <li>check<br> 该任务运行所有的检查</li> <li>connectedCheck<br> 在连接设备或模拟器的情况下运行检查. 会同时在所有已连接的设备上执行</li> <li>deviceCheck<br> 使用API连接远程设备运行检查. 用于CI服务器</li> <li>build<br> 该任务会执行 assemble 和 check</li> <li>clean<br> 该任务会清空项目的输出</li> </ul> <p>为了在不需要连接设备的情况下执行常规检查, 新的锚点任务是必要的. 注意 build 不依赖于 deviceCheck 或 connectedCheck</p> <p>一个Android项目至少有两种输出: 一个debug APK和一个release APK. 每种输出都有自己的锚点任务来分别执行构建:</p> <ul> <li>assemble <ul> <li>assembleDebug</li> <li>assembleRelease</li> </ul> </li> </ul> <p>他们都依赖于其他任务. assemble 任务同时依赖于这两个任务, 因此运行该指令会构建两种APK.</p> <p>提示:Gradle支持驼峰式的任务名称简写, 例如:</p> <pre> <code class="language-xml">gradle aR </code></pre> <p>与下面的命令相同</p> <pre> <code class="language-xml">gradle assembleRelease </code></pre> <p>只要没有其他任务匹配’aR’即可</p> <p>check锚点任务有自己的依赖:</p> <ul> <li>check <ul> <li>lint</li> </ul> </li> <li>connectedCheck <ul> <li>connectedAndroidTest</li> </ul> </li> <li>deviceCheck <ul> <li>依赖于其他实现测试扩展点的插件</li> </ul> </li> </ul> <p>最后, 插件会创建任务来安装和卸载所有构建类型(debug, release, test), 例如</p> <ul> <li>installDebug</li> <li>installRelease</li> <li>uninstallAll <ul> <li>uninstallDebug</li> <li>uninstallRelease</li> <li>uninstallDebugAndroidTest</li> </ul> </li> </ul> <h2>基本构建自定义</h2> <p>Android插件提供了一个宽泛的DSL来在构建系统中直接自定义大部分的内容.</p> <h3>Manifest入口</h3> <p>通过DSL可以配置最重要的Manifest入口, 例如:</p> <ul> <li>minSdkVersion</li> <li>targetSdkVersion</li> <li>versionCode</li> <li>versionName</li> <li>applicationId </li> <li>testApplicationId (用于测试APK)</li> <li>testInstrumentationRunner</li> </ul> <p>示例:</p> <pre> <code class="language-xml">android { compileSdkVersion 23 buildToolsVersion "23.0.1" defaultConfig { versionCode 12 versionName "2.0" minSdkVersion 16 targetSdkVersion 23 } } </code></pre> <p>在构建文件中使用这些manifest属性的意义在于, 这些值可以动态设置. 例如, 你可以使用自定义逻辑从一个文件中读取版本名称:</p> <pre> <code class="language-xml">def computeVersionName(){ ... } android { compileSdkVersion 23 buildToolsVersion "23.0.1" defaultConfig { versionCode 12 versionName computeVersionName() minSdkVersion 16 targetSdkVersion 23 } } </code></pre> <p>注意:不要使用与作用域中已存在的getter方法冲突的方法名. 例如 defaultConfig {...} 会调用 getVersionName() 并自动使用 defaultConfig.getVersionName() 来替换自定义的方法.</p> <h2>Build Type</h2> <p>默认情况下, Android插件会自动为项目配置debug和release版本. 两者的区别主要在于是否可以在安全的(非工程机)设备上调试应用, 以及APK的签名细节. debug版本使用自动生成的已知的名称/密码(避免在构建过程中输入密码)来进行签名. release版本在构建过程中不进行签名, 签名需要放在构建之后.</p> <p>配置通过 BuildType 对象来完成. 默认情况下, 会创建两个实例, 一个 debug 一个 release . Android插件允许自定义这两个实例, 或者创建其他的 <em>Build Type</em> . 可以通过以下的 buildTypes DSL容器实现:</p> <pre> <code class="language-xml">android { buildTypes { debug { applicationIdSuffix ".debug" } jnidebug { initWith(buildTypes.debug) applicationIdSuffix ".jnidebug" jniDebuggable true } } } </code></pre> <p>上面的代码实现了如下功能:</p> <ul> <li>配置了默认的 debug 构建类型: <ul> <li>设置包名为 <app applicationId>.debug 以便在同一个设备上同时安装debug和release的apk</li> </ul> </li> <li>创建了一个新的构建类型 jnidebug 并使用了 debug 构建模式</li> <li>继续配置 jnidebug , 启用了JNI组件的debug, 并添加了一个不同的包名前缀.</li> </ul> <p>创建一个新的 <em>BuildType</em> 和在 buildTypes 容器中创建一个新元素一样简单. 可以调用 initWith() 或用括号来配置. 参见 <a href="/misc/goto?guid=4959650644723764814" rel="nofollow,noindex">DSL参考手册</a> 来了解可用于配置构建类型的所有属性.</p> <p>如果需要修改构建属性, <em>BuildType</em> 可以添加某些代码或资源. 对于每种构建类型, 都会创建一个与之匹配的 <em>sourceSet</em> , 默认路径在 src/<buildtypename>/ , 例如 src/debug/java 目录只能添加debug APK所用的资源. 这意味着 <em>BuildType</em> 名称不能使用 <em>main</em> 或 <em>androidTest</em> (这是插件强制设置的), 并且名称必须唯一.</p> <p>和其他资源目录一样, 构建类型资源目录页可以重新定义:</p> <pre> <code class="language-xml">android { sourceSets.jnidebug.setRoot('foo/jnidebug') } </code></pre> <p>此外, 对于每种 <em>BuildType</em> , 都会创建一个新的 assemble<BuildTypeName> 任务, 例如 assembleDebug . 这就是之前提到的 assembleDebug 和 assembleRelease 的来源. 当 debug 和 release 构建类型已经创建的情况下, 他们的任务也会自动被创建. 根据这一规则, 上面的 <em>build.gradle</em> 规则会生成一个 assembleJnidebug 任务, 并且 assemble 会依赖于该任务.</p> <p>提示:记住你可以输入 gradle aJ 来运行 assembleJnidebug 任务</p> <p>可能的用例:</p> <ul> <li>某些权限只用于debug模式, 在release模式没有</li> <li>自定义实现调试</li> <li>debug模式使用不同的资源 (例如某些资源的值与签名绑定)</li> </ul> <p><em>BuildType</em> 的代码/资源可以通过如下方式使用:</p> <ul> <li>将一个manifest合并进app的manifest</li> <li>作为其他资源目录的代码</li> <li>资源会覆盖主资源, 替换已有的值</li> </ul> <h2>签名配置</h2> <p>对一个应用签名有以下要求 </p> <ul> <li>一个keystore</li> <li>一个keystore密码</li> <li>一个key别名</li> <li>一个key密码</li> <li>存储类型</li> </ul> <p>路径, key名称, 密码和存储类型构成了一个 签名配置 . 默认情况下, debug 配置使用debug keystore, 它使用的是已知的密码和默认的key.</p> <p>debug keystore位于 $HOME/.android/debug.keystore , 如果没有的话会自动创建. debug 构建类型默认使用 debug 的 SigningConfig .</p> <p>可以创建其他配置或自定义默认的配置. 通过 signingConfigs DSL容器来配置:</p> <pre> <code class="language-xml">android { signingConfigs { debug { storeFile file("debug.keystore") } myConfig { storeFile file("other.keystore") storePassword "android" keyAlias "androiddebugkey" keyPassword "android" } } buildTypes { foo { signingConfig signingConfigs.myConfig } } } </code></pre> <p>上面的代码将debug keystore的路径修改为项目的根目录. 这会自动影响所有使用该配置的的 <em>Build Type</em> , 在本例中就是 debug 构建类型. 该代码同时会创建一个新的 <em>Signing Config</em> , 并且新的构建类型会使用这个配置.</p> <p>注意:只有默认路径下的debug keystore会被自动创建. 修改debug keystore路径不会生效. 使用其他名称在默认debug keystore路径创建 <em>SigningConfig</em> 可以被自动创建. 换句话说, 是否生成与keystore的路径有关, 而与配置的名称无关.</p> <p>注意:keystore的路径通常相对于项目的根目录, 但也可以使用绝对路径, 虽然这种方式是不推荐的(除了debug的, 因为会被自动创建)</p> <p>注意:如果你通过版本控制检出这些文件, 你可能不希望文件中出现密码. 这篇Stack Overflow文章 展示了如果从命令行, 或环境变量中读取值.</p> <h2>依赖, Android库和多项目设置</h2> <p>Gradle项目可以依赖于其他组件. 这些组件可以是外部二进制包, 或者其他Gradle项目.</p> <h3>二进制包依赖</h3> <p>本地包</p> <p>配置依赖于外部库的jar, 你需要在 compile 配置中添加依赖. 以下代码在 libs 目录下添加了对所有jar的依赖:</p> <pre> <code class="language-xml">dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) } android { ... } </code></pre> <p>注意: dependencies DSL元素是标准Gradle API的一部分, 并且不属于 android 元素之内</p> <p>compile 配置用于编译主应用. 编译配置中的所有内容都会被加入编译的classpath <strong>和</strong> 最终的APK. 还有其他可能的配置可用于添加依赖:</p> <ul> <li>compile : 主应用</li> <li>androidTestCompile : 测试应用</li> <li>debugCompile : debug构建类型</li> <li>releaseCompile : release构建类型</li> </ul> <p>由于不使用 <em>Build Type</em> 构建APK时不可能的, 所以APK通常会配置两种或多种配置: compile 和 <buildtype>Compile . 创建一个新的 <em>Build Type</em> 会自动根据他的名字创建一个新的配置. 这在debug版本使用一个自定义库(比如报告崩溃)而release版本不需要使用该库的情况下, 或是他们依赖于不同版本的库的情况下很有用. (参见 <a href="/misc/goto?guid=4959733516423296370" rel="nofollow,noindex">Gradle文档</a> )</p> <p>远程artifact</p> <p>Gradle支持从Maven或Ivy仓库拉取artifact. 首先必须将仓库添加到列表中, 然后必须按照Maven或Ivy方式声明依赖.</p> <pre> <code class="language-xml">repositories { jcenter() } dependencies { compile 'com.google.guava:guava:18.0' } android { ... } </code></pre> <p>注意: jcenter() 是指定仓库URL的快捷方式. Gradle支持远程和本地仓库.</p> <p>注意:Gradle会跟进所有依赖. 也就是说如果一个依赖有他自己的其他依赖, 这些依赖也会被拉取.</p> <h3>多项目设置</h3> <p>Gradle项目可以使用多项目设置同时依赖于其他Gradle项目. 多项目配置可以通过将所有项目作为制定根项目的子目录来实现. 例如, 有如下结构:</p> <pre> <code class="language-xml">MyProject/ + app/ + libraries/ + lib1/ + lib2/ </code></pre> <p>我们可以看到有3个项目. Gradle可以通过以下名称来引用他们:</p> <pre> <code class="language-xml">:app :libraries:lib1 :libraries:lib2 </code></pre> <p>每个项目都有有自己的 <em>build.gradle</em> 来声明如何进行构建. 此外, 在项目根目录还会有一个叫做 <em>settings.gradle</em> 的文件来声明项目. 结构如下:</p> <pre> <code class="language-xml">MyProject/ | settings.gradle + app/ | build.gradle + libraries/ + lib1/ | build.gradle + lib2/ | build.gradle </code></pre> <p>settings.gradle的内容很简单. 它定义了哪个目录是一个Gradle项目:</p> <pre> <code class="language-xml">include ':app', ':libraries:lib1', ':libraries:lib2' </code></pre> <p>:app 项目可能会依赖某些库, 可以通过如下的声明实现:</p> <pre> <code class="language-xml">dependencies { compile project(':libraries:lib1') } </code></pre> <h3>库项目</h3> <p>在上面的多项目设置中, :libraries:lib1 和 :libraries:lib2 可以是Java项目, :app Android项目可以使用它们的 <em>jar</em> 文件. 但是, 如果你想共享Android API或使用Android风格的资源, 这些库不能使用普通的Java项目, 必须使用Android库项目.</p> <h3>创建一个库项目</h3> <p>一个库项目同普通Android项目相似, 但有一些区别. 由于构建库与构建应用不同, 所以会使用另外一种插件. 在内部两种插件会共享大部分相同的代码, 他们都是由同一个 com.android.tools.build.gradle jar文件提供的.</p> <pre> <code class="language-xml">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" } </code></pre> <p>以上代码创建了一个库项目, 并使用API 23来编译. <em>SourceSets</em> , <em>build types</em> 和依赖库的处理方式都是相同的, 因为他们在同一个应用项目, 并通过相同的方式进行自定义.</p> <h3>项目和库项目的区别</h3> <p>库项目的主要输出是一个 .aar包 (即android archive缩写). 它是编译后的代码(例如jar文件或.so文件)和资源(manifest, res, assets). 一个库项目可以生成一个测试apk来单独测试一个库. 它会使用相同的锚点任务( assembleDebug , assembleRelease ), 因此使用的命令并没有什么区别. 除此之外, 库也可以表现的同应用项目一样. 库拥有构建类型和渠道, 并可以潜在生成多于一种版本的aar. 注意, 大部分的构建类型配置不适用于库项目. 但是你可以使用自定义 <em>sourceSet</em> 来在测试和非测试的情况下动态改变库所依赖的内容.</p> <h3>引用库</h3> <p>引用一个库和引用其他项目一样:</p> <pre> <code class="language-xml">dependencies { compile project(':libraries:lib1') compile project(':libraries:lib2') } </code></pre> <p>注意:如果你有一个以上的库, 那么库的顺序会很重要.</p> <h2>库的发布</h2> <p>默认情况下, 一个库只会发布他的 <em>release</em> 变量. 该变量可以被整个项目使用来引用这个库. 无论他们构建的是何种变量. 这是Gradle自身限制产生的一个临时限制, 我们正努力去除该限制. 你可以控制要发布何种变量:</p> <pre> <code class="language-xml">android { defaultPublishConfig "debug" } </code></pre> <p>注意这个发布配置名称引用了完整的变量名. <em>release</em> 和 <em>debug</em> 仅适用于没有渠道的情况. 如果你希望在使用渠道时改变默认发布变量, 你可以这样写:</p> <pre> <code class="language-xml">android { defaultPublishConfig "flavor1Debug" } </code></pre> <p>发布一个库的所有变量也是可以的. 我们计划对普通的项目对项目的依赖方式使用这种方式(例如上例), 但目前由于Gradle限制我们还无法做到(我们正在努力修复)</p> <p>默认情况下, 发布所有变量是禁用的. 以下代码可以启用该功能:</p> <pre> <code class="language-xml">android { publishNonDefault true } </code></pre> <p>发布多个变量意味着会发布多个aar文件, 而不是一个aar含有多个变量, 理解这点很重要. 每个aar包都含有一个单独的变量. 发布一个变量意味着将这个aar作为Gradle项目的输出. 这可以用于发布到maven仓库, 或者用于其他项目的依赖.</p> <p>Gradle有一个概念是”默认artifact”. 可以通过如下写法实现:</p> <pre> <code class="language-xml">dependencies { compile project(':libraries:lib2') } </code></pre> <p>若要创建一个依赖于另一个已发布的artifact, 你需要指定具体使用哪一个:</p> <pre> <code class="language-xml">dependencies { flavor1Compile project(path: ':lib1', configuration: 'flavor1Release') flavor2Compile project(path: ':lib1', configuration: 'flavor2Release') } </code></pre> <p>重要:注意发布配置是一个完整变量, 包含了构建类型</p> <p>重要:当启用非默认发布时, Maven发布插件会将这些额外变量作为额外包进行发布. 也就是说该方式并不能完全兼容maven仓库的发布. 你应该发布单独变量到仓库, 或是对所有内部项目依赖都设置相同的配置.</p> <h2>测试</h2> <p>构建测试应用的功能已经集成到了应用项目. 目前不再需要创建单独的测试项目.</p> <h3>单元测试</h3> <p>在1.1中加入了单元测试的支持, 本节余下内容描述了构建一个单独测试APK并在真机(或模拟器)上使用”instumentation测试”.</p> <h3>基础和配置</h3> <p>正如之前提到的, main 目录下面是 androidTest 目录, 默认位于 src/androidTest/ .</p> <p>@todo</p> <h3>解决main apk和test apk的冲突</h3> <p>@todo</p> <h3>运行测试</h3> <p>如之前所说, check需要一个已连接的设备才能执行, 它通过一个叫做 connectedCheck 的锚点任务来执行. 这基于 connectedDebugAndroidTest 任务. 该任务执行以下内容:</p> <ul> <li>保证app和测试app被构建 (基于 assebleDebug 和 assebleDebugAndroidTest )</li> <li>安装两个app</li> <li>运行测试</li> <li>卸载两个app</li> </ul> <p>如果连接了多个设备, 所有测试会在所有已连接设备上并行运行. 如果任一个设备上的任一测试失败, 整个构建都会失败</p> <h3>测试Android库</h3> <p>@todo</p> <h3>测试报告</h3> <p>当运行单元测试时, Gradle输出一个HTML报告来查看结果. Android插件根据此构建并扩展了HTML报告, 使其汇总所有连接设备的结果. 所有的测试结果存储在 build/reports/androidTests/ . 可以通过如下代码配置输出路径:</p> <pre> <code class="language-xml">android { ... testOptions { resultsDir = "${project.buildDir}/foo/results" } } </code></pre> <p>android.testOptions.resultsDir 的值通过 Project.file(String) )来获得</p> <h3>多项目报告</h3> <p>@todo</p> <h3>Lint支持</h3> <p>你可以为具体的variant运行lint, 例如 ./gradlew lintRelease , 也可以为所有variant运行lint, 例如 ./gradlew lint . 单独运行则产生单独的报告. 你可以添加lintOptions部分来配置lint. </p> <pre> <code class="language-xml">android { lintOptions { // 关闭指定问题的检查 disable 'TypographyFractions','TypographyQuotes' // 开启指定问题的检查 enable 'RtlHardcoded','RtlCompat', 'RtlEnabled' // 只检查指定的问题 check 'NewApi', 'InlinedApi' } } </code></pre> <h2>Build Variant</h2> <p>新构建系统的一个目的是为同一个应用创建不同的版本</p> <p>主要有两种用例:</p> <ol> <li>相同应用的不同版本<br> 例如, 一个免费/演示版本和一个”高级”付费版本</li> <li>相同应用在Google Play商店中的多apk打包<br> 参见 多apk</li> <li>以上两种情况的结合</li> </ol> <p>我们的目标是可以用相同的项目生成不同的apk.</p> <h3>Product Flavor</h3> <p>product flavor定义了项目构建的自定义版本. 一个单独项目可以有多种flavor, 它可以改变所生成的应用</p> <p>这种设计的目的是用于产生最小的区别. 如果问你”这些是相同的应用吗?”, 答案是”是”的话, 那么应该使用库项目.</p> <p>Product flavor使用 productFlavors DSL容器来声明:</p> <pre> <code class="language-xml">android { .... productFlavors { flavor1 { ... } flavor2 { ... } } } </code></pre> <p>这会创建两种flavor, 名为 flavor1 和 flavor2 .</p> <p>注意:flavor的名称不能与已存在的 <em>Build Type</em> 名称, 或是 androidTest , test 重复.</p> <h3>Build Type + Product Flavor = Build Variant</h3> <p>正如我们之前所见, 每个构建类型都会生成一个新的apk. Product Flavor也有同样作用: 项目的输出会是所有 <em>Build Type</em> 和 <em>product flavor</em> 的组合. 形成的组合叫做 <em>Build Variant</em> . 例如, 如果使用默认的 debug 和 release 构建类型, 上面的例子会生成以下Build Variant:</p> <ul> <li>Flavor1 - debug</li> <li>Flavor1 - release</li> <li>Flavor2 - debug</li> <li>Flavor2 - release</li> </ul> <p>没有flavor的项目也有Build Variant, 它会默认使用 default flavor.</p> <h3>Product Flavor配置</h3> <p>每种flavor使用单独的括号配置:</p> <pre> <code class="language-xml">android { ... defaultConfig { minSdkVersion 8 versionCode 10 } productFlavors { flavor1 { applicationId "com.example.flavor1" versionCode 20 } flavor2 { applicationId "com.example.flavor2" minSdkVersion 14 } } } </code></pre> <p>注意 android.productFlavors.* 对象和 android.defaultConfig 对象都是 ProductFlavor 类型的, 也就是说他们会共享相同的属性.</p> <p>defaultConfig 为所有flavor提供了基本配置, 每种flavor可重写任意值. 在上面的例子中, 最终的配置结果是这样的:</p> <ul> <li>flavor1 <ul> <li>applicationId : com.example.flavor1</li> <li>minSdkVersion : 8</li> <li>versionCode : 20</li> </ul> </li> <li>flavor2 <ul> <li>applicationId : com.example.flavor2</li> <li>minSdkVersion : 14</li> <li>versionCode : 10</li> </ul> </li> </ul> <p>通常情况下, Build Type配置会覆盖其他的配置. 例如, Build Type的 applicationIdSuffix 会添加到Product Flavor的 applicationId 之后. 这适用于某种配置在Build Type和Product Flavor都适用的情况. 这种情况需要具体分析. 例如, signingConfig 是其中一个可以同时配置的属性. 它既可以通过设置 android.buildTypes.release.signingConfig 为所有release包共享相同的 SigningConfig , 也可以通过为每一个release包配置单独的 android.productFlavors.*.signingConfig 对象来分别使用各自的 SigningConfig .</p> <h3>SourceSet和Dependency</h3> <p>与 BuildType 相同, Product Flavor 也会通过 sourceSet 使用自己的代码和资源. 上面的例子创建了4个 sourceSet :</p> <ul> <li>android.sourceSets.flavor1<br> 位于 src/flavor1/</li> <li>android.sourceSets.flavor2<br> 位于 src/flavor2</li> <li>android.sourceSets.androidTestFlavor1<br> 位于 src/androidTestFlavor1</li> <li>android.sourceSets.androidTestFlavor2<br> 位于 src/androidTestFlavor2</li> </ul> <p>这些 sourceSet 会被构建的apk使用. 在构建一个单独的apk时, 以下规则会被使用:</p> <ul> <li>所有的源代码( src/*/java )都在一起, 因为所有目录都会生成同一个输出</li> <li>所有Manifest都会合并入一个单独的Manifest中. 这使得 <em>Product Flavor</em> 可以拥有不同的组件和权限, 与 <em>Build Type</em> 相似</li> <li>所有资源(res和assets)都使用覆盖优先级, <em>Build Type</em> 会覆盖 <em>Product Flavor</em> , <em>Product Flavor</em> 会覆盖 main sourceSet.</li> <li>每种 <em>Build Variant</em> 会根据资源生成自己的R类. Variant之间不会共享.</li> </ul> <p>最后, 与 <em>Build Type</em> 类似, <em>Product Flavor</em> 可以有他们自己的依赖. 例如, 如果使用flavor来分别生成一个广告和一个付费app, 其中一个flavor可以设置一个广告sdk, 另一个则不需要.</p> <pre> <code class="language-xml">dependencies { flavor1Compile "..." } </code></pre> <p>在这个场景下, 文件 src/flavor1/AndroidManifest.xml 可能需要加入网络权限.</p> <p>还会为每个Variant创建额外的serceset:</p> <ul> <li>android.sourceSets.flavor1Debug<br> 位于 src/flavor1Debug/</li> <li>android.sourceSets.flavor1Release<br> 位于 src/flavor1Release/</li> <li>android.sourceSets.flavor2Debug<br> 位于 src/flavor2Debug/</li> <li>android.sourceSets.flavor2Release<br> 位于 src/flavor2Release/</li> </ul> <p>以上这些比build type的sourceSet拥有更高的优先级, 并允许在variant级别进行自定义</p> <h2>构建任务</h2> <p>每个 <em>Build Type</em> 都会创建自己的 assemble<name> 任务, 但 <em>Build Variant</em> 是 <em>Build Type</em> 和 <em>Product Flavor</em> 的结合</p> <p>当使用 <em>Product Flavor</em> 时, 会创建更多assemble类型的任务. 有如下这下:</p> <ul> <li>assemble<Variant Name></li> <li>assemble<Build Type Name></li> <li>assemble<Product Flavor Name></li> </ul> <p>第一个允许直接构建单独的variant. 例如 assembleFlavor1Debug</p> <p>第二个允许根据给定的Build Type构建所有APK. 例如 assembleDebug 会构建 Flavor1Debug 和 Flavor2Debug variant</p> <p>第三个匀速根据指定flavor构建所有apk. 例如 assembleFlavor1 会构建 Flavor1Debug 和 Flavor1Release variant.</p> <p>assemble 任务会构建所有可能的variant.</p> <h3>多flavor variant</h3> <p>在某些情况下, 你可能想要为同一个app根据多种要求创建多种版本的app.</p> <p>例如, 在Google Play所支持的multi-apk可以支持4中不同的过滤器.</p> <p>为每种过滤器创建不同的apk可以通过使用多种Product Flavor实现.</p> <p>假设一个游戏会有演示版本和付费版本, 并且希望使用multi-apk支持的ABI锅炉汽. 3种ABI和2个版本的应用, 需要生成6个apk</p> <p>然而, 付费版本的代码对于3种ABI是相通的, 所以简单的创建6个flavor是不合适的.</p> <p>此外, 现在有2种flavor, variant应该自动构建所有可能的组合.</p> <p>这一功能可以通过Flavor Dimension实现. Flavor被指定为一个具体的dimension:</p> <pre> <code class="language-xml">android { ... flavorDimensions "abi", "version" productFlavors { freeapp { dimension "version" ... } paidapp { dimension "version" ... } arm { dimension "abi" ... } mips { dimension "abi" ... } x86 { dimension "abi" ... } } } </code></pre> <p>android.flavorDimensions 数组定义了可能的dimension, 同时也定义了顺序. 每个定义的 <em>Product Flavor</em> 都被指定给了一个dimension</p> <p>通过以下dimension定义的 <em>Product Flavor</em> [freeapp, paidapp] 和[x86, arm, mips]和[debug, release] <em>Build Type</em> , 会创建以下的build variant:</p> <ul> <li>x86-freeapp-debug</li> <li>x86-freeapp-release</li> <li>arm-freeapp-debug</li> <li>arm-freeapp-release</li> <li>mips-freeapp-debug</li> <li>mips-freeapp-release</li> <li>x86-paidapp-debug</li> <li>x86-paidapp-release</li> <li>arm-paidapp-debug</li> <li>arm-paidapp-release</li> <li>mips-paidapp-debug</li> <li>mips-paidapp-release</li> </ul> <p>android.flavorDimensions 定义的dimension的顺序非常重要.</p> <p>每个variant通过多个 <em>Product Flavor</em> 对象进行配置:</p> <ul> <li>android.defaultConfig</li> <li>abi dimension中定义的一个</li> <li>version dimension中定义的一个</li> </ul> <p>dimension的顺序会决定哪个flavor会覆盖其他flavor, 当flavor中的某个值替换了低级别flavor中的值的情况下, 对于resource的影响还是比较重要的.</p> <p>先定义的flavor具有更高的优先级. 所以在本例中:</p> <p>abi > version > defaultConfig</p> <p>多flavor项目同时会有额外的sourceset, 与variant sourceset类似, 但不会包括build type:</p> <ul> <li>android.sourceSets.x86Freeapp<br> 位于 src/x86Freeapp/</li> <li>android.sourceSets.armPaidapp<br> 位于 src/arcPaidapp/</li> <li>etc…</li> </ul> <p>这允许在flavor组合级别进行自定义. 他们比基本的flavor sourceset用用更高的优先级, 但低于build type sourceset的优先级.</p> <h3>测试</h3> <p>@todo</p> <h3>BuildConfig</h3> <p>在编译时期, Android Studio会生成一个叫做 BuildConfig 的类, 它包含了构建具体variat的常量值. 你可以在不同的variant中通过检查这些常亮来改变行为, 例如:</p> <pre> <code class="language-xml">private void javaCode(){ if (BuildConfig.FLAVOR.equals("paidapp")) { doIt(); } else { showOnlyInPaindAppDialog(); } } </code></pre> <p>以下是BuildConfig包含的值:</p> <ul> <li>boolean DEBUG : 如果构建时可调式的</li> <li>int VERSION_CODE</li> <li>String VERSION_NAME</li> <li>String APPLICATION_ID</li> <li>String BUILD_TYPE : build type的名称, 例如”release”</li> <li>String FLAVOR : flavor名称, 例如: “paidapp”</li> </ul> <p>如果项目使用flavor dimension, 会生成额外的值:</p> <ul> <li>String FLAVOR = "armFreeapp"</li> <li>String FLAVOR_abi = "arm"</li> <li>String FLAVOR_version = "freeapp"</li> </ul> <h3>过滤Variant</h3> <p>当你增加了dimension和flavor, 你可能会创建一些没有意义的variant. 例如, 你可能定义了一个flavor来使用web api, 另一个flavor使用写死的假数据用于快速测试. 第二种flavor只在开发时有用. 你可以使用 variantFilter 闭包来删除这个variant:</p> <pre> <code class="language-xml">android { productFlavors { realData fakeData } variantFilter { variant -> def names = variant.flavors*.name if (names.contians("fakeData") && variant.buildType.name == "release") { variant.ignore = true } } } </code></pre> <p>通过以上配置, 你的项目只有3个variant:</p> <ul> <li>realDataDebug</li> <li>realDataRelease</li> <li>fakeDataDebug</li> </ul> <h2>高级构建自定义</h2> <h3>运行ProGuard</h3> <p>ProGuard插件在Android插件中会自动应用, 如果 <em>Build Type</em> 通过配置 <em>minifyEnabled</em> 属性启用了ProGuard后, 任务会自动创建.</p> <pre> <code class="language-xml">android { buildTypes { release { minifyEnalbled true proguardFile getDefualtProguardFile('proguard-android.txt') } } productFlavors { flavor1 { } flavor2 { proguardFile 'some-other-rules.txt' } } } </code></pre> <p>Variant会使用build type和product flavor中声明的所有规则文件</p> <p>有2个默认的规则文件:</p> <ul> <li>proguard-android.txt</li> <li>proguard-android-optimize.txt</li> </ul> <p>他们位于SDK中. 使用 getDefualtProguardFile() 可以返回文件的完整路径.</p> <h3>压缩资源</h3> <p>你可以在构建时自动删除没有使用的资源文件. </p> <h3>操作任务</h3> <p>基本的Java项目有多个任务一起工作来创建一个输出.</p> <p>classes 任务用于编译Java源代码. 通过在脚本中使用 classes 可以方便地访问 <em>build.gradle</em> . 这是 project.tasks.classes 的快捷方式</p> <p>在Android项目中, 这可能会复杂一些. 因为可能会有很大数量的相同任务, 并且他们的名字是根据 <em>Build Type</em> 和 <em>Product Flavor</em> 来生成的.</p> <p>为了修复这一问题, 在 android 对象中有两个属性:</p> <ul> <li>applicationVariants (只用于app插件)</li> <li>labraryVariants (只用于library插件)</li> <li>testVariants (用于两种插件)</li> </ul> <p>他们会返回 ApplicationVariant , LibraryVariant , 和 TestVariant 相应的 DomainObjectColletion</p> <p>注意, 访问任意的collection都会触发所有任务的创建. 也就是说在访问collection后不能再进行配置.</p> <p>DomainObjectCollection 为所有项目提供了直接访问或通过过滤器访问的方式</p> <pre> <code class="language-xml">android.applicationVariants.all { variant -> ... } </code></pre> <p>三种variant类都共享以下属性:</p> <table> <thead> <tr> <th>属性名</th> <th>属性类型</th> <th>描述</th> </tr> </thead> <tbody> <tr> <td>name</td> <td>String</td> <td>variat名称. 保证唯一.</td> </tr> <tr> <td>description</td> <td>String</td> <td>人类可读的variant描述.</td> </tr> <tr> <td>dirName</td> <td>String</td> <td>variant子目录名称. 保证唯一. 可能会有多个目录, 例如“debug/flavor1”</td> </tr> <tr> <td>baseName</td> <td>String</td> <td>variant输出的基本名称. 保证唯一</td> </tr> <tr> <td>outputFile</td> <td>File</td> <td>variant输出. 是读/写属性</td> </tr> <tr> <td>processManifest</td> <td>ProcessManifest</td> <td>处理Manifest的任务.</td> </tr> <tr> <td>aidlCompile</td> <td>AidlCompile</td> <td>编译AIDL文件的任务.</td> </tr> <tr> <td>renderscriptCompile</td> <td>RenderscriptCompile</td> <td>编译Renderscript文件的任务.</td> </tr> <tr> <td>mergeResources</td> <td>MergeResources</td> <td>合并资源的任务</td> </tr> <tr> <td>mergeAssets</td> <td>MergeAssets</td> <td>合并assets的任务</td> </tr> <tr> <td>processResources</td> <td>ProcessAndroidResources</td> <td>处理和编译资源的任务</td> </tr> <tr> <td>generateBuildConfig</td> <td>GenerateBuildConfig</td> <td>生成BuildConfig类的任务</td> </tr> <tr> <td>javaCompile</td> <td>JavaCompile</td> <td>编译Java代码的任务</td> </tr> </tbody> </table> <p>processJavaResources|Copy|处理Java资源的任务|</p> <p>|assemble|DefaultTask|该variant的组装锚点任务|</p> <p>ApplicationVariant 类增加了如下属性:</p> <table> <thead> <tr> <th>属性名</th> <th>属性类型</th> <th>描述</th> </tr> </thead> <tbody> <tr> <td>buildType</td> <td>BuildType</td> <td>variant的BuildType</td> </tr> <tr> <td>productFlavors</td> <td>List</td> <td>variant的ProductFlavors. 可以为空单永不为null.</td> </tr> <tr> <td>mergedFlavor</td> <td>ProductFlavor</td> <td>android.defaultConfig和variant.productFlavors的合并</td> </tr> <tr> <td>signingConfig</td> <td>SigningConfig</td> <td>该variant使用的SigningConfig对象</td> </tr> <tr> <td>isSigningReady</td> <td>boolean</td> <td>true如果variant拥有所有签名需要的信息</td> </tr> <tr> <td>testVariant</td> <td>BuildVariant</td> <td>测试该variant的TestVariant</td> </tr> <tr> <td>dex</td> <td>Dex</td> <td>dex代码的任务. 如果variant是一个libaray则为null</td> </tr> <tr> <td>packageApplication</td> <td>PackageApplication</td> <td>构建最终apk的任务. 如果variant是一个library则为null</td> </tr> <tr> <td>zipAlign</td> <td>ZipAlign</td> <td>zipalign apk的任务. 如果variant是一个library或apk无法签名时为null</td> </tr> <tr> <td>install</td> <td>DefaultTask</td> <td>安装任务, 可以为null</td> </tr> <tr> <td>uninstall</td> <td>DefaultTask</td> <td>卸载任务</td> </tr> </tbody> </table> <p>LibraryVariant 类增加了以下属性:</p> <table> <thead> <tr> <th>属性名</th> <th>属性类型</th> <th>描述</th> </tr> </thead> <tbody> <tr> <td>buildType</td> <td>BuildType</td> <td>variant的BuildType</td> </tr> <tr> <td>mergedFlavor</td> <td>ProductFlavor</td> <td>defaultConfig值</td> </tr> <tr> <td>testVariant</td> <td>BuildVariant</td> <td>测试该variant的Build Variant</td> </tr> <tr> <td>packageLibrary</td> <td>Zip</td> <td>打包Library的arr文件的任务. 如果不是library时为null</td> </tr> </tbody> </table> <p>TestVariant 增加了以下属性:</p> <table> <thead> <tr> <th>属性名</th> <th>属性类型</th> <th>描述</th> </tr> </thead> <tbody> <tr> <td>buildType</td> <td>BuildType</td> <td>variant的BuildType</td> </tr> <tr> <td>productFlavors</td> <td>List</td> <td>variant的ProductFlavors可以为空但永远不为null</td> </tr> <tr> <td>mergedFlavor</td> <td>ProductFlavor</td> <td>android.defaultConfig和variant.productFlavors的合并</td> </tr> <tr> <td>signingConfig</td> <td>SigningConfig</td> <td>该variant使用的SigningConfig对象</td> </tr> <tr> <td>isSigningReady</td> <td>boolean</td> <td>true如果该variant有所有签名需要的信息</td> </tr> <tr> <td>testedVariant</td> <td>BaseVariant</td> <td>该TestVariant测试用的BaseVariant</td> </tr> <tr> <td>dex</td> <td>Dex</td> <td>dex代码的任务. 如果variant是一个library则为null.</td> </tr> <tr> <td>packageApplication</td> <td>PackageApplication</td> <td>构建最终apk的任务. 如果variant是一个library则为null</td> </tr> <tr> <td>zipAlign</td> <td>ZipAlign</td> <td>zipalign apk的任务. 如果variant是一个library或apk无法签名则为null</td> </tr> <tr> <td>install</td> <td>DefaultTask</td> <td>安装任务, 可以为null</td> </tr> <tr> <td>uninstall</td> <td>DefaultTask</td> <td>卸载任务</td> </tr> <tr> <td>connectedAndroidTest</td> <td>DefaultTask</td> <td>在已连接的设备上运行android测试的任务</td> </tr> <tr> <td>providerAndroidTest</td> <td>DefaultTask</td> <td>使用扩展API运行android测试的任务</td> </tr> </tbody> </table> <p>Android具体任务类型的API:</p> <ul> <li>ProcessManifest <ul> <li>File manifestOutputFile</li> </ul> </li> <li>AidlCompile <ul> <li>File sourceOutputDir</li> </ul> </li> <li>RenderscriptCompile <ul> <li>File sourceOutputDir</li> <li>File resOutputDir</li> </ul> </li> <li>MergeResources <ul> <li>File outputDir</li> </ul> </li> <li>MergeAssets <ul> <li>File outputDir</li> </ul> </li> <li>ProcessAndroidResources <ul> <li>File manifestFile</li> <li>File resDir</li> <li>File assetsDir</li> <li>File sourceOutputDir</li> <li>File textSymbolOutputDir</li> <li>File packageOutputFile</li> <li>File proguardOutputFile</li> </ul> </li> <li>GenerateBuildConfig <ul> <li>File sourceOutputDir</li> </ul> </li> <li>Dex <ul> <li>File outputFolder</li> </ul> </li> <li>PackageApplication <ul> <li>File resourceFile</li> <li>File dexFile</li> <li>File javaResourceDir</li> <li>File jniDir</li> <li>File outputFile <ul> <li>若要改变最终输出文件, 可在variant对象中直接调用”outputFile”</li> </ul> </li> </ul> </li> <li>ZipAlign <ul> <li>File inputFile</li> <li>File outputFile <ul> <li>若要改变最终输出文件, 可在variant对象中直接调用”outputFile”</li> </ul> </li> </ul> </li> </ul> <p>每种任务类型的api会由于Gradle工作方式和Android插件的设置而有所限制</p> <p>首先, Gradle只希望配置输入/输出位置和可能的可选标志. 所以在此任务只在输入/输出定义.</p> <p>第二, 大部分任务的输出都是不重要的, 它们大部分都来自 <em>srouceSets</em> , <em>Build Type</em> , <em>Product Flavor</em> 的混合值. 为了保持构建文件的简洁易懂, 我们的目标是让开发者通过DSL来修改构建, 而不是深入输出和任务选项来修改它们.</p> <p>注意, 除了ZipAlign任务, 其他所有任务类型都需要设置私有数据才能正常工作. 这意味着不能手动创建这些类型的任务.</p> <h3>设置语言级别</h3> <p>你可以使用 compileOptions 块来选择编译器的语言级别. 默认会使用 compileSdkVersion 的值</p> <pre> <code class="language-xml">android { compileOptions { sourceCompatibility JavaVersion.VERSION_1_6 targetCompatitility JavaVersion.VERSION_1_6 } } </code></pre> <p> </p> <p>来自:http://blog.lixplor.com/2016/12/15/android-gradle-script/</p> <p> </p>