1. 程式人生 > >Android Studio Gradle打包實踐之多渠道+版本號管理

Android Studio Gradle打包實踐之多渠道+版本號管理

上次介紹了 Android Studio的安裝、配置和基本使用 。這次講一下Android Studio用到的打包工具Gradle。 Gradle 是一種構建專案的框架,相容Maven、Ant,為Java專案提供了很多外掛去實現打包功能。廢話不多說,下面直接進入實戰。當我寫這篇部落格的時候,Android Studio的版本已經更新到了 1.4 ,比上一篇部落格的版本又更新了。

Android Studio工程build.gradle指令碼介紹

在進行多渠道打包之前,先介紹一下Android Studio工程中的gradle指令碼長什麼樣。開啟Android Studio,新建一個Project,這裡我給它命名為Hello Gradle,一路點選下一步,最後Android Studio自動為我們建立的如下圖的這個工程。
這裡寫圖片描述

按照上篇部落格中介紹的,我們推薦大家採用Android結構的檢視來檢視專案結構。展開Gradle Scripts我們可以看到裡面有兩個 build.gradle 檔案和一個 settings.gradle 檔案。其中的 build.gradle(Project: HelloGradle) 檔案是我們整個工程的build檔案,而 build.gradle(Module: app) 檔案是我們工程下的一個Module的build檔案。前面我們就說過Android Studio採用單工程多Module結構,一個工程可以理解為Eclipse下的一個Workspace,一個Module可以理解為Eclipse下的Project。當我們用Android Studio建立一個預設的工程時,它自動為我們建立了一個名字為 app 預設的Module。

所以我們可以知道,一個Android Studio工程會有一個 工程級別的build.gradle 檔案,同時有N個Module,就還會有N個 Module級別的build.gradle 檔案。

工程目錄下的build.gradle(Project: HelloGradle)檔案

接著我們先看下這個工程級別的build.gradle檔案。

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

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.3.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 }

這個檔案裡的buildscript閉包中為我們定義了工程用到的repository地址,預設為我們加上了jcenter,並添加了版本號為1.3.0的Android Gradle外掛。關於閉包,由於gradle是基於Groovy語言編寫的,而閉包是裡面的一個概念,可以理解為最小的程式碼執行塊。關於jcenter,可以理解為一個相容Maven中央倉庫的東西,是Google為Android建立的。

最下面還有一個task clean, task 是gradle指令碼中用到最多的東西了。Gradle實際上是一個容器,實現真正的功能的都是Gradle的外掛Plugin,而Plugin中又定義了各式各樣的Task,這一個個的Task是執行任務的基本單元。
這裡一看就知道是一個delete型別的task,意思是在我們執行打包指令碼前做一個清理工作,把專案輸出資料夾中的檔案先全部清理乾淨。

Module目錄下的build.gradle(Module: app)檔案

接著看app Module下的build.gradle檔案。

apply plugin: 'com.android.application'

第一行 apply plugin: ‘com.android.application’ :指的是在這個指令碼中應用 Android Application 外掛。前面我們說到了Gradle中真正起作用的是外掛,每個外掛中可以定義各種各樣的Task,當然還可以有一些Property屬性,如果你以前是用Ant打包的,那麼對屬性一定不會陌生吧。

