手把手講解 Android開發(fā)中Gradle編程一條龍攻略(1)

前言

手把手講解系列文章,是我寫給各位看官,也是寫給我自己的。
文章可能過分詳細(xì),但是這是為了幫助到盡量多的人,畢竟工作5,6年,不能老吸血,也到了回饋開源的時候.
這個系列的文章:
1、用通俗易懂的講解方式,講解一門技術(shù)的實用價值
2、詳細(xì)書寫源碼的追蹤,源碼截圖,繪制類的結(jié)構(gòu)圖,盡量詳細(xì)地解釋原理的探索過程
3、提供Github 的 可運行的Demo工程,但是我所提供代碼,更多是提供思路,拋磚引玉,請酌情cv
4、集合整理原理探索過程中的一些坑,或者demo的運行過程中的注意事項
5、用gif圖,最直觀地展示demo運行效果

如果覺得細(xì)節(jié)太細(xì),直接跳過看結(jié)論即可。
本人能力有限,如若發(fā)現(xiàn)描述不當(dāng)之處,歡迎留言批評指正。

學(xué)到老活到老,路漫漫其修遠(yuǎn)兮。與眾君共勉 !


正文大綱

1. gradle是什么
2. groovy語言的特性以及它和java的關(guān)系
3. 為什么你的apk打包這么慢
4. 如何利用gradle編程解決工作中的實際問題
5. gradle的高級用法(gradle多渠道快速打包插件)


正文

1. gradle 是什么?

先看看來自百度百科的解釋:

Gradle是一個基于Apache AntApache Maven概念的項目自動化構(gòu)建開源工具。它使用一種基于Groovy的特定領(lǐng)域語言(DSL)來聲明項目設(shè)置,目前也增加了基于Kotlin語言的kotlin-based DSL,拋棄了基于XML的各種繁瑣配置。
面向Java應(yīng)用為主。當(dāng)前其支持的語言限于Java、Groovy、Kotlin和Scala,計劃未來將支持更多的語言.

Gradle是一個基于JVM的構(gòu)建工具,是一款通用靈活的構(gòu)建工具,支持maven, Ivy倉庫,支持傳遞性依賴管理,而不需要遠(yuǎn)程倉庫或者是pom.xml和ivy.xml配置文件,基于Groovy,build腳本使用Groovy編寫。

大家懂的,百度百科就是把簡單的一個東西解釋得一般人看不懂。那么來看看上面一段話中的重點。
Gradle 是基于Jvm的項目自動化構(gòu)建工具 ,可以使用java groovy kotlin 和 scala語言進(jìn)行編程。 它是DSL 特定領(lǐng)域語言,它是專門用來構(gòu)建項目的.

再解讀并且擴(kuò)展一下:

  1. Gradle是一個工具,用于構(gòu)建項目,在androidStudio的工程目錄中的體現(xiàn),就是 幫我們把 Android工程中的java源碼文件,資源文件,依賴包,普通配置文件等,經(jīng)過一系列步驟, 最終生成一個 或者多個apk文件,有了apk文件,才能到 應(yīng)用市場去發(fā)布。
  2. Gradle是一個編程框架,Groovy是該框架常用的編程語言,既然它是語言,那么就可以進(jìn)行程序邏輯的編寫,編寫之后,會被編譯成字節(jié)碼,交給JVM運行。
  3. 項目構(gòu)建工具,除了gradle之外,還有 ant,maven,只不過很少使用,已經(jīng)漸漸轉(zhuǎn)gradle.

GradleandroidStudio項目中存在的痕跡如下圖(這是一個新建的project):

image.png

上面圖中,上圖中,我標(biāo)記了1-6,現(xiàn)在來一一解釋:

1. Gradle- Wrapper

gradle的包裝盒,里面有g(shù)radle的jar包,還有properties屬性. Jar包包含的是.class文件,這也就解釋了為什么gradle的代碼是基于JVM的,可以預(yù)見,這個jar包內(nèi)肯定包含了gradle編譯之后生成的java類,各種api. 然而,事實也證明我的猜想沒有錯。

反編譯.png

Jar包之外,還有一個.properties.
properties.png

這里是gradle的一些配置信息,比較重要的是最后一條,distributionUrl 它代表了gradle工具包的下載路徑.

