腾讯Bugly特约做者:霍丙乾java
真正开始近距离接触编程实际上是在2012年,年末的时候带个人大哥说,我们这个 app 发布的时候手动构建耗时过久,研究一下 ant 脚本吧。linux
那个时候连 HashMap 都不知道是啥,可想开发经验几乎为零,一个小小的 ant 脚本看得我真是深深地感觉到了这个世界充满的恶意。好在后来硬着头皮搞明白了什么 target 之类的鬼东西,否则就没有而后了。android
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 的事情也已经成为过去。缓存
13年的时候,我兴奋地跟前面提到的大哥说 Maven 是个好同志的时候,大哥说,Google 推荐用 Gradle。。因此,我想 Gradle,你爹是否是 Google。。或者至少是个干爹吧。网络
其实这都不重要了,毕竟 Gradle 实在是好用。比起前面两位的 xml 配置的手段,直接用代码的方式上阵必然是灵活得多。不只如此,Gradle 竟然可使用 Maven 仓库来管理依赖,就像是一个简易版的 Maven 同样,若是不是看不到 pom 文件,你都还觉得你仍然在使用 Maven(固然,因为你在用 Maven 的仓库,因此你天然也是离不开 Maven 的)。哦,你是 Ant 用户啊,那也不要紧啊,不信你看:并发
task helloTAS << { ant.echo(message: 'Hello TAS.') }
如图所示,这是一个不能更普通的 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 一下。
这一小节的出场顺序基本上跟 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。
若是管理员给你开了这个权限,你会在 ui 上面看到 upload artifact 的 tab,选择你要上传的构件,配置好对应的参数,点击上传便可。
这里的意思是使用 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包的方式相似,这里就不在列出了。
这个能够经过 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
插件其实就是用来让咱们偷懒的。若是没有插件,咱们想要构建一个 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
目前接触到的插件,有下面这么几种:
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 插件工程
建立一个普通的 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。
不用中央仓库。若是你的 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空间、手机管家相同的质量保障手段