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

相關文章
相關標籤/搜索