2. 外部的build.gradle

它是全局范圍的構(gòu)建配置.

3. setting.gradle

image.png

但是這里不僅僅可以定義要編譯的module,還可以進(jìn)行插件化編程。

4. gradle.properties

gradle的全局配置
image.png

5. gradlew 和 gradlew.bat

這兩個,是不同平臺下的可執(zhí)行文件,前者是 .sh格式,希爾文件,linux下的可執(zhí)行文件。后者是 window下的批處理命令。

6. module內(nèi)部的build.gradle

綜上所述

Gradle是貫穿了整個android工程的構(gòu)建工具,幾乎隨處可見gradle的身影,作為高級開發(fā)者,如果只知道android的java代碼,寫寫xml,而不懂得gradle編程的話,開發(fā)中遇到某些問題(特別是架構(gòu)方面的問題),很可能不知所措。


2. groovy語言的特性以及它和java的關(guān)系

Gradle的常用編程語言有java,groovy,kotlin,scala等,但是最常見的,谷歌默認(rèn)的構(gòu)建語言,還是groovy.
那么, 了解groovy語言的特性是繞過不去的。
但是精(熟)通(練)Java的開發(fā)者, groovy和java都是基于jvm,都是編譯成字節(jié)碼來運行在jvm中,我們在有java老底的基礎(chǔ)上,不需要逐字逐句去學(xué)習(xí)語法,而只需要總結(jié)出groovy和java語法上比較相異的部分,則可以輕松掌握它。

先給出Groovy語法的官網(wǎng):
https://www.w3cschool.cn/groovy/groovy_basic_syntax.html
大部分語法,我們作為java開發(fā)者都能看懂,但是以下這些,則需要動一下腦子。

1.語句結(jié)束的逗號可以省略

但是你要加上也不會報錯,建議能省就省吧

2. 導(dǎo)包

Groovy的導(dǎo)包方式和java一樣:import java.lang.*,但是.gradle內(nèi)會有默認(rèn)導(dǎo)包。每一個xxx.gradle文件,都能
不用導(dǎo)包而直接使用以下包的類。

import java.lang.* 
import java.util.* 
import java.io.* 
import java.net.* 
import groovy.lang.* 
import groovy.util.* 
import java.math.BigInteger 
import java.math.BigDecimal

3. 定義變量

除了java所支持的 String a = "aaa" ,之外,它還可以def a = "aaaa"

    String s = "aaaaa"
    def s2 = "ssssss"

4. 循環(huán)

    for (int i in 1..10) {//用for in 遍歷 1到10(包含頭尾)
        println i
    }

    def dd= [1,2,3,4,5]//for in  遍歷數(shù)組
    for(d in dd){
        println d
    }

    def map = ["ken":21,"ju":12,"aaaaa":223] // for in 遍歷 map
    for(d in map){
        println d
    }

另外,如果是簡單的多次執(zhí)行同一段代碼,還可以這么寫(這個涉及到閉包,后面解釋):

    10.times {
        variable -> println variable
    }

5.文件IO

除了可以使用java的標(biāo)準(zhǔn)io流去進(jìn)行文件讀寫之外,還使用Groovy可以進(jìn)行文件的IO讀寫操作,后者更加簡單.比如

    new File("test.txt").eachLine { line -> println "line $line" } //讀取每一行
    //讀取文件全部內(nèi)容
    File f = new File("test.txt")
    println "文件全部內(nèi)容 \n$f.text"

    //把helloWorld字符串寫入到test.txt文件
    new File("test.txt").withWriter('utf-8', {
        writer -> writer.writeLine("hello world")
    })

總之一句話,Groovy提供的 File類,可以進(jìn)行簡單的文件操作,足夠滿足我們平時的gradle編程IO流操作,其他更多api可以翻閱 https://www.w3cschool.cn/groovy/groovy_file_io.html

6. 閉包

導(dǎo)致有些人看不懂.gradle文件的原因就是 閉包, 這個是java中不存在的概念(拉姆達(dá)表達(dá)式類似閉包,但是不是閉包).可以大致理解 為: 閉包==匿名代碼塊

    //閉包測試
    def clo = { println("無參閉包") }//無參閉包
    clo()

    def clo1 = { a -> println("1個參數(shù)的閉包:" + a) }
    clo1("參數(shù)")


    def clo2 = { a, b, c -> println("多個參數(shù)的閉包 $a $b $c") }
    clo2(1,2,3)

