Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Gradle如何管理Android Studio工程

Gradle如何管理Android Studio工程

編輯:關於Android編程

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'

和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()
    }
}

 

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'
}

開頭第一行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
        }
    }
  }

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

啟用proguard混淆

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

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

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

啟用zipAlign

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

android {
    buildTypes {
        release {
            zipAlignEnabled true
        }
   }
}

多渠道打包

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

android  {
    productFlavors {
        dev{
        }
        google{
        }
        baidu{
        }
    }
}

這樣當我們運行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
                    }
                }
            }
        }
    }
}

以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"/>

如上${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/"

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

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

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

此外,比如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"
}
  1. 上一頁:
  2. 下一頁: