Android Studio使用Gradle构建示例

本文介绍了Android Studio、Gradle和Groovy之间的关系,详细解析了Android Studio项目中的gradlew、build.gradle等文件作用,并展示了如何构建不同渠道的多个包。通过示例展示了Gradle的task使用,以及在build.gradle文件中配置构建变体,实现免费和付费版本的DEBUG、RELEASE构建。此外,还讨论了如何为不同的Flavor变体加入不同的资源和逻辑。

前言

最近遇到了问题,大概是 APPT2 ERROR 错误,这个错误很常见,说的是 .9图片 有问题,但是网上的回答都非常的零散和不够系统。编译的时候从 LOG终端 中也看不了太多信息。网上的建议是加编译参数 --stacktrace --debug ,所以就想着把 Gradle 构建系统详细了解下。
以下说的大多是学习总结,一些概念可能描述的不准确,更多信息请参考文中和文末的资料链接。

问题

  1. Android Studio, Gradle, Groovy 的关系
  2. Android Studio 生成的项目中 build.gradle , settings.gradle , gradle.properties , gradle-wrapper.properties 这些文件的作用
  3. 如何构建不同渠道的多个包
  4. 如何构建服务器地址不同的包,甚至逻辑不同的版本,而不是新开分支

回答

Android Studio, Gradle, Groovy 的关系
  • Groovy 是基于JVM的一门语言,类似于其他基于JVM的语言一样,如 JAVA , KOTLIN 等,最终生成JAVA字节码运行在JVM虚拟机上。 我们从 Groovy官网 中下载安装,并了解 语言特性

      println ("Groovy Demo")
      def num1 = 6
      num2 = 3
      
      ppp "你好","Groovy"
      println "$num1 的平方是 ${sqrt(num1)}"
      println "$num1 + $num2 = ${ add(num1,num2)}"
      println "平方求和 ${sqrtSum(num1, num2, {var1, var2->var1 + var2})}"
      
      
      def ppp(m1, m2) {
         println "$m1 $m2"
      }
      def sqrtSum(var1, var2, action) {
          return sqrt(action(var2, var2) )
      }
      def add(x, y) {
          x + y
      }
      def sqrt(x) {
          x * x
      }
      def mul(var1, var2) {
          return var1 - var2
      }
      def div = {
          int var1, int var2->
          var1 / var2
      }
    

    Groovy 了解个大概,有个概念差不多了。具体的可以参考官网。

  • Android Studio 编译时就是把整个构造任务委托给 Gradle 来处理,可以构建Android项目还有 Ant , Buck 等。 Gradle 可以说是脚本,或者是语言,又或者是一个平台。 Groovy 基于JVM,Gradle 基于 GroovyGradle 可以实现自动化打包,自动化测试,项目依赖管理等功能。 Gradle 基于 约定优先配置 原则,能够非常智能的知道该如果加载库,编译源码,加载资源,以及生成目标产物的最终位置。而配置的各种的插件,就是定义了一系列的规则。比如,java插件,android插件。

      apply plugin: 'com.android.application'
    

    Gradle 编译的时候是执行一系列的task,可以使用下列命令查看所有的task

      gradle task --all
    

    常用的有 assemble , assembleDebug , assembleRelease , build , clean
    下面的代码直观感受下,如何编写 build.gradle , 以及如何执行的。我们先新建文件夹 GradleDemo ,然后新建文件 build.gradle , 基本结构就好了。在 build.gradle 写上下面的代码:

      println "Gradle demo"
      
      
      task sayHello {
          println "hello from task 'sayHello'"
      }
    

    如果我们执行 gradle task --all 将会看到 sayHello 这个 task , 我们执行下

    gradle sayHello

    将会看到如下输出

    Gradle demo

    hello from task ‘sayHello’

    :sayHello UP-TO-DATE

    BUILD SUCCESSFUL

    Total time: 2.401 secs

    同理,我们执行 gradle -q assemble 的时候也就是执行 plugin 事先定义的复杂的 task assemble 进行构建。 显然加载不同的插件, Gradle 可以构建 java , ANDROID , C/C++ 等不同的项目。