閉包也可以作為參數(shù)傳遞給一個方法:

task test() {
    //閉包測試
    def clo = { println("無參閉包") }//無參閉包
    def clo1 = { a -> println("1個參數(shù)的閉包:" + a) }
    def clo2 = { a, b,c -> println("多個參數(shù)的閉包 $a $b $c") }

    //閉包作為參數(shù)
    testClo(clo)
    testClo1(clo1)
    testClo2(clo2)
}

void testClo(Closure clo) {
    println("=======開始測試閉包0咯=======")
    clo()
}

void testClo1(Closure clo) {
    println("=======開始測試閉包1咯=======")
    clo("哈哈哈")
}

void testClo2(Closure clo) {
    println("=======開始測試閉包2咯=======")
    clo("啊1", 2, 3)
}
集合/映射+閉包
task test() {
    def list = [1, 2, 3, 4, 5]
    list.each { a -> println(a) }//指明形參名
    list.each { println(it) } //使用默認(rèn)的形參名it

    def map = ["zhou": 22, "li": 30, "han": 40]
    map.each { a -> println(a) }//指明形參名
    map.each { a -> println(a.key + "- "+ a.value) }//指明形參名
    map.each { println(it.key + "- "+ it.value) } //使用默認(rèn)的形參名it
}

7. 省略小括號

除了閉包之外,groovy對于小括號的省略寫法也是阻礙我們看懂gradle文件的原因。那么groovy在什么情況下可以省略小括號呢?

1. 所有的頂級表達(dá)式的括號可以省略
2. 當(dāng)閉包作為頂級表達(dá)式的最后一個參數(shù)的時候,小括號可以省略

但是,當(dāng)函數(shù)嵌套調(diào)用的時候,如果函數(shù)是無參的,這個時候小括號不可省略.

什么意思?小括號的省略往往和閉包 / 方法調(diào)用有關(guān),我們拿實際案例來說:

buildscript {
    repositories {
        google()
        jcenter()

    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.4.1'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

上面這段,如果按照java的語言習(xí)慣,根本讀不通,但是,如果我們把括號補(bǔ)全以下,變成這樣:

buildscript({
    repositories({
        google()
        jcenter()

    })
    dependencies({
        classpath ('com.android.tools.build:gradle:3.4.1')

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    })
})

解讀以上補(bǔ)全過程,通過代碼閱讀,我發(fā)現(xiàn),buildscript / repositories /google/jcenter/dependencies / classpath 其實都是 一個一個的"方法名",方法自然是會帶上小括號,小括號里面就會裝著參數(shù),而參數(shù)類型恰恰就是 閉包Closure,
最后一個classpath比較特殊,他的參數(shù)類型是Object[]
比如

void buildscript(Closure configureClosure);
void repositories(Closure configureClosure);

補(bǔ)全了小括號,我們就能以java的閱讀習(xí)慣去閱讀gradle文件。
再舉一個例子(如果上面的能讀懂,那么下面的這一段代碼應(yīng)該能讀懂了):

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"
    defaultConfig {
        applicationId "com.example.myapplication"
        minSdkVersion 15
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

}

補(bǔ)全括號之后,

android({
    compileSdkVersion(29)
    buildToolsVersion("29.0.2")
    defaultConfig({
        applicationId("com.example.myapplication")
        minSdkVersion(15)
        targetSdkVersion(29)
        versionCode(1)
        versionName("1.0")
        testInstrumentationRunner("androidx.test.runner.AndroidJUnitRunner")
    })
    buildTypes({
        release({
            minifyEnabled(false)
            proguardFiles(getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro')
        })
    })

})

那么什么時候不可以省略小括號呢?也舉個例子:

image.png

無參函數(shù)調(diào)用的時候,括號不可省略。有參數(shù)的函數(shù),可以省略。

總結(jié)

從一個java老司機(jī)的眼光去看 groovy,其實java和groovy差別就是以上這些(其他一些 groovy 語法并不是不重要,而是java開發(fā)者一眼就能看明白,沒必要拿出來占字?jǐn)?shù)),掌握起來并不難,就一句話:

gradle/ groovy 編程,在函數(shù)調(diào)用時往往會省略小括號,如果省略了小括號再添上閉包參數(shù),看上去就比較詭異了。但是,補(bǔ)全了小括號,理解了閉包之后,讀寫gradle/groovy將不會再有障礙。


3. 為什么你的apk打包這么慢

你們有沒有經(jīng)歷過 用as新建一個project,然后半天不能構(gòu)建成功的窘境?如果有,那么恭喜你,你的電腦網(wǎng)絡(luò)被墻了,gradle插件包服務(wù)器并不在國內(nèi),如果網(wǎng)絡(luò)被墻,那么下載不動,你的項目構(gòu)建就很慢。

解決方案1

image.png

還記不記得這里的wrapper / gradle-wrapper.properties 這里有一個關(guān)鍵配置,
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
https\://services.gradle.org 是協(xié)議和主機(jī)
/distributions/ 是路徑
gradle-5.1.1-all.zip 則是插件包的名字
這里的gradle-5.1.1-all.zip插件包,包含了源文件和可執(zhí)行文件,其實我們構(gòu)建項目,并不需要源文件,所以,我們可以試著把這里的-all 改成 -bin,變成下面這樣:
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-bin.zip
也許可以加快下載速度,有利于你的項目編譯。

解決方案2

承接方案1,distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip 這個插件包,最后下載到了我們的本地,window系統(tǒng)下,一般都在:

image.png
MAC系統(tǒng)應(yīng)該也在對應(yīng)的用戶目錄,我沒有mac電腦,就不截圖了.

我們可以手動通過別的下載軟件把這個包,下載到上圖的目錄下 (C:\Users\adminstrator\.gradle\wrapper\dists),然后在gradle-wrapper.properties中設(shè)置對應(yīng)的版本即可,這樣也能免除你在編譯的時候去浪費時間了。 而且這些包應(yīng)該也能復(fù)用,以后拷貝出去,給其他項目來用,也能節(jié)約時間。

解決方案3

除了gradle插件包下載慢之外,很多第三方依賴庫都是來自北美大陸,我們?nèi)ハ螺d,依然可能被墻。一定有人經(jīng)歷過jcenter下的包下半天沒動靜的情況了。這個如何解決?
使用國內(nèi)鏡像去替換
舉個例子,如下圖,jcenter下了半天沒動靜。

image.png
那么,你改成這樣:
image.png

jcenter()的后面加上{ url 'https://maven.aliyun.com/repository/jcenter' }
aliyun的鏡像地址為:https://maven.aliyun.com/mvn/view
image.png

如果發(fā)現(xiàn)其他的依賴庫下的依賴包下載不動,同理。
除了阿里云之外,還有其他公司發(fā)布國內(nèi)鏡像,使用方法同理。

那么如果你想玩點騷操作,你想先去國內(nèi)倉庫去找,然后再去國外倉庫去找。也可以實現(xiàn),方法為:

image.png

當(dāng)給同一個倉庫設(shè)置多個地址的時候,系統(tǒng)按照從上到下的書寫順序去查找,找到為止,查到最后還找不到,那就GG了,沒法編譯。

解決方案4

存在一種情況,即使你按照前面3步去做了,編譯依然很慢,這是為何?很可能是因為:

image.png

這里的加號,意思是:你這次所下載的依賴包,自動去查找網(wǎng)絡(luò)上的最新版
如果每一次編譯,都要去請求網(wǎng)絡(luò),然后下載最新版,勢必會拖慢編譯速度。
加號,谷歌設(shè)計出來應(yīng)該是為了提供使用最新包的簡易寫法,但是這個并不是一個好習(xí)慣。試想,你引用的第三方依賴包 發(fā)生了api變化,很可能讓你的引用代碼出現(xiàn)bug。

解決方案5

如果上面的方案都不能解決你編譯慢的問題,比如你的項目引用了大量的依賴包,down下來一個新的項目,首次編譯都要花費大量的時間。這個時候,可以:使用本地私庫 常用的私庫有 artifact 等。
至于私庫搭建,相信網(wǎng)上有很多攻略,我就不在這里浪費篇幅了。


未完待續(xù)...

結(jié)語

全文太長,截取一下吧。
下一篇(2) 將會 繼續(xù) 第4節(jié) 往后講解。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容