使用 Gradle 管理你的 Android Studio 工程

jopen 9年前發布 | 179K 次閱讀 Android開發 移動開發 Android Gradle

 

Gradle簡介

Gradle是一個基于Ant和Maven概念的項目自動化建構工具。它使用一種基于Groovy的特定領域語言(DSL)來聲明項目設置,這比我們的 ANT使用XML構建配置要靈活的多。在編寫配置時,你可以像編程一樣靈活,Gradle是基于Groovy的DSL語言,完全兼容JAVA

Gradle入門

projects 和 tasks是Gradle中最重要的兩個概念,任何一個Gradle構建都是由一個或者多個project組成,每個project可以是一個jar包,一個web應用,或者一個android app等,每個project又由多個task構成,一個task其實就是構建過程中一個原子性的操作,比如編譯、拷貝等。

一個build.gradle文件是一個構建腳本,當運行gradle命令的時候會從當前目錄查找build.gradle文件來執行構建。下面我們來看下gradle的Hello World。在build.gradle構建文件中輸入以下構建腳本:

task hello {
    doLast {
        println 'Hello world!'
    }
}

task定義了一個任務,這個任務名字是hello。doLast是Task的方法,意思是在該hello任務執行之后作的事情,可以用一個閉包配置它,這里是輸出Hello world!字符串。我們在終端里執行如下命令運行查看結果:
$gradle hello -q
Hello world!

其他關于Gradle的更多介紹請參考 Gradle使用指南

Android Studio入門

使用Android Studio新建一個工程之后,其目錄結構是這樣的:

├── app #Android App目錄
│   ├── app.iml
│   ├── build #構建輸出目錄
│   ├── build.gradle #構建腳本
│   ├── libs #so相關庫
│   ├── proguard-rules.pro #proguard混淆配置
│   └── src #源代碼,資源等
├── build
│   └── intermediates
├── build.gradle #工程構建文件
├── gradle
│   └── wrapper
├── gradle.properties #gradle的配置
├── gradlew #gradle wrapper linux shell腳本
├── gradlew.bat
├── LibSqlite.iml
├── local.properties #配置Androod SDK位置文件
└── settings.gradle #工程配置

settings.gradle用于配置project,標明其下有幾個module,比如這里包含一個:app module

include ':app' 
</div>