Android Studio 生成的项目中 build.gradle , settings.gradle , gradle.properties , gradle-wrapper.properties 这些文件的作用
  • 我们先看 gradle-wrapper.properties 文件, 位于 project/app/gradle/wrapper 中,这个是用于 gradle 版本管理的,我们的不同项目现在都可以方便的指定 gradle版本

    distributionBase=GRADLE_USER_HOME

    distributionPath=wrapper/dists

    zipStoreBase=GRADLE_USER_HOME

    zipStorePath=wrapper/dists

    distributionUrl=https://services.gradle.org/distributions/gradle-3.3-all.zip

    如果在指定的目录中存在 3.3 的版本,就直接使用,如果不存在就去指定的服务器中下载。如果是下载的 gradle ,可以使用 gradle wrapper 命令来生成 wrapper ,它将会生成 .gradle , gradle , gradlew.bat , gradlew 等文件和文件夹。现在我们可以使用

      gradlew.bat --version
    

    查看下运行环境。它将显示 gradlegroovy , jvm 等使用版本。

  • 我们接着看 gradle.properties 文件,顾名思义这个是 gradle 配置属性值的地方。比如 jdk (org.gradle.java.home)位置,编译参数(org.gradle.jvmargs)等。具体可以查看 官网说明 build_environment。另外一种使用情况是定义一下统一的配置变量,比如 compileSdkVersionbuildToolsVersionsupportLibrary 等变量。

    举个例子:
    gradle.properties 中添加如下代码:

      greetingWord=Hi, groovy
    

    build.gradle 中添加一个 task

      task sayHi {
          println rootProject.buildDir
          println "sayHi word from file $greetingWord"
      }
    

    它将会打印出 Hi, groovy ,这是我们定义的值。

  • 我们接着看 settings.gradle 文件,这个文件是针对多项目的配置文件,比如这个 app 项目引入来了其它库,对于 gradle 来说,就是引入了 project , 这个文件使用 include 函数来说明了构建项目的 mul-project

  • 我们最后看app模块中的 build.gradle 文件,这个文件是单独对该 模块/PROJECT 进行配置的。

      apply plugin: 'com.android.application' //加载android 应用插件
      
      android {   //dsl android 配置部分
          compileSdkVersion 25            //这两行必须要有
          buildToolsVersion "26.0.2"      //这两行必须要有
          defaultConfig {
              applicationId "com.bbg.gradledemo"
              minSdkVersion 19
              targetSdkVersion 25
              versionCode 1
              versionName "1.0"
              testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
          }
          buildTypes {
              release {
                  minifyEnabled false
                  proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
              }
          }
      }
      
      dependencies {  //依赖
          compile fileTree(dir: 'libs', include: ['*.jar'])
          androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
              exclude group: 'com.android.support', module: 'support-annotations'
          })
          compile 'com.android.support:appcompat-v7:25.3.1'
          compile 'com.android.support.constraint:constraint-layout:1.0.2'
          testCompile 'junit:junit:4.12'
      }
    

    里面的代码块是什么意思,更具体的可以参考 官网说明

如何构建不同渠道的多个包

这个问题和接下来的问题,将涉及到两个主要概念,build typesproduct flavors ,这两个概念弄明白了,我们的问题也就差不多解决了。 build types 是什么? build types 是站在程序员的角度来说的,比如 debug , release 版本,它并不为终端用户所感知。而 product flavors 则可以理解为面向终端用户的,比如 免费版付费版 ,又比如各大应用市场的不同版本。

我们看个复杂的实例,构建不同渠道的免费和收费的DEBUG、RELEASE两种版本。这个例子分开来看的话,一共是3个阶段。第一阶段,构建 releasedebug 版本;第二阶段,构建 channel1channel2 两个不同渠道的版本;第三阶段,构建 freepay 收费模式版本。组合起来的话,一共 2*2*2 最终会构建成 8 版本。

buildTypes 构建apk类型,即 debugrelease ; productFlavors 构建用户感知的app变体,即不同的app; flavorDimensions 构建多维度的 flavor,可以理解为多维度的用户使用情形。比如该例子说的是,既要区分不同渠道,又要对不同渠道做 freepay 两种产品的区分。具体的写法参考下面的 build.gradle 中的代码。下面的代码也展示如何在 AndroidManifest.xml 中插入我们配置的值,以及如何在 build.gradle 中配置运行时变量。 在项目代码中还展示了如何读取 meta-data 的值和在 build.gradle 中配置的值。

当我们 sync 我们的项目时,IDE中的
Build Variants 选项卡也会出现对应的各种APP变体。更官方的 Build Variants 可以参考这里,而 build.gradleblock代码 块中的属性值或方法具体是什么意思或该怎么用,可以参考这里

下面是我们编译出来的共 8 个APP变体。 8 种不同的 flavors

app-channe2-free-debug.apk

app-channe2-free-release.apk

app-channe2-pay-debug.apk

app-channe2-pay-release.apk

app-channel-free-debug.apk

app-channel-free-release.apk

app-channel-pay-debug.apk

app-channel-pay-release.apk

//默认配置
defaultConfig {

	//应用包名,注意和 AndroidManifest.xml 中的 packageName 区别,
	//后者影响资源的R类的生成; 前者是唯一包名
    applicationId "com.bbg.gradledemo"			
						
    minSdkVersion 16
    targetSdkVersion 25
    versionCode 1
    versionName "1.0"
    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

	//manifest 占位符, map类型
	//buildConfigField 生成 BuildConfig 类的静态值,可以代码访问
	//
    manifestPlaceholders = ["KEY_TYPE": "KEY-default"]		
    buildConfigField("String", "var1", "\"var-default\"")	
															
}

