騰訊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空間、手機管家相同的質量保障手段