版權聲明:本文為LooperJing原創文章,轉載請注明出處!
·
本來這篇要寫Android性能優化的,個人時間比較少,每天加班到很晚,寫博客的時間就很少了,但是Gradle系列的文章還沒有寫完,所以補一篇,在Gradle系列第(二)篇---Gradle編程主要對象主要寫了Gradle中的幾個對象(Project,Settings,Gradle,Task、Action),現在聊一聊Android Studio中的gradle常見的功能需求。如果你還沒有閱讀過我的前兩篇博客Gradle系列第(一)篇---Groovy語法初探和Gradle系列2---Gradle編程主要對象,可以先看一下,有助于本文的理解,好啦,各位看官準備好瓜子花生,接下來一大篇文章嘩啦啦的來了。不過不用擔心,這篇博客仍然是面向基礎。
讀完這篇博客,你會了解到這些內容
- 1、Android的構建文件
- 2、全局參數配置
- 3、用腳本更改項目結構
- 4、多種apk的生成
- 5、簽名的配置與使用
- 6、項目混淆(Proguard)
- 7、gradle多渠道打包
- 8、APK需求定制的案例
- 9、動態參數配置
- 10、gradle依賴管理
- 11、gradle.properties文件配置
- 12、jar文件輸出
一、AS項目構建文件的簡單解釋
一個AS項目結構大概像下面這樣子
如藍色條所示,項目中總共包含了6個構建文件(不算Library中的gradle),我們先從宏觀的方面了解一下,每個構建文件的作用是啥?
- 1、這個文件是app文件夾下這個Module的gradle配置文件,也可以算是整個項目最主要的gradle配置文件,比如自動打包debug,release,beta等環境,簽名,多渠道打包,混淆等操作都可以在這里面寫。每一個Module都需要有一個gradle配置文件。
- 2、我們主要看下gradle-wrapper.properties這個文件的內容
#Mon Dec 28 10:00:20 PST 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
可以看到里面聲明了gradle的目錄與下載路徑以及當前項目使用的gradle版本,這些默認的路徑我們一般不會更改的,有時候導入一個新項目,gradle版本不對,可以在這里修改。
- 3、這個文件是整個項目的gradle基礎(全局)配置文件,內容主要包含了兩個方面:一個是聲明倉庫的源,這里可以看到是指明的jcenter(), 之前版本則是mavenCentral(), jcenter可以理解成是一個新的中央遠程倉庫,兼容maven中心倉庫,而且性能更優。另一個是聲明了android gradle plugin的版本。allprojects:中定義的屬性會被應用到所有 moudle 中,但是為了保證每個項目的獨立性,我們一般不會在這里面操作太多共有的東西。
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
所有通過gradle導入的jar包都是從http://bintray.com/bintray/jcenter這個中央倉庫上扒下來的。如果你需要的jar包在這個網站上沒有,那就無法通過gradle的方式來導入哦。
- 4、這個里面可以配置參數,然后在其他build.gradle中引用,后面會講例子,如何動態配置參數。
- 5、這里主要指定了ndk和SDK的路徑
ndk.dir=G\:\\Users\\wangjing\\AppData\\Local\\Android\\sdk\\ndk-bundle
sdk.dir=G\:\\Users\\wangjing\\AppData\\Local\\Android\\sdk
- 6、setting.gradle最關鍵的內容就是告訴Gradle這個multiprojects包含哪些子projects,當你的app只有一個模塊的時候,你的setting.gradle將會是這樣子的:
include ':app'
當你的app有多個模塊的時候,你的setting.gradle將會是這樣子的
include ':app', ':library',。。。。
setting.gradle文件將會在gradle初始化時期執行,關于初始化時期,可以查看上一篇博客,并且定義了哪一個模塊將會被構建。舉個例子,上述setting.gradle包含了app模塊,setting.gradle是針對多模塊操作的,所以單獨的模塊工程完全可以刪除掉該文件。在這之后,Gradle會為我們創建一個Setting對象,每一個settings.gradle都會轉換成一個Settings對象,并為其包含必要的方法,你不必知道Settings類的詳細細節,但是你最好能夠知道這個概念。另外可以在settings做一些初始化的工作,后面介紹。
讀到這里做個總結
- build.gradle:控制每個Module的構建過程
- gradle.properties:設置gradle腳本中的參數
- local.properties:gradle的SDK和NDK環境變量配置
- gradle.properties:用于配置參數信息
- setting.gradle :配置gradle的多項目管理
二、實用技能精講
1、全局參數配置
通常我們的項目都有很多的Module,像我現在公司的項目就有十幾個,那么每個Module里面的gradle文件通常都有類似這樣的配置。
android {
compileSdkVersion 24
buildToolsVersion "24.0.0"
defaultConfig {
applicationId "com.zhangwan.www.gradle"
minSdkVersion 15
targetSdkVersion 24
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}
這些配置對于每個Module來說,最好統一,我把它定義在項目根目錄的gradle文件中,如下。
//全局配置
ext {
minSdkVersion =15
targetSdkVersion =24
compileSdkVersion =24
buildToolsVersion ="24.0.0"
versionCode =1
versionName="1.0"
}
定義好了,我們可以在各個Module的gradle文件文件中引用,如下:
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}
利用Gradle全局變量,對于多Module有很大的好處,方便統一,除了上面的列子,在舉個例子。上面全部按照單個的屬性配置的,對于相關的屬性,可以將他們寫到一個列表中,下面定義了一個dependencies_config的列表。
ext{
dependencies_config=[supportv7:"com.android.support:appcompat-v7:25.0.0"]
}
在Module中,這樣引用
dependencies {
compile rootProject.ext.dependencies_config.supportv7
.....
}
2、項目結構更改
sourceSets 的作用是重新定義資源文件位置,比如
android {
compileSdkVersion 24
buildToolsVersion "24.0.0"
sourceSets{
main{
res.srcDirs=['src/main/res','src/main/res/layout/activity','src/main/res/layout/fragment']
}
}
}
在你Sync Now之后,會出現activity和fragment兩個文件夾
最常見的是下面這塊代碼,當Eclipse項目轉到Studio的時候,需要重新指定一些文件的位置。
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs']
}
}
sourceSets的用法就是這樣,可以重新指定文件目錄,但是讀到這,可能有些人心中有個問題,為什么sourceSets ,defaultConfig這樣的東東要寫在android的大括號中。換言之,android這個大括號里面還能寫什么東西,我來列舉一下。
android {
defaultConfig {
//默認配置項,defaultConfig就是程序的默認配置,注意,如果在 AndroidMainfest.xml里面定義了與這里相同的屬性,會以這里的為主。
}
buildTypes {
// 編譯配置,release或debug版本的內容
}
compileOptions {
// Java 的版本配置
}
sourceSets {
//源碼設置(項目目錄結構的設置)
}
packagingOptions {
//打包時的相關配置
}
lintOptions {
//編譯的 lint 開關,程序在buid的時候,會執行lint檢查,有任何的錯誤或者警告提示,都會終止構建,我們可以將其關掉。
//abortOnError false
}
productFlavors {
//產品發布的一些東西,比如渠道、包名等
flavor1 {
}
flavor2 {
}
}
signingConfigs {
//簽名的配置
release {
}
}
testOptions{
//測試配置,TestOptions類型
}
aaptOptions{
//aapt配置,AaptOptions類型
}
lintOptions{
//lint配置,LintOptions類型
}
dexOptions{
//dex配置,DexOptions類型
}
compileOptions{
// 編譯配置,CompileOptions類型
}
packagingOptions{
// PackagingOptions類型
}
jacoco{
//JacocoExtension類型。 用于設定 jacoco版本
}
splits{
//Splits類型。
}
}
在DSL文檔中,以上每個類型都有它的詳細配置選項,一般常見的設置就是上面啦,如果你覺得有的不太了解,看下面之后就了解了。
3、多種apk的生成
默認studio生成的buildTypes是像下面這樣的,但是呢,我還想要其他的變種類型。
buildTypes {
release {
minifyEnabled false// 不混淆
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
我們可以這樣添加
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
r1{
applicationIdSuffix ".r1"
}
r2{
applicationIdSuffix ".r2"
}
r3{
applicationIdSuffix ".r3"
}
}
通過這樣就可以得到多種變種app,執行assemble這個task,打出所有apk。
總共得到系統默認有的release和debug兩個apk,額外還有r1,r2,r3三個不同的apk。
那么applicationIdSuffix是什么呢,逆向r1包看看。
系統通過包名來區分應用,這種方式無非就是在包名后面加上了一個后綴r1。
4、簽名的配置與使用
上面打出的包都是沒有指定簽名的,我們要配置一個簽名,首先需要生成簽名文件。我生成的簽名文件是1.jks
signingConfigs{
signR1{
storeFile file("build/1.jks");
storePassword "123456"
keyAlias "xxx"
keyPassword "123456"
}
signR2{
storeFile file("build/2.jks");
storePassword "123456"
keyAlias "xxx"
keyPassword "123456"
}
}
簽名在signingConfigs中配置,signR1,signR2是簽名的名字,在buildTypes中使用。
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
r1{
signingConfig signingConfigs.signR1
applicationIdSuffix ".r1"
}
r2{
signingConfig signingConfigs.signR2
applicationIdSuffix ".r2"
}
}
加上簽名后打的包是這樣,跟未加簽名相比較,多了app-r1.apk,app-r2.apk。
5、項目混淆(Proguard)
面對眾多的渠道,打包也有很多不同的需求。 比如 debug版,release版,dev版等等。 有時候不同的版本中使用到的不同的服務端api域名也不相同。 比如 debug_api.com,release_api.com,dev_api.com等等。不同的版本對應了不同的 api 域名,還可能對應不同的 icon 等。渠道首發包通常需要要求在歡迎頁添加渠道的logo等。下面我們開始進行打包。首先進行混淆設置,混淆需要buildTypes中配置,在上面說過,默認生成的buildTypes是這樣子的
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
其中,proguard-android.txt是在你的sdk\tools\proguard目錄下。minifyEnabled:表示是否開啟混淆,默認為false;proguardFiles:混淆配置文件,一般就采用項目中默認的proguard-rules.pro文件。在這個文件中寫我們的混淆規則,比如:
-keepclasseswithmembernames class * { # 保持 native 方法不被混淆
native <methods>;
}
-keepclassmembers enum * { # 保持枚舉 enum 類不被混淆
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keep class * implements android.os.Parcelable { # 保持 Parcelable 不被混淆
public static final android.os.Parcelable$Creator *;
}
有這些還不夠,還需要在gradle中開啟混淆
buildTypes {
release {
// 不顯示 Log
buildConfigField "boolean", "LOG_DEBUG", "false"
shrinkResources true
signingConfig signingConfigs.release
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard.cfg'
}
debug {
// 顯示 Log
buildConfigField "boolean", "LOG_DEBUG", "true"
signingConfig signingConfigs.debug
}
}
我們設置minifyEnabled true,就會在打包的時候進行代碼混淆處理. 其中proguard-android.txt不用管,在sdk目錄里面,我們主要是配置了proguard.cfg文件。可能大家直接在android studio創建項目不會有這個文件,而是proguard-rules.pro文件,其實一樣的,我這里是因為項目是從eclipse遷移過來的,之前在eclipse上混淆是proguard.cfg文件.
6、gradle多渠道打包
- 1、第一步 在AndroidManifest.xml里配置PlaceHolder
<meta-data
android:name="MY_CHANNEL"
android:value="${MY_CHANNEL}" />
- 2、第二步 在build.gradle設置productFlavors
productFlavors{
xiaomi {
//用gradle修改AndroidManifest.xml中的meta-data元素值
manifestPlaceholders = [MY_CHANNEL: "xiaomi"]
}
_360 {
manifestPlaceholders = [MY_CHANNEL: "_360"]
}
baidu {
manifestPlaceholders = [MY_CHANNEL: "baidu"]
}
huawei{
manifestPlaceholders = [MY_CHANNEL: "huawei"]
}
}
或者批量修改
productFlavors{
xiaomi {}
_360 {}
baidu {}
huawei{}
}
productFlavors.all {
flavor -> flavor.manifestPlaceholders = [MY_CHANNEL: name]
}
最后,最好在defaultConfig中定義一個默認的渠道
defaultConfig{
manifestPlaceholders = [ MY_CHANNEL:"xiaomi" ]
}
到此配置完成,可以執行命令了。
- 3、去工程的根目錄,也就是有gradlew文件的目錄,打開命令行,輸入命令:
./gradlew assemble
這時候你去app/build/outputs/apk中就能看到自動打好的渠道包了。
./gradlew assembleRelease
只打Release包
./gradlew assembleDebug
只打Debug包
./gradlew assemblebaidu
只打360的渠道包
./gradlew assemblebaiduRelease
不想敲命令行的,調起下面這個面板打包
7、APK需求定制
上面說了一下打包,在打包的時候,一些特殊化的操作,比如修改指定apk Logo,apk重命名,這些怎么搞?
- 渠道包重命名
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
if (outputFile != null && outputFile.name.endsWith('.apk')) {
File outputDirectory = new File(outputFile.parent);
def fileName
if (variant.buildType.name == "release") {
fileName = "wangjing_${variant.productFlavors[0].name}.apk"
} else {
fileName = "wangjing_${variant.productFlavors[0].name}_beta.apk"
}
output.outputFile = new File(outputDirectory, fileName)
}
}
}
- 根據渠道修改APP名稱
buildTypes {
debug {
// 顯示Log
buildConfigField "boolean", "LOG_DEBUG", "true"
//重命名
resValue("string","app_name","DEBUG")
versionNameSuffix "-debug"
minifyEnabled false
zipAlignEnabled false
shrinkResources false
signingConfig signingConfigs.debug
}
release {
// 不顯示Log
buildConfigField "boolean", "LOG_DEBUG", "false"
//重命名
resValue("string","app_name","DEBUG")
//混淆
minifyEnabled true
//加載默認混淆配置文件
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
//簽名
signingConfig signingConfigs.release
}
}
其中 resValue("string","app_name","DEBUG") 表示一個string 類型的變量app_name的值是DEBUG,做了上面的配置之后,需要將string.xml的app_name刪掉,因為gradle編譯的時候,會將腳本中的配置跟string.xml的合并。
8、動態參數配置
signingConfigs{
release{
storeFile file("build/mykey.jks")
storePassword "123456"
keyAlias "123456"
keyPassword "123456"
}
}
上面的這段配置,有個缺點,就是值直接寫死了,我們可以動態配置參數。在哪里配置呢,一開始就說了,在gradle.properties中配置參數。如下:
systemPro.keyAliasPassword=123456
systemPro.keyAlias=123456
systemPro.keyStorePassword=123456
systemPro.keyStore=mykey.jks
配置好了,就可以“到處”使用了
signingConfigs{
release{
storeFile System.properties["keyStore"]
storePassword System.properties["keyStorePassword"]
keyAlias System.properties["keyAlias"]
keyPassword System.properties["keyAliasPassword"]
}
debug{
storeFile file("mykey.jks")
storePassword "123456"
keyAlias"123456"
keyPassword "123456"
}
}
9、gradle依賴管理
比如我們想依賴個support-v4包,直接一句話:
compile 'com.android.support:support-v4:23.1.1'
一個依賴需要定義三個元素:group,name和version。group意味著創建該library的組織名,通常這會是包名,name是該library的唯一標示。
上述的代碼是基于groovy語法的,所以其完整的表述應該是這樣的:
compile group: 'com.android.support:', name: 'support-v4', version:'23.1.1'
有些時候,你可能需要和sdk協調工作。為了能順利編譯你的代碼,你需要添加SDK到你的編譯環境。你不需要將sdk包含在你的APK中,因為它早已經存在于設備中,不需要在compile,我們總共有5個不同的配置:
compile是默認的那個,其含義是包含所有的依賴包,即在APK里,compile的依賴會存在。
apk的意思是apk中存在,但是不會加入編譯中,這個貌似用的比較少。
provided的意思是提供編譯支持,但是不會寫入apk。
testCompile和androidTestCompile會添加額外的library支持針對測試。
通常項目的Module很多,依賴也非常多,為了方便管理,我們應該將這些依賴寫到一個全局的地方,可以供其他module使用。這種思想也是第一小節所提的全局參數的配置。依賴管理可以參考:http://stormzhang.com/android/2016/03/13/gradle-config/
10、gradle.properties文件配置
gradle.properties常見配置比如有:
開啟并行編譯:加快gradle 的編譯
org.gradle.parallel=true開啟編譯守護進程:該進程在第一次啟動后回一直存在,當你進行二次編譯的時候,可以重用該進程。
org.gradle.daemon=true加大可用編譯內存:
org.gradle.jvmargs=-Xms256m -Xmx1024m
11、jar文件輸出
android Studio常常有輸出jar包的需求,只要下面這段代碼即可:
task makeJar(type: Copy) {
delete 'build/libs/my.jar'
from('build/intermediates/bundles/release/')
into('build/libs/')
include('classes.jar')
rename ('classes.jar', 'my.jar')
}
OK,Gradle研究了一個多星期,這篇博客耗時兩個晚上,終于結束,另外如果時間來的急,在寫一篇Gradle系列4或5,因為感覺自己還沒有講清楚,側重多個Module中gradle的使用與Gradle常用命令的使用,下篇博客繼續性能優化系列的更新,每一次寫博客都花費很多的時間和精力也是一次鍛煉,跟他人分享自己的學習成果,最后附上參考資料,比我寫的好。