//签名配置
signingConfigs {
    config {
        keyAlias 'key0'
        keyPassword '123456'
        storeFile file("$rootProject.rootDir/keystore.jks")
        storePassword '123456'
    }
}

//build types
buildTypes {
    release {
        minifyEnabled true
        debuggable true
        shrinkResources true
        zipAlignEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        signingConfig signingConfigs.config
        applicationIdSuffix ".release"
    }
    debug {
        minifyEnabled true
        debuggable true
        shrinkResources true
        zipAlignEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        signingConfig signingConfigs.config
        applicationIdSuffix ".debug"
    }
}

//多维度flavor解决方法,前后顺序影响生成的 variants 类型
//然后在product flavors 指定 deminsion 

flavorDimensions "channel", "mode"

productFlavors {
    channel {
        dimension "channel"
        manifestPlaceholders = ["KEY_TYPE": "KEY-channel"]
        buildConfigField("String", "var1", "\"var-channel\"")
    }
    channe2 {
        dimension "channel"
        manifestPlaceholders = ["KEY_TYPE": "KEY-channe2"]
        buildConfigField("String", "var1", "\"var-channe2\"")
    }

    free {
        dimension "mode"
        buildConfigField("String", "mode", "\"mode-free\"")
    }

    pay {
        dimension "mode"
        buildConfigField("String", "mode", "\"mode-pay\"")
    }
}

#####为不同的Flavor变体加入不同的资源和逻辑
从上面中的回答,我们可以知道,这种方式已经可以满足我们提出的要求了。一些不同的 Build Variants , 可以根据变量值,在代码中进行设置,进行不同的逻辑处理。 假如我们不同的 Variants 需要不一样的启动图标,甚至逻辑部分不同,有没有另外的解决方案。答案是有的,当我们在 build.gradle 中配置了 buildTypsproductFlavors 的时候,Android Studio 就逻辑关系将每个源代码和资源分组为 源集

Android Studio 按逻辑关系将每个模块的源代码和资源分组为源集。模块的 main/ 源集包括其所有构建变体共用的代码和资源。其他源集目录为可选项,在您配置新的构建变体时,Android Studio 不会自动为您创建这些目录。不过,创建类似于 main/ 的源集有助于让 Gradle 只应在构建特定应用版本时使用的文件和资源井然有序

如果不同的 源集 包含同一文件的不同版本,Gradle 将按以下优先顺序决定使用哪一个:

构建变体 > 构建类型 > 产品风味 > 主源集 > 库依赖项

我们实践下,我们把上面的 channe2 变体的应用图标修改,把第一次进入时候的是否 pay 的逻辑改成 free 。具体的代码请参考Github。我们创建文件时,可以借助 IDE 来选择不同的 源集。这样我们的 app 目录结构大概是这样:

```
app
|
└─src
    ├─androidTest
    │  └─java
    │      └─com
    │          └─bbg
    │              └─gradledemo
    ├─channe2
    │  └─res
    │      ├─mipmap-xhdpi
    │      └─values
    ├─free
    │  ├─java
    │  │  └─com
    │  │      └─bbg
    │  │          └─gradledemo
    │  └─res
    │      └─values
    ├─main
    │  ├─java
    │  │  └─com
    │  │      └─bbg
    │  │          └─gradledemo
    │  └─res
    │      ├─drawable
    │      ├─layout
    │      ├─mipmap-hdpi
    │      ├─mipmap-mdpi
    │      ├─mipmap-xhdpi
    │      ├─mipmap-xxhdpi
    │      ├─mipmap-xxxhdpi
    │      └─values
    ├─pay
    │  ├─java
    │  │  └─gradledemo
    │  └─res
    │      └─values
    └─test
        └─java
            └─com
                └─bbg
                    └─gradledemo


```

我们在 chane2 变体中引入 ic_launcher,在 payfree 变体中处理是否收费逻辑。编译的时候,可以把我们想要的都一起编译出来,实现自动化,简直完美。显然, gradle 知道该如何合并,我们可以参考这里,了解是 gradle 是如何处理合并的。

结尾

到这里,我们顺利的解决了提出的问题。发现 GRADLE 构建系统太出色了。这么好的工具,当然要用起来。期间查阅学习了较多资料,写成此文,希望对读者有帮助。文中有较多参考链接可以扩展查阅,最后末尾附上一些相关的参考补充资料。

Gradle 完整指南(Android)
深入理解Android(一):Gradle详解
构建配置
Groovy File IO Document
gradle 编译不过,Build可以问题解决方法
gradle实战
Android SDK构建视频
Android Gradle
Build Type, Flavour and Build Variant

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值