最近在公司項目中需要利用同一套代碼打包出具有不同皮膚和有一些功能差異的兩個apk包,于是學習了一下Android Studio中利用gradle框架進行多渠道或多apk打包。
認識build.gradle腳本
Gradle是一種構建項目的框架,兼容Maven、Ant,為Java項目提供了很多插件去實現打包功能。Android Studio也集成了gradle構建項目框架,在一個Android Studio project中會自動生成一些gradle script文件,如下圖所示。
展開Gradle Scripts我們可以看到里面有兩個build.gradle文件和一個settings.gradle文件,其中的build.gradle(Project: GradleTestDemo)文件是整個工程的build文件,而build.gradle(Module: app)文件是工程下的一個Module的build文件,settings.gradle用于配置project,標明其下有幾個module,比如這里包含一個:app module。
在Android Studio工程目錄結構中一個Project可以有多個Module,Project可以理解為Eclipse下的一個Workspace,Module可以理解為Eclipse下的Project。當我們用Android Studio建立一個默認的工程時,它自動為我們建立了一個名字為app的默認Module。所以我們容易理解,一個Android Studio工程會有一個工程級別的build.gradle文件,同時如果有N個Module,就會有N個Module級別的build.gradle文件。
settings.gradle文件
settings.gradle 文件位于項目根目錄,用于指示 Gradle 在構建應用時應將哪些模塊包括在內。對大多數項目而言,該文件很簡單,只包括以下內容:
include ':app'
Project的build.gradle文件
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
// 指定依賴版本號為2.3.0的Android gradle build插件
classpath 'com.android.tools.build:gradle:2.3.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
//為所有的工程的repositories配置為jcenter
allprojects {
repositories {
jcenter()
}
}
//執行清除project的output文件的任務
task clean(type: Delete) {
delete rootProject.buildDir
}
在Project根目錄下的build.gradle文件被稱為頂級構建文件,為整個project的所有module配置build所依賴的一些資源及插件,頂級構建文件使用 buildscript {} 代碼塊來定義項目中所有模塊共用的 Gradle 存儲區和依賴項,比如指定依賴android gradle build插件的版本。在頂級構建文件中還可以配置一些project共用的構建任務,比如上面示例代碼中clean是一個delete類型的task,意思是在執行打包腳本前做一個清理工作,把項目輸出文件夾中的文件先全部清理干凈。
Module的build.gradle文件
//為這個工程的構建指定Android插件, 'com.android.application'這個插件中提供了下面要用的android{}閉包
//如果是一個android library,那么這里應寫成 apply plugin: ‘com.android.library’
apply plugin: 'com.android.application'
android {
/**
* 指定Gradle編譯所需要的Android API版本號,這表明當前App可以使用這個版本號以及低于這個版本的
* SDK版本中的API
*/
compileSdkVersion 25
//指定基于哪個版本的構建工具進行構建
buildToolsVersion '25.0.0'
/**
* 在defaultConfig {} 閉包中可以為所有構建變量設置默認值,這些設置可以在編譯系統中動態地復寫一些
* main/AndroidManifest.xml中的屬性,可以通過配置productFlavors來為不同版本的apk賦屬性值
* 比如applicationId是配置包名的,versionCode是版本號,versionName是版本名稱等
* 如果沒有其他的配置覆蓋,就會使用這里的值
*/
defaultConfig {
applicationId "com.study.gradletestdemo"
minSdkVersion 15
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
/**
* 在buildTypes {} 閉包中可以配置多種構建類型,默認有兩種構建類型:debug、release
* 還可以配置自定義的構建類型,比如internal 國內類型、external 國外類型等
* 這里做release類型下配置了代碼混淆文件
*/
buildTypes {
release {
minifyEnabled false
//指定混淆配置文件
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
/**
* productFlavors {} 閉包用于定義多種定制產品,在這里為不同版本類型的產品設置構建屬性,
* 它們將覆蓋defaultConfig{}中設置的相關值,Product flavors不是Gradle默認的構建設置項,一般用于
* 多渠道或多apk打包時自定義設置,下面示例代碼中設置了兩個產品,分別用于免費和收費發布,
* 這樣構建出來的apk可以在同一個應用市場或同一臺Android設備上同時存在
*/
productFlavors {
free {
applicationId 'com.study.gradletestdemo.free'
}
paid {
applicationId 'com.study.gradletestdemo.paid'
}
}
/**
* splits {} 閉包用于構建只支持特定屏幕像素密度或ABI的Android設備的apk
*/
splits {
// Screen density split settings
density {
// Enable or disable the density split mechanism
enable false
// Exclude these densities from splits
exclude "ldpi", "tvdpi", "xxxhdpi", "400dpi", "560dpi"
}
}
dependencies {
//依賴工程libs目錄下所有的jar包,也可以指定具體的每一個jar包,而不是采用*.jar通配符匹配的方式
compile fileTree(dir: 'libs', include: ['*.jar'])
//通過"包名:工程名:版本號"的形式來依賴當前工程需要的第三方類庫
compile 'com.android.support:appcompat-v7:25.0.0'
compile 'com.android.support.constraint:constraint-layout:1.0.0-beta2'
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
//測試依賴庫
testCompile 'junit:junit:4.12'
}
}
在每個 <project>/<module>/ 目錄下的build.gradle文件稱為模塊級構建文件,用于配置適用于其所在模塊的構建設置。可以通過配置這些構建設置來提供自定義打包選項(例如附加構建類型和產品定制),以及替換 main/AndroidManifest.xml或頂級 build.gradle 文件中的設置。
用Gradle進行多版本打包
用Gradle進行多版本打包主要有兩個使用場景:
1、同一個應用的不同版本。比如一個免費的版本和一個付費的專業版本。
2、同一個應用被打包成多個不同的apk以發布到Google Play商店。
綜合第1條和第2條還可以變幻出其他需求的多版本打包。
從上文中描述的Module的build.gradle文件可以知道Gradle通過設置buildTypes、productFlavors或splits來構建同一個工程的多種不同版本的應用,本文不討論splits的詳細用法,它的主要用途如上面的示例中說明。正如上文的示例代碼及注釋所表達的,一個Build Type設置對應生成一個apk文件,一個Product Flavor設置也對應一個apk文件,所以每一個組合(Build Type, Product Flavor)就生成一個特定版本的apk。以默認的 debug 和 release Build Types 為例,上面的例子會生成四個 Build Variants:
多渠道打包
多版本打包在國內Android開發比較典型的應用場景就是多渠道打包,由于國內做Android應用分發的市場很多,一般公司開發一款App如果要在國內發布就需要覆蓋幾乎所有主流的應用市場,這時為了做一些用戶量統計以及其他行為分析就需要為不同的應用市場出不同的App包。
在國內Android市場多渠道出包往往需要借助友盟統計來進行渠道分析,這種多渠道打包可能apk的所有信息一致,只需要配置包所帶的渠道信息,可以通過productFlavors配置輕松搞定。
首先,在AndroidManifest.xml文件中設置置友盟賬號信息,以及通過占位符設置動態渠道變量
<meta-data android:value="Channel ID" android:name="UMENG_CHANNEL"/>
<meta-data android:name="UMENG_CHANNEL" android:value="${UMENG_CHANNEL_VALUE}" />
然后,在build.gradle設置productFlavors
//假定我們需要打包的渠道包括華為市場、360、小米、百度、豌豆莢
android {
productFlavors {
huawei {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "huawei"]
}
xiaomi {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "xiaomi"]
}
qh360 {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "qh360"]
}
baidu {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "baidu"]
}
wandoujia {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "wandoujia"]
}
}
}
//可以批量修改打包渠道配置
android {
productFlavors {
huawei {}
xiaomi {}
qh360 {}
baidu {}
wandoujia {}
}
productFlavors.all {
flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
}
}
按上面的方式配置好以后即可以執行多渠道的打包了。
多apk打包
多apk打包的需求比較多樣,比如構建應用的免費版本(只提供有限的內容)和付費版本(提供更多內容),構建演示版本和正式發布版本,還可以針對不同設備、根據API級別或者針對不同的目標用戶等出不同的包。這種多apk打包可以通過productFlavors配置不同的applicationId、minSdkVersion、targetSdkVersion等
productFlavors {
free {
applicationId 'com.study.gradletestdemo.free'
minSdkVersion 14
targetSdkVersion 21
versionCode 1
versionName "1.0.0"
}
paid {
applicationId 'com.study.gradletestdemo.paid'
minSdkVersion 15
targetSdkVersion 22
versionCode 10
versionName "5.0.2"
}
}
多apk打包除了需要配置以上信息,可能不同的apk需要的代碼或資源文件也會有差異,自定義每個構建變體及其功能和資源,就需要了解如何創建和管理源集(sourceSets)。
Android Studio 默認會創建 main/ 源集及相關目錄,用于存儲所有構建變體可以共享的一切資源。同時我們可以創建新的源集來確切控制 Gradle 要為特定的構建類型、產品定制和構建變體來編譯和打包哪些文件。例如,在 main/ 源集中定義共有的基本功能,使用產品定制源集針對不同的客戶更改應用的品牌,或者僅針對使用調試構建類型的構建變體包含特殊的權限和日志記錄功能。
Gradle 要求按照與 main/ 源集類似的特定方式組織源集文件和目錄。當我們在productFlavors{}中創建了新的構建變體時,Android Studio 不會自動創建源集目錄,可以通過下面的步驟創建:
- 打開 Project 窗格并從窗格頂端的下拉菜單中選擇 Project 視圖。
- 導航至 MyProject/app/src/。
- 右鍵點擊 src 目錄并選擇 New > Folder > Java Folder。
- 從 Target Source Set 旁邊的下拉菜單中,選擇需要創建源集的build flavor名稱。
- 點擊 Finish。
Android Studio 將會為我們選擇的構建類型創建源集目錄,然后在該目錄內部創建 java/ 目錄。或者,也可以讓 Android Studio 在為特定的構建變體創建新文件時創建相關目錄,例如需要為free產品創建定義特殊顏色值的colors.xml文件:
- 在該 Project 窗格中,右鍵點擊 src 目錄并選擇 New > XML > Values XML File。
- 為 XML 文件輸入colors。
- 從 Target Source Set 旁邊的下拉菜單中,選擇free。
- 點擊 Finish。
由于free被指定為目標源集,Android Studio 會在創建 XML 文件時自動創建必要的目錄,創建后的目錄如下:
同樣的方式,也可以在java目錄下添加特定產品自定義的一些代碼文件,這樣利用源集來為不同的定制產品開發特定功能,然后再統一打包發布。
配置簽名信息
首先生成一份簽名文件,可以用Android Studio中的New Key Store圖形界面工具創建一份簽名文件
1、在Android studio圖形界面中配置
在Android Studio菜單欄點擊Build菜單–>Generate signed APK–>選擇key,并輸入密碼
2、在gradle文件中配置
signingConfigs {
release {
storeFile file("myreleasekey.keystore")
storePassword "password"
keyAlias "my_key"
keyPassword "password"
}
}
執行打包
1、Android studio圖形界面功能操作
在上面配置簽名信息的圖形界面操作中選擇了Key過后,點擊Next,在面板中選擇要打包的Flavors,并選擇簽名版本,然后點擊Finish。打包成功后的apk文件在 GradleTestDemo/app/ 目錄下
2、用命令打包
除了可以用Android Studio很直觀方便的圖形界面進行打包,作為程序員還需要會用相應的命令行。用命令行打包主要是運用gradlew命令,可以同時為所有定制產品所有構建類型進行打包,也可以指定為某一個定制產品的某種構建類型打包。例如,在Android Studio的Terminal面板中輸入gradlew assembleRelease命令,將生成所有定制產品的release版本的apk文件,這些文件會默認生成到 app/build/outputs/apk/ 目錄下。如果沒有配置簽名信息,生成的apk文件名后面會加上"-unsigned"。
總結
用gradle進行多變種打包還有諸多細節可以深入討論,這里只是總結了多渠道、多apk打包的主要操作流程及相關知識點,在實際工作中有更明確的需求時可以以此為線索深入學習。
Thanks To
配置構建變體
Gradle Plugin User Guide
手把手教你AndroidStudio多渠道打包
Android Studio Gradle實踐之多渠道自動化打包+版本號管理