和settings.gradle在同一目錄下的build.gradle是一個頂級的build配置文件,在這里可以為所有project以及module配置一些常用的配置。

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript { repositories { jcenter()//使用jcenter庫 } dependencies { // 依賴android提供的1.1.0的gradle build classpath 'com.android.tools.build:gradle:1.1.0'

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

} //為所有的工程的repositories配置為jcenters allprojects { repositories { jcenter() } }</pre>

Android Gradle基本配置

下面著重說一下Android的Gradle,畢竟對Android開發來說,這才是重中之重。這里以初始化好的build.gradle為例。

apply plugin: 'com.android.application'

android { compileSdkVersion 21 buildToolsVersion "22.0.1"

defaultConfig {
    applicationId "org.flysnow.demo"
    minSdkVersion 9
    targetSdkVersion 21
    versionCode 1
    versionName "1.0"
}
buildTypes {
    release {
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
}

}

dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:22.0.0' }</pre>

開頭第一行apply plugin: ‘com.android.application’,這表示該module是一個app module,應用了com.android.application插件,如果是一個android library,那么這里的是apply plugin: ‘com.android.library’。

其次是基于哪個SDK編譯,這里是API LEVEL,是21,buildToolsVersion是基于哪個構建工具版本進行構建的。defaultConfig是默認配置,如果沒有其他的配置覆蓋,就會使用這里的。看其屬性的名字就可以知道其作用,比如applicationId是配置包名的,versionCode是版本號,versionName是版本名稱等。

buildTypes是構建類型,常用的有release和debug兩種,可以在這里面啟用混淆,啟用zipAlign以及配置簽名信息等。

dependencies就不屬于Android專有的配置了,它定義了該module需要依賴的jar,aar,jcenter庫信息。

配置應用的簽名信息

在android.signingConfigs{}下定義一個或者多個簽名信息,然后在buildTypes{}配置使用即可。比如這里

android {

signingConfigs {
    release {
        storeFile file("release.keystore")
        keyAlias "release"
        keyPassword "123456"
        storePassword "123456"
    }
    debug {
        ...
    }
}

buildTypes {
    release {
        signingConfig signingConfigs.release
    }
    debug {
        signingConfig signingConfigs.debug
    }
}

}</pre>

storeFile是簽名證書文件,keyAlias是別名,keyPassword是key的密碼,storePassword是證書的密碼。配好好相關信息即可在buildTypes配置使用。

啟用proguard混淆

我們可以為不同的buildTypes選擇是否啟用混淆,一般release發布版本是需要啟用混淆的,這樣別人反編譯之后就很難分析你的代碼,而我們自己開發調試的時候是不需要混淆的,所以debug不啟用混淆。對release啟用混淆的配置如下:

android {

buildTypes {
    release {
        minifyEnabled true
        proguardFile 'proguard.cfg'
    }

} }</pre>

minifyEnabled為true表示啟用混淆,proguardFile是混淆使用的配置文件,這里是module根目錄下的proguard.cfg文件

啟用zipAlign

這個也是比較簡單的,同樣也是在buildTypes里配置,可以為不用的buildTypes選擇時候開啟zipAlign

android {

buildTypes {
    release {
        zipAlignEnabled true
    }

} }</pre>

多渠道打包

東西到了國內就變了,做什么都是一窩蜂,比如Android App市場就是,所以才有了多渠道打包,每次發版幾十個渠道包。還好Android Gradle給我們提供了productFlavors,讓我們可以對生成的APK包進行定制,所以就有了多渠道。

android  {
    productFlavors {
        dev{

    }
    google{

    }
    baidu{

    }
}

}</pre>
這樣當我們運行assembleRelease的時候就會生成3個release包,分別是dev、google以及baidu的。目前看這三個包除了文件名沒有什么不一樣,因為我們還沒有定制,使用的都是defaultConfig配置。這里的flavor和defaultConfig是一樣的,可以自定義其applicationId、versionCode以及versionName等信息,比如區分不同包名:

android  {
    productFlavors {
        dev{
            applicationId "org.flysnow.demo.dev"
        }
        google{
            applicationId "org.flysnow.demo.google"
        }
        baidu{
            applicationId "org.flysnow.demo.baidu"
        }
    }
}

批量修改生成的apk文件名

在我們打包發版的時候,一次性打幾十個包,這時候我們就想讓生成的apk文件名有區分,比如一眼就能看出這個apk是哪個版本的,哪個渠道的,是哪天打的包等等,這就需要我們在生成apk文件的時候動態修改生成的apk文件名達到這一目的。這里以我們的產品隨手記為例:

def buildTime() {
    def date = new Date()
    def formattedDate = date.format('yyyyMMdd')
    return formattedDate
}

android { buildTypes { release { applicationVariants.all { variant -> variant.outputs.each { output -> if (output.outputFile != null && output.outputFile.name.endsWith('.apk') &&'release'.equals(variant.buildType.name)) { def apkFile = new File( output.outputFile.getParent(), "Mymoney_${variant.flavorName}v${variant.versionName}${buildTime()}.apk") output.outputFile = apkFile } } } } } }</pre>

以baidu渠道為例,以上的代碼會生成一個名字為Mymoney_baidu_v9.5.2.6_20150330.apk安裝包。下面我們分析一下,Android Gradle任務比較復雜,它的很多任務都是自動生成的,為了可以更靈活的控制,Android Gradle提供了applicationVariants、libraryVariants以及testVariants,他們分別適用于app、 library、app和library都適用。

這里是循環處理每個applicationVariant,當他們的輸出文件名以apk結尾并且buildType是release時,重新設置新的輸出文件名,這樣就達到了我們批量修改生成的文件名的目的。

AndroidManifest里的占位符

AndroidManifest.xml這是一個很重要的文件,我們的很多配置都在這里定義。有時候我們的一些配置信息,比如一個第三方應用的 key,第三方統計分析的渠道號等也要在這里進行配置。這里以友盟統計分析平臺為例,演示這一功能的使用。在友盟統計分析中,我們需要根據渠道進行統計,比如google,百度,應用寶等渠道的活躍新增等,友盟的SDK是在AndroidManifest里配置一個name為UMENG_CHANNEL的 meta-data,這樣這個meta-data的值就表示這個apk是哪個渠道,我們版本發布有幾十個渠道,以前ant打包的時候是采用文字替換的辦法,現在Gradle有更好的處理辦法,那就是manifestPlaceholders,它允許我們動態替換我們在AndroidManifest文件里定義的占位符。

<meta-data android:value="${UMENG_CHANNEL_VALUE}" android:name="UMENG_CHANNEL"/> 
</div>

如上${UMENG_CHANNEL_VALUE}就是一個占位符,然后我們在gradle的defaultConfig;里這樣定義腳本:

android {
    defaultConfig {
        manifestPlaceholders = [UMENG_CHANNEL_VALUE: 'dev']
    }
}

以前的意思就是我們的默認配置里AndroidManifest的${UMENG_CHANNEL_VALUE}占位符會被dev這個字符串所替換,也就說默認運行的版本是一個開發板。以此類推,我們其他渠道的版本就可以這樣定義:
android  {
    productFlavors {
        google{
            applicationId "org.flysnow.demo.google"
            manifestPlaceholders.put("UMENG_CHANNEL_VALUE",'google')
        }
        baidu{
            applicationId "org.flysnow.demo.baidu"
            manifestPlaceholders.put("UMENG_CHANNEL_VALUE",'baidu')
        }
    }
}

這樣有多少個渠道就做多少次這樣的定義,即可完成分渠道統計。但是如果上百個渠道,這樣一個個寫的確太累,很麻煩,我們繼續研究,同學們有沒有發現,我們的渠道名字和我們的flavorName一樣,我們用這個flavorName作為UMENG_CHANNEL_VALUE不就好了嗎,可以批量的替換嗎?當然可以,這又體現了我們Gradle的強大和靈活之處。
productFlavors.all { flavor ->
        manifestPlaceholders.put("UMENG_CHANNEL_VALUE",name)
    }

循環每個flavor,并把他們的UMENG_CHANNEL_VALUE設置為他們自己的name名字,ok,搞定。

自定義你的BuildConfig

BuildConfig.java是Android Gradle自動生成的一個java類文件,無法手動編譯,但是可以通過Gradle控制,也就是說他是動態可配置的,有了這個功能就很好玩了,這里以生產環境和測試環境為例來說明該功能的使用。

我們在開發App的時候免不了要和服務器進行通信,我們的服務器一般都有生產和測試環境,當我們處理開發和測試的時候使用測試環境進行調試,正式發布的時候使用生成環境。以前的時候我們通過把不同的配置文件打包進APK中來控制,現在不一樣了,我們有更簡便的方法,這就是 buildConfigField。

android {
    defaultConfig {
        buildConfigField 'String','API_SERVER_URL','"http://test.flysnow.org/"'
    }
    productFlavors {
        google{
            buildConfigField 'String','API_SERVER_URL','"http://www.flysnow.org/"'
        }
        baidu{
            buildConfigField 'String','API_SERVER_URL','"http://www.flysnow.org/"'
        }
    }
}

buildConfigField 一共有3個參數,第一個是數據類型,就是你定義的常量值是一個什么類型,和Java的類型是對等的,這里是String。第二個參數是常量名,這里是 API_SERVER_URL。第三個參數是常量值。如此定義之后,就會在BuildConfig.java中生成一個常量名為 API_SERVER_URL的常量定義。默認配置的生成是:

public final static String API_SERVER_URL = "http://test.flysnow.org/" 
</div>

當是baidu和google渠道的時候生成的就是http://www.flysnow.org/了。這個常量可以在我們編碼中引用。在我們進行打包的時候會根據Gradle配置動態替換。

我們發現一般渠道版本都是用來發布的,肯定用的是生產服務器,所以我們可以使用批處理來搞定這個事情,而不用在一個個渠道里寫這些配置。

productFlavors.all { flavor ->  buildConfigField 'String','API_SERVER_URL','"http://www.flysnow.org/"'  } 
</div>

此外,比如Gradle的resValue,也是和buildConfigField,只不過它控制生成的是資源,比如我們在android的 values.xml定義生成的字符串。可以用它來動態生成我們想要的字符串,比如應用的名字,可能一些渠道會不一樣,這樣就可以很靈活的控制自動生成,關于resValue詳細介紹請參考相關文檔,這里不再舉例說明。

插裝測試覆蓋率代碼

代碼覆蓋率現在已經成為檢驗單元測試是否覆蓋到的一種手段,Android Gradle提供了原生的用于單元測試的代碼覆蓋率,這個就是jacoco。今天我們不談這個,我想要的是在我們生成的APK包中已經包含了檢測代碼覆蓋率的代碼,這樣當我們安裝APK后運行進行一些測試的時候,這些檢測代碼覆蓋率的代碼就會被執行到,這樣最后我們導出一份代碼測試覆蓋率的文件,然后生成查看測試覆蓋率報告看哪些覆蓋到,哪些沒有覆蓋到。這種場景在檢測測試工程師測試功能以及Android UI自動化測試是否完全覆蓋尤為有效。這里代碼覆蓋率框架我選擇的是emma,一來這個在Ant打包的時候一直在用,二來它具有很方便的插裝功能。

emma插裝的是class文件,所以我們只能在編譯完java文件生成class文件后進行插裝,這是我們進行覆蓋率代碼插裝的最好時機。找到了時機,那么具體對應在Gradle腳本上是哪呢?還記不記得我們上面講的applicationVariants,每一個 applicationVariant都有一個javaCompile屬性,javaCompile是一個JavaCompile類型的Task,這個就是負責編譯java代碼的。是Task就有doLast方法,就是在這個任務本身完成之后要做的事情,我們就是在這個方法里進行我們的代碼覆蓋率的安裝。一般我們這個插裝只是在特性情況下,那么我們新增一個特殊的flavor好了,專門做這個使用,這里我姑且叫feature。

applicationVariants.all { variant ->
    //為feature 版本加上代碼覆蓋率
    if('feature'.equals(variant.flavorName)){
        variant.javaCompile.doLast {
            def coverageFile=file('out/coverage.em')
            if(coverageFile.exists()){
                coverageFile.delete()
            }
            javaexec {
                main 'emma'
                args 'instr','-ip',variant.javaCompile.destinationDir,'-m','overwrite','-out','out/coverage.em'
                classpath files(new File(getSdkDirectory(),'tools/lib/emma.jar'))
            }
        }
    }
}

非常簡單,我們使用javaexec命令執行java應用程序進程插裝,插裝模式使用的是overwrite,就是插裝后覆蓋源文件。 getSdkDirectory()函數獲取你電腦上的Android SDK目錄,這里我們使用SDK自帶的emma,保持每個人的統一。另外注意進行代碼覆蓋率插裝的APK不能進行代碼混淆,這個很簡單,為feature flavor指定不混淆的proguardFile覆蓋默認的proguardFile即可。最后該APK需要emma的框架代碼,所以要配置 feature flavor的特殊依賴信息。
dependencies {
    compile fileTree(dir: 'libs', include: '*.jar')
    featureCompile files(new File(android.getSdkDirectory(),'tools/lib/emma_device.jar'))
}

dexOptions javaMaxHeapSize

在Gradle 進行dex的可能會遇到內存不夠用的情況,錯誤信息大概是java.lang.OutOfMemoryError: GC overhead limit exceeded。這個時候只需要配置dexOptions的javaMaxHeapSize大小即可,我這里配置4g:

dexOptions {  javaMaxHeapSize "4g" } 
</div>

結束語

到這里Android Gradle在項目中的大概使用就著一些了,當然不用的項目可以定制的程度不一樣,關于更詳細和更深入的介紹可以參考我之前翻譯的官方文檔 https://github.com/rujews/android-tech-docs/blob/master/new-build-system/user-guide/README.md

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!