Android 开发必备知识:我和 Gradle 有个约会

腾讯Bugly特约做者:霍丙乾java

0、讲个故事

0.1 Ant,我还真觉得你是只蚂蚁

真正开始近距离接触编程实际上是在2012年,年末的时候带个人大哥说,我们这个 app 发布的时候手动构建耗时过久,研究一下 ant 脚本吧。linux

那个时候连 HashMap 都不知道是啥,可想开发经验几乎为零,一个小小的 ant 脚本看得我真是深深地感觉到了这个世界充满的恶意。好在后来硬着头皮搞明白了什么 target 之类的鬼东西,否则就没有而后了。android

0.2 Maven,大家真的会读这个单词么

Maven /`meivn/web

接触 Maven,彻底是由于读陈雄华的《Spring 实战》,他的源码竟然是用 Maven 构建的,结果 Spring 学得一塌糊涂,Maven我却是用顺手了。。编程

跟 Ant 同样,Maven 能够用来构建 Java 工程;跟 Ant 同样,Maven 的配置用 xml 来描述;但,Maven 能够管理依赖,它可让你作到“想要什么,就是一句话的事儿”。好比我想要个 gson,Maven 说能够,你记下来我带会儿构建的时候给你去取。windows

<dependency>
  <groupId>com.google.code.gson</groupId>
  <artifactId>gson</artifactId>
  <version>2.4</version>
</dependency>

真是让你当大爷呢。不过,Maven 这家伙学起来有点儿费劲,不少初学的时候在搭建环境的时候就被搞死了——你觉得是由于 Maven 的学习曲线陡峭吗?固然不是,是由于当初 Maven 的中央仓库被 x 了,因此你就每天看着 cannot resovle dependencies 玩就行了。api

后来 OSChina 傍上了阿里这个爸爸,就有了 maven.oschina.net。我去年找工做落定以后,想着作点儿什么的时候,发现 maven.oschina.net 估计被阿里爸爸关禁闭,死了几天,如今又活过来了。那又怎样呢,反正中央仓库被 x 的事情也已经成为过去。缓存

0.3 Gradle,你爹是否是 Google!!

13年的时候,我兴奋地跟前面提到的大哥说 Maven 是个好同志的时候,大哥说,Google 推荐用 Gradle。。因此,我想 Gradle,你爹是否是 Google。。或者至少是个干爹吧。网络

其实这都不重要了,毕竟 Gradle 实在是好用。比起前面两位的 xml 配置的手段,直接用代码的方式上阵必然是灵活得多。不只如此,Gradle 竟然可使用 Maven 仓库来管理依赖,就像是一个简易版的 Maven 同样,若是不是看不到 pom 文件,你都还觉得你仍然在使用 Maven(固然,因为你在用 Maven 的仓库,因此你天然也是离不开 Maven 的)。哦,你是 Ant 用户啊,那也不要紧啊,不信你看:并发

task helloTAS << {
     ant.echo(message: 'Hello TAS.')
}

一、用 Gradle 构建

1.1 工程结构

如图所示,这是一个不能更普通的 android 的 gradle 工程了。

根目录下面的 settings.gradle 当中主要是用来 include 子模块的,好比咱们这个工程有一个叫作 app 的子模块,那么 settings.gradle 的内容以下:

include ':app'

根目录下面的 build.gradle 包含一些通用的配置,这些配置能够在各个子模块当中使用。

gradle.properties 文件包含的属性,会成为 project 的 properties 的成员,例如咱们添加了属性 hello,

hello=Hello Tas!

而后咱们在 build.gradle 当中建立 task:

task hello << {
       println hello
       println project.getProperties().get("hello")
 }

输出地结果是同样的:

14:28:11: Executing external task 'hello'...
Configuration on demand is an incubating feature.
:app:hello
Hello Tas!
Hello Tas!

BUILD SUCCESSFUL

Total time: 0.54 secs
14:28:12: External task execution finished 'hello'.

local.properties 这个文件在 android 工程当中会遇到,咱们一般在其中设置 android 的 sdk 和 ndk 路径。固然,这个 android studio 会帮咱们设置好的。为了更清楚地了解这一点,我把 android 的 gradle 插件的部分源码摘录出来:

SDK.groovy,下面的代码主要包含了加载 sdk、ndk 路径的操做。

private void findLocation() {
 if (TEST_SDK_DIR != null) {
    androidSdkDir = TEST_SDK_DIR
    return
}

 def rootDir = project.rootDir
 def localProperties = new File(rootDir, FN_LOCAL_PROPERTIES)
 if (localProperties.exists()) {
    Properties properties = new Properties()
    localProperties.withInputStream { instr ->
    properties.load(instr)
    }
    def sdkDirProp = properties.getProperty('sdk.dir')

     if (sdkDirProp != null) {
        androidSdkDir = new File(sdkDirProp)
} else {
        sdkDirProp = properties.getProperty('android.dir')
        if (sdkDirProp != null) {
           androidSdkDir = new File(rootDir, sdkDirProp)
           isPlatformSdk = true
        } else {
           throw new RuntimeException(
"No sdk.dir property defined in local.properties file.")
       }
    }

    def ndkDirProp = properties.getProperty('ndk.dir')
    if (ndkDirProp != null) {
        androidNdkDir = new File(ndkDirProp)
     }

} else {
   String envVar = System.getenv("ANDROID_HOME")
   if (envVar != null) {
       androidSdkDir = new File(envVar)
   } else {
      String property = System.getProperty("android.home")
      if (property != null) {
         androidSdkDir = new File(property)
      }
  }

  envVar = System.getenv("ANDROID_NDK_HOME")
  if (envVar != null) {
      androidNdkDir = new File(envVar)
      }
   }
}

BasePlugin.groovy,经过这两个方法,咱们能够在 gradle 脚本当中获取 sdk 和 ndk 的路径

File getSdkDirectory() {
    return sdk.sdkDirectory
}

File getNdkDirectory() {
    return sdk.ndkDirectory
}

例如:

task hello << {
   println android.getSdkDirectory()
}

14:37:33: Executing external task 'hello'...
Configuration on demand is an incubating feature.
:app:hello
/Users/benny/Library/Android/sdk

BUILD SUCCESSFUL

Total time: 0.782 secs
14:37:35: External task execution finished 'hello'.

上面给出的只是最多见的 hierarchy 结构,还有 flat 结构,以下图1为 flat 结构,2为 hierarchy 结构。有兴趣的话能够 Google 一下。

1.2 几个重要的概念

这一小节的出场顺序基本上跟 build.gradle 的顺序一致。

1.2.1 Repository和Dependency

若是你只是写 Android 程序,那么依赖问题可能还不是那么的烦人——若是你用 Java 写服务端程序,那可就是一把辛酸一把泪了。

仓库的出现,完美的解决了这个问题,咱们在开发时只须要知道依赖的 id 和版本,至于它存放在哪里,我不关心;它又依赖了哪些,构建工具均可以在仓库中帮咱们找到并搞定。这一切都是那么天然,要不要来一杯拿铁,让代码构建一下子?

听说在 Java 发展史上,涌现出很是多的仓库,不过最著名的固然是 Maven 了。Maven 经过 groupId 和 artifactId 来锁定构件,再配置好版本,那么 Maven 仓库就能够最终锁定一个肯定版本的构件供你使用了。好比咱们开头那个例子,

<dependency>
  <groupId>com.google.code.gson</groupId>
  <artifactId>gson</artifactId>
  <version>2.4</version>
</dependency>

Maven 就凭这么几句配置就能够帮你搞定 gson-2.4.jar,不只如此,它还会按照你的设置帮你把 javadoc 和 source 搞定。妈妈不再用担忧我看不到构件的源码了。

那么这个神奇的 Maven 仓库在哪儿呢? Maven Central,中央仓库,是 Maven 仓库的鼻祖,其余的大多数仓库都会对它进行代理,同时根据需求添加本身的特点库房。简单说几个概念:

  • 代理仓库:要租房,去搜房网啊。你要去驾校报名,我是驾校代理,你找我,我去找驾校。具体到这里,还有点儿不同,一旦有人从代理仓库下载过一次特定得构件,那么这个构件会被代理仓库缓存起来,之后就不须要找被代理的仓库下载了。

  • 私有仓库:中国特点社会主义。走本身的路,你管我啊?公司内部的仓库里面有几个 hosted 的仓库,这些仓库就是咱们公司内部特有的,里面的构件也是咱们本身内部的同事上传之后供团队开发使用的。

  • 本地仓库:大隐隐于市。跟代理仓库的道理很像,只不过,这个仓库是存放在你本身的硬盘上的。

提及来,Andoid sdk 下面有个 extra 目录,里面的不少依赖也是以Maven 仓库的形式组织的。不过这是 Google 特点嘛,人家牛到不往 Maven 的中央仓库上传,真是没辙。

1.2.2 SourceSets

源码集,这里面主要包含你的各类类型的代码的路径,好比 'src/main/java' 等等。

1.2.3 Properties

前面咱们其实也稍稍有提到,这个 properties 实际上是 gradle 的属性,在 gradle 源码当中,咱们找到 Project.java 这个接口,能够看到:

/**
 * <p>Determines if this project has the given property. See <a href="#properties">here</a> for details of the
 * properties which are available for a project.</p>
 *
 * @param propertyName The name of the property to locate.
 * @return True if this project has the given property, false otherwise.
 */
boolean hasProperty(String propertyName);

/**
 * <p>Returns the properties of this project. See <a href="#properties">here</a> for details of the properties which
 * are available for a project.</p>
 *
 * @return A map from property name to value.
 */
Map<String, ?> getProperties();

/**
 * <p>Returns the value of the given property.  This method locates a property as follows:</p>
 *
 * <ol>
 *
 * <li>If this project object has a property with the given name, return the value of the property.</li>
 *
 * <li>If this project has an extension with the given name, return the extension.</li>
 *
 * <li>If this project's convention object has a property with the given name, return the value of the
 * property.</li>
 *
 * <li>If this project has an extra property with the given name, return the value of the property.</li>
 *
 * <li>If this project has a task with the given name, return the task.</li>
 *
 * <li>Search up through this project's ancestor projects for a convention property or extra property with the
 * given name.</li>
 *
 * <li>If not found, a {@link MissingPropertyException} is thrown.</li>
 *
 * </ol>
 *
 * @param propertyName The name of the property.
 * @return The value of the property, possibly null.
 * @throws MissingPropertyException When the given property is unknown.
 */
Object property(String propertyName) throws MissingPropertyException;

/**
 * <p>Sets a property of this project.  This method searches for a property with the given name in the following
 * locations, and sets the property on the first location where it finds the property.</p>
 *
 * <ol>
 *
 * <li>The project object itself.  For example, the <code>rootDir</code> project property.</li>
 *
 * <li>The project's {@link Convention} object.  For example, the <code>srcRootName</code> java plugin
 * property.</li>
 *
 * <li>The project's extra properties.</li>
 *
 * </ol>
 *
 * If the property is not found, a {@link groovy.lang.MissingPropertyException} is thrown.
 *
 * @param name The name of the property
 * @param value The value of the property
 */
    void setProperty(String name, Object value) throws MissingPropertyException;

不难知道,properties 其实就是一个 map,咱们能够在 gradle.properties 当中定义属性,也能够经过 gradle 脚原本定义:

setProperty('hello', 'Hello Tas again!')

使用方法咱们前面已经提到,这里就很少说了。

1.2.4 Project和Task

若是你用过 ant,那么 project 基本上相似于 ant 的 project 标签,task 则相似于 ant 的 target 标签。咱们在 build.gradle 当中编写的

task hello << {
......
}

实际上,是调用

Task Project.task(String name) throws InvalidUserDataException;

建立了一个 task,并经过 << 来定义这个 task 的行为。咱们看到 task 还有以下的重载:

Task task(String name, Closure configureClosure);

因此下面的定义也是合法的:

task('hello2',{
    println hello
})

简单说,project 就是整个构建项目的一个逻辑实体,而 task 就是这个项目的具体任务点。更多地介绍能够参见官网的文档,和 gradle 的源码。

二、发布构件

发布构件,仍是依赖仓库,咱们仍然以 Maven 仓库为例,私有仓库多数采用 sonatype。

2.1 UI 发布

若是管理员给你开了这个权限,你会在 ui 上面看到 upload artifact 的 tab,选择你要上传的构件,配置好对应的参数,点击上传便可。

2.2 使用 Maven 插件

这里的意思是使用 Maven 的 gradle 插件,在构建的过程当中直接上传。构建好的构件须要签名,请下载 GPG4WIN (windows),或者 GPGTOOLS(mac),生成本身的 key。

直接上代码:

gradle.properties

sonatypeUsername=你的用户名
sonatypePassword=你的密码
signing.keyId=你的keyid
signing.password=你的keypass
#注意,一般来说是这个路径。
# mac/linux
signing.secretKeyRingFile=/Users/你的用户名/.gnupg/secring.gpg

# Window XP and earlier (XP/2000/NT)
# signing.secretKeyRingFile=C:\\Documents and Settings\\<username>\\Application Data\\GnuPG\\secring.gpg

# Windows Vista and Windows 7
# signing.secretKeyRingFile=C:\\Users\\<username>\\AppData\\Roaming\\gnupg\\secring.gpg


projectName=你的构件名称
group=你的构件groupid
artifactId=你的构件artifactid
# 版本号,采用三位数字的形式,若是是非稳定版本,请务必添加SNAPSHOT
version=0.0.1-SNAPSHOT

build.gradle

apply plugin: 'com.android.library'
apply plugin: 'maven'
apply plugin: 'signing'

android {
   compileSdkVersion 21
   buildToolsVersion "21.1.2"

   defaultConfig {
       minSdkVersion 17
       targetSdkVersion 21
       versionCode 1
       versionName "0.2"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
         }
     }
}

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

def isSnapshot = version.endsWith('-SNAPSHOT')
def sonatypeRepositoryUrl
if(isSnapshot) {
    sonatypeRepositoryUrl = "http://maven.oa.com/nexus/content/repositories/thirdparty-snapshots/"
} else {
     sonatypeRepositoryUrl = "http://maven.oa.com/nexus/content/repositories/thirdparty/"
}

sourceSets {
    main {
        java {
            srcDir 'src/main/java'
        }
    }
}
task sourcesJar(type: Jar) {
    from sourceSets.main.allSource
    classifier = 'sources'
}

artifacts {
    //archives javadocJar
    archives sourcesJar
}

signing {
    if(project.hasProperty('signing.keyId') && project.hasProperty('signing.password') &&
        project.hasProperty('signing.secretKeyRingFile')) {
    sign configurations.archives
    } else {
        println "Signing information missing/incomplete for ${project.name}"
    }
}

uploadArchives {
    repositories {
        mavenDeployer {

           if(project.hasProperty('preferedRepo') && project.hasProperty('preferedUsername')
           && project.hasProperty('preferedPassword')) {

              configuration = configurations.archives
              repository(url: preferedRepo) {

                  authentication(userName: preferedUsername, password: preferedPassword)
                  }

             } else if(project.hasProperty('sonatypeUsername') && project.hasProperty('sonatypePassword')) {

                 beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }

                 repository(url: sonatypeRepositoryUrl) {
                    authentication(userName: sonatypeUsername, password: sonatypePassword)

                  }
               } else {
               println "Settings sonatypeUsername/sonatypePassword missing/incomplete for ${project.name}"
                }

               pom.artifactId = artifactId
               pom.project {
                   name projectName
                   packaging 'aar'

                   developers {
                       developer {
                           id 'wecar'
                           name 'wecar'
                        }
                    }
                }
            }
        }
    }

而后运行 gradle uploadArchives 就能够将打包的 aar 发布到公司的 Maven 仓库当中了。jar包的方式相似,这里就不在列出了。

2.3 使用 Maven 命令

这个能够经过 mvn 在 cmdline 直接发布构件,命令使用说明:

mvn deploy:deploy-file -Durl=file://C:\m2-repo \
   -DrepositoryId=some.id \
   -Dfile=your-artifact-1.0.jar \
   [-DpomFile=your-pom.xml] \
   [-DgroupId=org.some.group] \
   [-DartifactId=your-artifact] \
   [-Dversion=1.0] \
   [-Dpackaging=jar] \
   [-Dclassifier=test] \
   [-DgeneratePom=true] \
   [-DgeneratePom.description="My Project Description"] \
   [-DrepositoryLayout=legacy] \
   [-DuniqueVersion=false]

固然这里仍然有个认证的问题,咱们须要首先在 maven 的 settings 配置当中加入:

<servers>
        <server>
            <id>Maven.oa.com</id>
            <username>rdm</username>
            <password>rdm</password>
         </server>
</servers>

而后咱们就可使用命令上传了:

mvn deploy:deploy-file -DgroupId=com.tencent.test -DartifactId=test -Dversion=1.0.0 -Dpackaging=aar -Dfile=test.aar -Durl=http://maven.oa.com/nexus/content/repositories/thirdparty -DrepositoryId=Maven.oa.com

三、插件

3.1 什么是插件

插件其实就是用来让咱们偷懒的。若是没有插件,咱们想要构建一个 Java 工程,就要本身定义 sourceSets,本身定义 classpath,本身定义构建步骤等等。

简单地说,插件其实就是一组配置和任务的合集。

gradle 插件的存在形式主要由三种,

gradle 文件中直接编写,你能够在你的 build.gradle 当中写一个插件来直接引入:

apply plugin: GreetingPlugin

class GreetingPlugin implements Plugin<Project{
   void apply(Project project) {
      project.task('hello') << {
          println "Hello from the GreetingPlugin"
      }
   }
}

buildSrc工程,这个就是在你的工程根目录下面有一个标准的 Groovy 插件工程,目录是 buildSrc,你能够直接引用其中编写的插件。

独立的工程,从结构上跟 buildSrc 工程是同样的,只不过这种须要经过发布到仓库的形式引用。一般咱们接触的插件都是这种形式。

详细能够参考:Chapter 61. Writing Custom Plugins

3.2 常见的插件

目前接触到的插件,有下面这么几种:

  • java,构建 java 工程

  • war,发布 war 包用,构建 web 工程会用到

  • groovy,构建 groovy 工程

  • com.android.application,构建 Android app 工程

  • com.android.library,构建 Android library,一般输出 aar

  • sign,签名

  • maven,发布到 maven 仓库

  • org.jetbrains.intellij,构建 intellij 插件工程

3.3 本身动手写一个插件

建立一个普通的 groovy 工程(java 工程也没有关系),建立 src/main/groovy 目录,编写下面的代码:

package com.tencent.wecar.plugin

import org.gradle.api.Plugin
import org.gradle.api.internal.project.ProjectInternal

class GreetingPlugin implements Plugin<ProjectInternal> {

    void apply(ProjectInternal project) {
       project.task('hello') << {
           println 'hello'
       }
    }
}

在 src/main/resources 建立 META-INF/gradle-plugins 目录,建立 greetings.properties 文件:

implementation-class=com.tencent.wecar.plugin.GreetingPlugin

其中 greettings 就是你的插件 id。

build.gradle

group 'com.tencent.wecar.plugin'
version '1.1-SNAPSHOT'

buildscript {
    repositories {
        mavenLocal()
    }
}

apply plugin: 'groovy'
apply plugin: 'java'

repositories {
    mavenCentral()
}

sourceSets {
    main {
        groovy {
            srcDirs = [
                'src/main/groovy',
                'src/main/java'
            ]
         }  // compile everything in src/ with groovy
         java { srcDirs = []}// no source dirs for the java compiler

      }
}

dependencies {
    //tasks.withType(Compile) { options.encoding = "UTF-8" }
    compile gradleApi()
}

// custom tasks for creating source jars
task sourcesJar(type: Jar, dependsOn:classes) {
    classifier = 'sources'
    from sourceSets.main.allSource
}

// add source jar tasks as artifacts
artifacts { archives sourcesJar }

// upload to local
uploadArchives {
    repositories{
        mavenLocal()
    }
}

运行 uploadArchives 发布到本地仓库,那么就能够找到咱们本身的插件了,因为当中没有指定 artifactId,那么咱们的插件的 artifactId 就是咱们的工程名称,好比这里是 deployplugin。

那么咱们要怎么引入这个插件呢?

首先要再 buildScript 增长依赖:

buildscript {
    repositories {
        mavenLocal()
    }
    dependencies {
        classpath 'com.tencent.wecar.plugin:deployplugin:1.1-SNAPSHOT'
    }
}

而后:

apply plugin: 'greetings'

这样咱们的 task “hello” 就被引入了。

四、Gradle 运行慢?

用过 Gradle 的朋友多少会感受到这货有时候会比较慢。咱们能够经过下面的三个手段加速你的 Gradle。

  • 不用中央仓库。若是你的 repository 配置的是 mavenCentral,放开它吧,全世界的人都在琢磨着怎么虐它,你就不要瞎掺和了。试试 jCenter。

  • 升级最新的 Gradle 版本。目前最新的版本是2.4,Android Studio 从1.3开始默认使用 Gradle2.4

  • 开启Gradle的电动小马达。在 gradle.properties(眼熟?没错,就是它!!)

里面添加下面的配置:

若是你的任务没有时序要求,那么打开这个选项能够并发处理多个任务,充分利用硬件资源。。嗯,若是你的是单核 CPU。。当我没说。。

org.gradle.parallel=true

这个也能够在命令行经过参数的形式启动,3个小时有效。守护进程可使编译时间大大缩短

org.gradle.daemon=true

这个看需求吧,Gradle 是运行在 Java 虚拟机上的,这个指定了这个虚拟机的堆内存初始化为256M,最大为1G。若是你内存只有2G,那当我没说。。

org.gradle.jvmargs=-Xms256m -Xmx1024m

固然,建议的方式是在你的用户目录下面的 .gradle/ 下面建立一个 gradle.properties,省得坑你的队友。。。


腾讯Bugly

Bugly是腾讯内部产品质量监控平台的外发版本,支持iOS和Android两大主流平台,其主要功能是App发布之后,对用户侧发生的crash以及卡顿现象进行监控并上报,让开发同窗能够第一时间了解到app的质量状况,及时修改。目前腾讯内部全部的产品,均在使用其进行线上产品的崩溃监控。

腾讯内部团队4年打磨,目前腾讯内部全部的产品都在使用,基本覆盖了中国市场的移动设备以及网络环境,可靠性有保证。使用Bugly,你就使用了和手机QQ、QQ空间、手机管家相同的质量保障手段

相关文章
相关标签/搜索