Gradle for Android(九) 高級構(gòu)建定制

現(xiàn)在你已經(jīng)知道了Gradle是如何工作的,如何創(chuàng)建你自己的任務(wù)和插件,如何運行測試,以及如何設(shè)置持續(xù)集成,你差不多可以稱呼自己為Gradle專家了。本章會包含一些之前沒有提及的竅門和技巧,使使用Gradle進行構(gòu)建、開發(fā)和部署變得簡單。

本章內(nèi)容有:

  • 減小APK體積
  • 加速構(gòu)建
  • 忽略Lint檢查
  • 在Gradle中使用Ant
  • 高級應用部署

減小APK體積

在過去的幾年里,apk文件的體積有了顯著的增大。這里有幾個原因:Android開發(fā)者有更多的庫可以使用,增加了更多的密度,以及應用有了更多的功能。

保證APK有更小的體積是很好的想法。不僅是因為Goole Play有50M大小的限制,還因為更小的APK意味著用戶可以更快地下載并安裝應用,以及占用更少的內(nèi)存。

本節(jié),我們會討論幾個Gradle構(gòu)建文件的屬性,來幫助減小APK文件體積。

ProGuard

ProGuard是一個java工具,在編譯階段不僅可以壓縮,還可以優(yōu)化,混淆,預先審核代碼。它會遍歷應用中的所有代碼路徑,找到不用的代碼并刪除它。ProGuard也會重命名你的類和成員。這個過程可以減少應用的內(nèi)存占用,并增大反編譯的難度。

Gradle Android插件有一個minifyEnabled屬性可以設(shè)置開啟ProGuard:

android {
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

當你設(shè)置minifyEnabled為true后,proguardRelease任務(wù)就會執(zhí)行,并且在構(gòu)建過程中觸發(fā)ProGuard。

最好在打開ProGuard后重新檢查一下整個應用,因為它可能剔除一些你仍然需要的代碼。這是ProGuard令很多開發(fā)者苦惱的問題。為了解決這個問題,你可以定義ProGuard規(guī)則,將不需要移除或者混淆的類排除出去。proguardFiles屬性用來定義包含ProGuard規(guī)則的文件。比如,要保持一個類不變,你可以添加如下規(guī)則:

-keep public class <MyClass>

getDefaultProguardFile('proguard-android.txt')方法從proguard-android.txt文件獲取ProGuard設(shè)置,該文件在Android SDK的tools/proguard目錄下。proguard-rules.pro文件由Android Studio默認添加到模塊中,所以你可以很簡單的在該文件添加這個模塊的規(guī)則。

ProGuard規(guī)則對于每個app或者library都是不同的,所以本書不會深入研究細節(jié)。如果你想了解更多相關(guān)內(nèi)容,可以瀏覽http://developer.android.com/tools/help/proguard.html

除了壓縮Java代碼,也可以壓縮不用的資源。

壓縮資源

Gradle和Gradle Android插件可以在構(gòu)建時去掉不用的資源。這在你有一些舊的忘掉刪除的資源時非常有用。另一個場景是你引入了一個包含很多資源的庫,但你只用了很少一部分。你可以打開資源壓縮來處理這種情況。有兩種方式可以進行資源壓縮:自動和手動。

自動壓縮

最簡單的方式是在你的構(gòu)建中配置shrinkResources屬性。如果設(shè)置為true,Android構(gòu)建工具會自動檢測哪些資源沒有用到,不應該包含到APK中。

使用這個特性有一個需求,就是你也必須使用ProGuard。這源于資源壓縮的工作方式,因為Android構(gòu)建工具在引用資源的代碼被移除之前無法確定哪些資源是沒有用到的。

下面的代碼片段展示了如何在一個特定的構(gòu)建類型中配置自動壓縮資源:

android {
    buildTypes {
        release {
            minifyEnabled = true
            shrinkResources = true
        }
    }
}

如果你想準確知道開啟資源壓縮后,APK減小了多少,你可以運行shrinkReleaseResources任務(wù)。這個任務(wù)會打印出包減小的大小:

:app:shrinkReleaseResources
Removed unused resources: Binary resource data reduced from 433KB
to 354KB: Removed 18%

通過給構(gòu)建命令添加--info標識,你可以得到關(guān)于APK去除的資源的詳細概況:

$ gradlew clean assembleRelease --info

使用這個標識后,Gradle會打印出構(gòu)建過程的更多的信息,包括最后的輸出不包含的資源。

自動資源壓縮的一個問題是它可能移除過多的資源。尤其是被動態(tài)使用的資源可能被意外刪除。為防止這種情況,你可以在res/raw/目錄放置keep.xml文件來定義一些特殊情況。一個簡單的keep.xml文件如下:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@layout/keep_me,@layout/also_used_*"/>

keep.xml本身會在最終的輸出中被移除。

手動壓縮

一個不那么極端的移除資源的方式是除去特定的語言文件或者特定密度的圖像。某些庫,比如Google Play Services,包含很多語言。如果你的應用只支持一種或者兩種語言,就沒必要在最終的APK中包含所有的語言文件。你可以使用resConfigs屬性來配置你想保留的資源,其余的將被移除。

如果你只想保留英語、丹麥語和荷蘭語的字符串資源,可以如下配置:

android {
    defaultConfig {
        resConfigs "en", "da", "nl"
    }
}

你也可以配置密度:

android {
    defaultConfig {
        resConfigs "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"
    }
}

你還可以結(jié)合語言和密度。實際上,可以使用此屬性限制所有類型的資源。

如果你在艱難地設(shè)置ProGuard,或者你只想去除應用不支持的語言和密度資源,使用resConfigs是一個很好的壓縮資源的方式。

加速構(gòu)建

很多剛開始使用Gradle的Android開發(fā)者會抱怨它編譯時間長。Gradle比Ant的構(gòu)建時間要長,因為每次你執(zhí)行任務(wù),它都需要走完生命周期的三個階段。這使它的整個過程是可配置的,但是也會變慢很多。幸運的是,有幾種方式可以加快Gradle的構(gòu)建。

Gradle屬性

一個加速Gradle構(gòu)建速度的方法是修改默認的配置。我們在第五章已經(jīng)提及了并行構(gòu)建執(zhí)行,除此之外,你仍然可以調(diào)整一些其他的配置。

回顧一下,你可以通過設(shè)置項目根目錄的gradle.properties文件打開并行構(gòu)建。只需要添加如下屬性:

org.gradle.parallel=true

另一個簡單的方式是打開Gradle守護進程,這會在你第一次構(gòu)建的時候啟動一個后臺進程。任何串行構(gòu)建都會重復利用這個后臺進程,這就減少了啟動成本。這個進程在你使用Gradle期間會一直保留,直到空閑3個小時后才會殺死。在你短時間內(nèi)多次使用Gradle的情況下,使用守護進程是很有用的。可以如下打開守護進程:

org.gradle.daemon=true

在Android Studio中,Gradle守護進程是默認打開的。這意味著在IDE第一次構(gòu)建之后,接下來的構(gòu)建會稍微快一些。如果你通過命令行進行構(gòu)建,Gradle守護進程是關(guān)閉的,除非你在屬性中打開它。

為了加速編譯,你可以修改JVM的參數(shù)。你可以使用jvmargs這個Gradle屬性來設(shè)置JVM內(nèi)存分配池的值。兩個對構(gòu)建速度有直接影響的參數(shù)是XmsXmxXms參數(shù)用來設(shè)置初始的內(nèi)存值,Xmx參數(shù)用來設(shè)置最大值。你可以在gradle.properties文件中手動設(shè)置這兩個值:

org.gradle.jvmargs=-Xms256m -Xmx1024m

你需要設(shè)置一個值和一個單位(k,m,g)。最大內(nèi)存分配(Xmx)默認是256MB,起始內(nèi)存分配(Xms)默認沒有設(shè)置。可設(shè)置的值依賴你電腦的內(nèi)存大小。

最后一個你可以配置的屬性是org.gradle.configureondemand。如果你的工程有幾個模塊,這會非常有用。因為它可以忽略當前任務(wù)不需要的模塊,來嘗試減少配置階段的耗時。如果該屬性設(shè)置為true,Gradle在運行配置階段之前,會嘗試計算出哪些模塊更改了配置,哪些沒有。如果你的項目是單app或者library的項目,這個特性將不會有太大的作用。如果你有很多松散耦合的模塊,這個特性可以節(jié)省你很多構(gòu)建時間。

系統(tǒng)范圍的Gradle屬性
如果你想將這些屬性應用到所有的基于Gradle的項目中,你可以在home目錄的.gradle文件夾創(chuàng)建一個gradle.properties文件。在home目錄設(shè)置這些屬性是很好的實踐。因為通常你想降低在構(gòu)建服務(wù)器上的內(nèi)存消耗,構(gòu)建時間相對來說不是那么重要。

Android Studio

Android Studio的設(shè)置中同樣包含這些可以加速構(gòu)建時間的配置。打開Settings,導航到Build,Execution,Deployment|Compiler。該頁面,你可以找到并行構(gòu)建、JVM選項,configure on demand等配置。這些配置只在基于Gradle的Android模塊中才會出現(xiàn)。

圖1

性能分析

如果你想找到哪一部分減慢了構(gòu)建速度,可以分析整個構(gòu)建過程。你可以在執(zhí)行Gradle任務(wù)的時候添加--profile標識。有了這個標識,Gradle會生成一個性能報告,告訴你哪一部分耗時過長。知道了瓶頸,你就可以進行必要的優(yōu)化。報告以HTML的形式保存在模塊的build/reports/profile目錄。

圖2 性能報告

性能報告展示任務(wù)執(zhí)行過程中,每個階段耗時的概況。Summary展示了Gradle在配置階段為每一個模塊消耗的時間。Dependency Resolution展示了每個模塊解決依賴的時間。Task Execution展示了任務(wù)執(zhí)行細節(jié),包含每個單獨任務(wù)的耗時,排列順序從耗時高到耗時低排序。

Jack和Jill

如果你想使用實驗工具,你可以打開Jack和Jill來加速構(gòu)建時間。Jack(Java Android Compiler Kit)是一個新的Android構(gòu)建工具鏈,可以直接將Java資源代碼編譯成dex格式。它有自己的.java庫格式,同時接管了打包和壓縮。**Jill(Jack Intermediate Library Linker)是一個將.aar.jar文件轉(zhuǎn)換為.jack庫的工具。這些工具仍然是實驗性質(zhì)的,但它們可以用來加速構(gòu)建時間,簡化Android構(gòu)建過程。雖然不建議在生產(chǎn)版本使用它們,但可以嘗試一下。

為了使用Jack和Jill,你需要使用21.1.1或更高版本的build tools,1.0.0或更高版本的Gradle Android插件。在defaultConfig塊打開Jack和Jill:

android {
    buildToolsRevision '22.0.1'
    defaultConfig {
        useJack = true
    }
}

你也可以在特定的構(gòu)建類型或product flavor中打開Jack和Jill。這種情況下,你可以繼續(xù)使用常規(guī)構(gòu)建工具鏈,并額外使用實驗性質(zhì)的構(gòu)建:

android {
    productFlavors {
        regular {
            useJack = false
        }

        experimental {
            useJack = true
        }
    }
}

一旦設(shè)置了useJack=true,精簡和混淆將不再使用ProGuard,但你仍然可以使用ProGuard規(guī)則語法來指定特定的規(guī)則和例外,同樣可以使用proguardFiles方法。

忽略Lint

在使用Gradle執(zhí)行release構(gòu)建時,Lint檢查將會執(zhí)行。Lint是一個靜態(tài)代碼檢查工具,會標識出你的布局和代碼中潛在的問題。在某些情況下,甚至會阻塞構(gòu)建過程。如果你的工程之前沒有使用過Lint,并且你想遷移到Gradle中,Lint檢查可能會有很多錯誤。為使構(gòu)建可以進行,你可以通過關(guān)閉abortOnError忽略Lint檢查,阻止它中斷構(gòu)建。這只是一個臨時解決方案,因為忽略Lint錯誤可能導致一些問題,比如丟失翻譯,這可能引起應用崩潰。代碼如下:

android {
    lintOptions {
        abortOnError false
    }
}

臨時關(guān)閉Lint檢查可以很容將Ant構(gòu)建遷移到Gradle中。另一種平滑過渡方式是在Gradle中執(zhí)行Ant任務(wù)。

在Gradle中使用Ant

如果你已經(jīng)花費了大量時間來設(shè)置Ant構(gòu)建,那么切換到Gradle可能聽起來會很恐怖。在這種情況下,Gradle不僅能夠執(zhí)行Ant任務(wù),還能擴展它們。這意味著你可以用很少的步驟從Ant遷移到Gradle,而不是花費好幾天來進行項目轉(zhuǎn)換。

Gradle為Ant集成使用Groovy的AntBuilder。AntBuilder使你可以執(zhí)行任何標準的Ant任務(wù),你自定義的Ant任務(wù)和整個Ant構(gòu)建。你還可以在Gradle構(gòu)建配置中定義Ant屬性。

在Gradle中運行Ant任務(wù)

Gradle可以直接運行Ant任務(wù)。你只需要為相應的Ant任務(wù)添加ant.前綴就可以了。比如,創(chuàng)建一個歸檔:

task archive << {
    ant.echo 'Ant is archiving...'
    ant.zip(destfile: 'archive.zip') {
        fileset(dir: 'zipme')
    }
}

這個任務(wù)在Gradle中定義,使用了echozip這兩個Ant任務(wù)。

你更應該首先考慮Gradle相同功能的任務(wù)。上例中,你可以定義相應的Gradle任務(wù):

task gradleArchive(type:Zip) << {
    from 'zipme/'
    archiveName 'grarchive.zip'
}

相應的Gradle任務(wù)更加簡潔和易于理解,也更加高效。

引入整個Ant腳本

如果你創(chuàng)建了一個Ant腳本來構(gòu)建應用,你可以使用ant.importBuild來引入整個構(gòu)建配置。所有的Ant targets會自動轉(zhuǎn)換為Gradle任務(wù),你可以通過原始名稱訪問它們。比如下面這個Ant構(gòu)建文件:

<project>
    <target name="hello">
        <echo>Hello, Ant</echo>
    </target>
</project>

你可以這樣引入到Gradle:

ant.importBuild 'build.xml'

hello任務(wù)將會出現(xiàn)在Gradle構(gòu)建中,你可以像常規(guī)Gradle任務(wù)一樣執(zhí)行它:

$ gradlew hello
:hello
[ant:echo] Hello, Ant

因為Ant任務(wù)被轉(zhuǎn)換為了Gradle任務(wù),所以你可以使用doFirstdoLast或者<<來進行擴展。比如,你可以在控制臺打印另一行:

hello << {
    println 'Hello, Ant. It\'s me, Gradle'
}

執(zhí)行結(jié)果為:

$ gradlew hello
:hello
[ant:echo] Hello, Ant
Hello, Ant. It's me, Gradle

你也可以依賴這些來自Ant的任務(wù)。比如:

task hi(dependsOn: hello) << {
    println 'Hi!'
}

結(jié)果如下:

$ gradlew intro
:hello
[ant:echo] Hello, Ant
Hello, Ant. It's me, Gradle
:hi
Hi!

如果需要,你甚至可以創(chuàng)建依賴Gradle任務(wù)的Ant任務(wù)。你需要在build.xml文件中為該任務(wù)添加depends屬性:

<target name="hi" depends="intro">
    <echo>Hi</echo>
</target>

如果你有一個很大的Ant構(gòu)建文件,并且你想保證任務(wù)名稱不重復,你可以在引入Ant任務(wù)時進行重命名:

ant.importBuild('build.xml') { antTargetName ->
    'ant-' + antTargetName
}

如果你決定重命名所有的Ant任務(wù),你需要時刻謹記如果你有Ant任務(wù)依賴于一個Gradle任務(wù),那么這個Gradle任務(wù)也需要添加前綴。否則,Gradle會找不到它并拋出UnknownTaskException

屬性

Gradle和Ant不僅可以共享任務(wù),你還可以在Gradle中定義屬性,然后在Ant構(gòu)建文件中使用它們。下面的Ant target打印一個version的屬性:

<target name="appVersion">
    <echo>${version}</echo>
</target>

你可以在Gradle的構(gòu)建配置中定義version屬性,屬性前添加ant.前綴,就像任務(wù)一樣。這是定義Ant屬性最簡短的方式:

ant.version = '1.0'

Groovy隱藏了很多實現(xiàn)細節(jié)。屬性定義的全寫如下:

ant.properties['version'] = '1.0'

運行version任務(wù),輸出如下:

$ gradlew appVersion
:appVersion
[ant:echo] 1.0

Gradle的深度Ant集成使你很容易的將基于Ant的構(gòu)建移植到Gradle中。

高級應用部署

在第四章,我們講解了幾種方式來創(chuàng)建同一個應用的不同版本,即使用構(gòu)建類型和product flavors。而某些情況下,使用一些特殊技巧將更加方便,比如APK分割。

分割APK

構(gòu)建變體可以看做單獨的實體,它有自己的代碼、資源和清單文件。另一方面,APK分割只影響應用的打包。編譯、壓縮和混淆等仍是共享的。這個機制允許你基于密度或者application binary interface(ABI)來分割APK。

你可以使用android塊中的splits塊來配置分割。配置密度分割,可以在splits塊中創(chuàng)建density塊。同理abi塊用于設(shè)置ABI分割。

如果你打開密度分割,Gradle會為每個密度創(chuàng)建單獨的APK。你可以手動去除不需要的密度,以加速構(gòu)建過程。如下代碼展示了如何打開密度分割,并去掉較低的密度:

android {
    splits {
        density {
            enable true
            exclude 'ldpi', 'mdpi'
            compatibleScreens 'normal', 'large', 'xlarge'
        }
    }
}

如果你只支持幾個密度,可以使用include來定義白名單。使用include,你首先需要reset()方法來將密度白名單列表置空。

compatibleScreens屬性是可選的,它會在manifest文件中注入匹配的節(jié)點。上例中配置應用支持normal及以上的屏幕,不支持小屏。

基于ABI分割APK也是同樣的方式,所有的屬性和密度分割一致,除了compatibleScreens

執(zhí)行配置了密度分割的構(gòu)建,Gradle會創(chuàng)建一個universal APK和幾個特定密度的APK。也就是說你會得到幾個APK文件:

app-hdpi-release.apk
app-universal-release.apk
app-xhdpi-release.apk
app-xxhdpi-release.apk
app-xxxhdpi-release.apk

使用APK分割有一個警告。如果你想向Google Play推幾個APK,你需要確保每個APK有不同的版本號。也就是說每個分割有一個單獨的版本號。幸運的是,現(xiàn)在你可以使用applicationVariants屬性。

下面的片段來自Gradle Android插件的文檔,展示了如何為每個APK生成不同的版本號:

ext.versionCodes = ['armeabi-v7a':1, mips:2, x86:3]

import com.android.build.OutputFile

android.applicationVariants.all { variant ->
    // assign different version code for each output
    variant.outputs.each { output ->
        output.versionCodeOverride = project.ext.versionCodes.get(output.getFilter(OutputFile.ABI)) * 1000000 + android.defaultConfig.versionCode
    }
}

這個小片段會檢查每個構(gòu)建變體使用的ABI,然后將一個乘數(shù)應用到版本代碼,以確保每個變體都有一個獨特的版本號。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,552評論 25 708
  • 這一章主要針對項目中可以用到的一些實用功能來介紹Android Gradle,比如如何隱藏我們的證書文件,降低風險...
    acc8226閱讀 7,695評論 3 25
  • 1.介紹 如果你正在查閱build.gradle文件的所有可選項,請點擊這里進行查閱:DSL參考 1.1新構(gòu)建系統(tǒng)...
    Chuckiefan閱讀 12,189評論 8 72
  • 上一章我們學習了Gralde的使用,創(chuàng)建和轉(zhuǎn)換Android工程。本章我們將深入了解構(gòu)建文件,學習一些有用的tas...
    sollian閱讀 1,383評論 0 3
  • 認識她是因為朋友追求她,我是朋友的軍師。說來對不起朋友,她和朋友的話越來越少,和我的話卻越來越多。順利成章的我約了...
    一個特別的人閱讀 500評論 0 1