android {
    compileSdkVersion 22
    buildToolsVersion "23.0.1"

    defaultConfig {
        applicationId "com.nought.hellogradle"
        minSdkVersion 14
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

接著看android閉包,裡面首先定義了我們這個Module使用的 compileSdkVersionbuildToolsVersion ,這兩個屬性大家肯定知道,一個是用來編譯程式碼的sdk版本,一個是用來打包apk的build-tools版本。

再看裡面的defaultConfig,又定義了幾個屬性。依次有 applicationId ,代表著你的包名,以前我們都是在AndroidManifest.xml檔案中通過 package=”com.nought.hellogradle” 指定應用程式的包名,現在我們可以在gradle打包指令碼中指定它,後面你會發現我們結合 buildTypesproductFlavors ,還可以動態的改變它,有點神奇了吧! minSdkVersion 指的是你的應用程式相容的最低Android系統版本; targetSdkVersion 指的是你的應用程式希望執行的Android系統版本; versionCode 是你的程式碼構建編號,一般我們每打一次包就將它增加1; versionName 則是你對外發布時,使用者看到的應用程式版本號,一般我們都用“點分三個數字”來命名,例如 1.0.0

接著看下 buildTypes ,這裡面預設只定義了 release 型別,其實還可以定 debug 型別以及你自己定義的例如 internal 國內型別、 external 國外型別等等。以前在每一個type中,可以分別配置不同的選項,例如可以 配置不同的包名、是否混淆 等等,目前的預設release型別中配置了混淆檔案, minifyEnabled false 指的是不混淆程式碼,下面這行

proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

指定的是你的混淆配置檔案。這裡就不詳細介紹了,馬上我們就會看一下多渠道打包,實踐一下大家就清楚了。

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:22.2.1'
    compile 'com.android.support:design:22.2.1'
}

最後,我們看dependencies閉包,這裡指的是我們的工程依賴的庫,以往在Eclipse中開發,我們常常通過jar包,以及新增library的形式來新增依賴,現在方便了,在gradle腳本里,一行程式碼通通搞定!真是簡單啊!dependencies閉包下,有幾種基本的語法。

1: compile fileTree(dir: 'libs', include: ['*.jar']) ,指的是依賴libs下面所有的jar包,你還可以指定具體的每一個jar包,而不是採用 *.jar 萬用字元匹配的方式,例如 compile files('libs/檔名.jar') ;

2: compile 'com.android.support:appcompat-v7:22.2.1' ,這種語法是通過包名:工程名:版本號的形式來依賴的,

3: testCompile 'junit:junit:4.12' ,指的是測試時才會用到的依賴,這裡一看就知道是指做單元測試時依賴junit。

好了,上面介紹了Android Studio預設生成的基本的Gradle打包指令碼的結構。下面我們在實踐中學習,怎麼修改這個指令碼,來實現自己的各種需求,例如多渠道自動化打包等等。

多渠道打包實踐

多渠道指的是你的應用程式可以釋出到不同的應用市場,被不同的使用者從各個市場下載以後,你可以監測到每一個使用者安裝的這個應用程式是來自哪個市場的。實現的方法有很多,主要是通過在安裝包中的放置一個標誌位來區分不同的渠道包。

多渠道打包實現思路

思路1:AndroidManifest.xml佔位符與productFlavor結合
比較常見的友盟移動統計sdk中使用的方案,這種方案是 通過 build.gradle 指令碼中的 productFlavor 來實現的。首先在AndroidManifest.xml檔案的 application 標籤裡指定一個 meta-data ,然後Umeng SDK會讀取這個標籤中value傳到Umeng的後臺,這樣就可以讓開發者監測到自己的應用程式渠道分佈情況了。

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

其實元素可以作為子元素,被包含在activityapplicationservicereceiver標籤中,但是不同位置下的meta-data讀取方法不一樣,現在以application中放置佔位符為例實踐。

思路2:一次打包,動態替換渠道識別符號

在 美團的技術部落格 上還分享過 另外一種實現思路 :就是在打包完一個apk之後,再拆包替換掉其中一個用來標識渠道市場的檔案。這種思路和官方的 buildTypes + productFlavor 方式有所不同。因為這種思路只需要執行一次打包任務,剩下的操作是拆開apk(實際上也是一種zip包),替換檔案。可想而知這種速度比較快,如果你有很多個渠道包要打的話,這種思路能提高很多速度,據說100個渠道包大概只要2分鐘。而普通的 buildTypes + productFlavor 方式,我打了4個渠道包也花費了幾十秒。可見如果有很多渠道包要出,建議採用美團的這種思路。

多渠道打包實現步驟

1. 在AndroidManifest.xml的 application 標籤下定義UMENG_CHANNEL佔位符。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.nought.hellogradle" >

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme" >

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

        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:theme="@style/AppTheme.NoActionBar" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

2. 修改app Module的build.gradle指令碼,在 android 閉包中新增 productFlavors 屬性,配置替換佔位符的渠道標識。

apply plugin: 'com.android.application'

android {
    compileSdkVersion 22
    buildToolsVersion "23.0.1"

    defaultConfig {
        applicationId "com.nought.hellogradle"
        minSdkVersion 14
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    productFlavors {
        GooglePlay {
            manifestPlaceholders = [UMENG_CHANNEL: "GooglePlay"]
        }
        Baidu {
            manifestPlaceholders = [UMENG_CHANNEL: "Baidu"]
        }
        Wandoujia {
            manifestPlaceholders = [UMENG_CHANNEL: "Wandoujia"]
        }
        Xiaomi {
            manifestPlaceholders = [UMENG_CHANNEL: "Xiaomi"]
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:22.2.1'
    compile 'com.android.support:design:22.2.1'
}

3. 開啟Android Studio自帶的命令列工具,執行 gradle build 命令,就可以在 app/build/outputs/apk/ 目錄下看到生成渠道包apk檔案。注意:輸出的apk檔案是在app Module下的build目錄中,不是工程根目錄下的build目錄。

這裡寫圖片描述

我們可以看到在 app/build/outputs/apk/ 中,生成的帶有渠道標識的apk檔案有12個,這時因為 buildTypesproductFlavors 兩兩組合,2*4=8,Android Studio預設必須有 releasedebug 這兩種Type。此外,由於buildTypes中還可以定義 zipAlignEnabled true ,意思是混淆後的zip優化,該值預設為true,因此每個渠道還多了一個 app-渠道標識-debug-unaligned.apk 檔案。

小結:

執行 gradle build 命令時,終端裡會顯示當前正在執行的task,裡面有很多我們熟悉的任務,例如dex、javaCompile這些。前面我們說過,gradle指令碼會以鉤子的形式,執行一系列的tasks,最終構建出我們所需要的程式安裝包。感興趣的同學可以執行一下 gradle tasks 命令,這個命令可以檢視當前工程下所有的tasks,後面我也將結合這些tasks,實踐一下jar包的構建。

這裡寫圖片描述

PS:渠道包修改包名

如果你想修改不同的渠道包的包名,可以在你的 productFlavors 指定不同的 applicationId 即可。在build.gradle檔案中,輸入的時候你就發現自動補全已經提示你,還有很多其他的屬性可以配置了,感興趣的同學不妨試試。

PPS:渠道包改應用名稱

如果你還想給不同的渠道指定不同的應用名字,例如想要在Xiaomi市場上叫做 “HelloGradle-小米專供版” , 那麼你可以新建 app/src/Xiaomi/res/values/strings.xml 的檔案,裡面填寫 HelloGradle-小米專供版 ,這樣打包出來的小米渠道包,應用程式的名稱就改變成 “HelloGradle-小米專供版” 了。

PPPS:單獨打包某一個渠道

執行 gradle build 會一次性打包出所有的渠道包,花費的時間還是很長的。如果只想打一個渠道的渠道包話應該怎麼做?以百度為例,可以在命令列中執行 gradle assembleBaidu ,我是怎麼找到 assembleBaidu 這個任務名字的?前面提到過的,執行 gradle tasks ,你就會發現所有的tasks列表,找到build類的tasks,就看到了!其實Android Studio裡面,這些全部都有介面操作的,大家看下程式碼編輯視窗的右邊欄,是不是有一個Gradle的按鈕,點選一下展開它,然後點選面板左上角的重新整理按鈕,就可以將所有的tasks列出來了,和執行命令列的效果是一樣的。定製化打包的需求還有很多,同學們可以自己嘗試嘗試,記得分享出來給大家啊!

這裡寫圖片描述

版本號管理實踐

版本號這個東西,在我出來實習的時候都還沒什麼感覺,直到不久前有一個需求,和後臺的同事合作一起實現一個新特性。客戶端這邊要求,只能在某一個版本以上,後臺才能返回特定的資料,而舊版本沒有解析這些資料的功能。結果後臺的同事噴了客戶端同學一頓,說客戶端這邊的版本號命名非常混亂,沒法按照這個客戶端的版本號來定邏輯,老闆們也會拍掉這個方案。。。所以呢,我覺得科學地管理好版本號,還是非常重要的!

為了簡單起見,這裡先把上面的多渠道打包指令碼改回原來預設。一般版本號都會用 三個數字加兩個點 分開,例如 1.0.0 這樣的形式。