您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
   
 
 
     
   
 订阅
  捐助
Android开发必备知识:我和Gradle有个约会
 
作者:霍丙乾 来源:CSDN 发布于: 2015-12-15
   次浏览      
 

摘要:Gradle和Maven的作用相似,是帮助我们构建Android工程的工具,Gradle也是Google推荐的工具,本文详细分析了 Maven、Gradle的工作原理、使用方法以及Android中项目构建的基础知识。

1、用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的仓库,这些仓库就是我们公司内部特有的,里面的构件也是我们自己内部的同事上传以后供团队开发使用的。
  • 本地仓库:大隐隐于市。跟代理仓库的道理很像,只不过,这个仓库是存放在你自己的硬盘上的。

说起来,Android 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的源码。

2、发布构件

发布构件,还是依赖仓库,我们仍然以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、插件

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工程是一样的,只不过这种需要通过发布到仓库的形式引用。通常我们接触的插件都是这种形式。

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”就被引入了。

4、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,免得坑你的队友。。。

   
次浏览       
 
相关文章

手机软件测试用例设计实践
手机客户端UI测试分析
iPhone消息推送机制实现与探讨
Android手机开发(一)
 
相关文档

Android_UI官方设计教程
手机开发平台介绍
android拍照及上传功能
Android讲义智能手机开发
相关课程

Android高级移动应用程序
Android系统开发
Android应用开发
手机软件测试
最新活动计划
LLM大模型应用与项目构建 12-26[特惠]
QT应用开发 11-21[线上]
C++高级编程 11-27[北京]
业务建模&领域驱动设计 11-15[北京]
用户研究与用户建模 11-21[北京]
SysML和EA进行系统设计建模 11-28[北京]

android人机界面指南
Android手机开发(一)
Android手机开发(二)
Android手机开发(三)
Android手机开发(四)
iPhone消息推送机制实现探讨
手机软件测试用例设计实践
手机客户端UI测试分析
手机软件自动化测试研究报告
更多...   


Android高级移动应用程序
Android应用开发
Android系统开发
手机软件测试
嵌入式软件测试
Android软、硬、云整合


领先IT公司 android开发平台最佳实践
北京 Android开发技术进阶
某新能源领域企业 Android开发技术
某航天公司 Android、IOS应用软件开发
阿尔卡特 Linux内核驱动
艾默生 嵌入式软件架构设计
西门子 嵌入式架构设计
更多...