2015年3月26日木曜日

[安藤]android studio的新專案架構:Gradle build system

當把手上的Android開發用潮到出水機升級成Yosemite之後,第一件事情就是發現ADT不能在native的環境下跑了。得裝jdk 的舊版本。
心想將近一年沒升級ADT,也該升級一下了。

上google官方的開發者首頁之後才想起來google在去年底推出Android Studio。
(這個時間點是1.1版)於是下載來更新。 應該會跟之前Eclipse改到ADT那樣的無痛吧?
但是把Project匯入之後,才發現事情完全不是如想像的那樣...

有一大堆東西要重新設定。好不容易整理完(光是這個整理的流程就又可以寫一篇)之後,視窗一直跳出提醒:

...Gradle? What!?
點了「More information...」出現了Gradle的user guide:
http://tools.android.com/tech-docs/new-build-system/user-guide

看了一下(英文不好,花了快近8小時看完),似乎是為了解決之前的舊系統要讓同一份code可以產生不同版本(免費跟付費版)可以比較方便。對於正在用同一份程式碼開發多版本的人來說還蠻方便的。
決定花一些時間來了解,順便轉移程式碼到此架構。不想一直被吵
就算是失敗了也還有之前已經轉移到Android studio的基礎可以繼續用。

花了時間啃完硬到不行的user guide,還是一知半解。
還是從空的project來了解最快。
總之開了一個空的計畫:

從檔案總管看到的檔案結構:

文件裡面提到,所有的操作描述全部都放在build.gradle這個檔案裡面。
總之打開project根目錄的build.gradle瞧瞧。

...哇靠,這是啥?一些文件裡面沒提到的指令,看不懂。
有個檔案:settings.gradle也蠻可疑的。總之打開來看看:
include ':app'
看來這檔案是拿來include 「app」用的。


這時又想起文件內的一句話:project設定的操作都可以使用Android studio的「Project structure」來做修改。意下就是不要自己手改檔案...

好吧。繼續從Android studio裡面觀察。
發現還有另一個build.gradle:「Module: app」裡面的。
旁邊寫著「Module:app」,所以「app」就是指Module嘍。
總之打開來看看:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 19
    buildToolsVersion "21.1.1"

    defaultConfig {
        applicationId "com.akazukin.chacha"
        minSdkVersion 15
        targetSdkVersion 19
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
}


總算有不少文件裡面有提到的指令了。
先來找找要怎麼增加Module吧? 總之在Module「app」上面按右鍵:
喔,有指令可用。
總之加了一個Module 「chachalibrary」:
看來「Module」比較像舊系統的的「Project」。有自己的res/manifest。
那要怎麼設定Module 的關聯,讓Module可以include進來?
打開「Project Structure」:

選擇「Module depedency」:

然後會自動列出可以import的模組:

就加好了。也太簡單XD
順道一提,「Scope」的「Complie」是指這個project在make project的階段會被compile。provided的話聽說還沒有完整實作,建議不要用。

這時再回來看「app」的build.gradle,發現多了一行
「compile project(':chachalibrary')」

apply plugin: 'com.android.application'

android {
    compileSdkVersion 19
    buildToolsVersion "21.1.1"

    defaultConfig {
        applicationId "com.akazukin.chacha"
        minSdkVersion 15
        targetSdkVersion 19
        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 project(':chachalibrary')
}


看來加模組的方式就是如法泡製,加在depedencies裡面就好。
假如要import的是已經編成jar的project的話,請直接copy jar檔擺在
app/libs 目錄裡面再加在depedencies裡面。
直接使用import project的方法,可能主程式碼會無法參照內部的class跟function。

import eclipse project成module的流程:

  • android studio的視窗menu->File->Import Module,選擇要import的模組(會自動copy所需檔案,不需要先copy進project裡面),一步步照系統給你的流程做完。
  • 修改project根目錄的settings.gradle,加上
    「include ':< module名稱>'」
  • 重啓android studio(因為不會refresh...)
  • 修改project root/< module名稱>/build.gradle(這個時候會因為buildToolsVersion指定的19版沒有安裝而沒出現在project裡面,請直接使用文字編輯程式編輯),調整compileSdkVersion/buildToolsVersion兩個參數,讓它跟app Module裡面的build.gradle參數相同。dependencies若是沒有「compile fileTree(dir: 'libs', include: ['*.jar'])」這一行的話就加上去。
  • 一樣在build.gradle裡面,defaultConfig至少要加這兩個參數,要不然可能無法被其他模組參照:
        {
            minSdkVersion 15
            targetSdkVersion 15
        }
  • AndroidManifest.xml若是有參數衝突的話,拿掉import進來的module裡面的AndroidManifest.xml的參數。
  • 在lib路徑有擺android support library的話,請刪除,改用系統提供的。(在project structure的dependencies裡面可以add library depedency,有android support library可以選。記住,每個有include的都要選一樣的版本,否則之後執行的時候會出「multiple dex files define landroid/support/annotation/AnimRes」的錯誤。)

注意這幾點應該就可以compile了。





繼續測試文件裡面還有提到的三個重要觀念:
sourceSet / buildtype / flavor。

buildtype:通常是debug/release兩種。
flavor:通常是拿來設定package name,package sign,切換resources目錄的設定用。
這樣就可以達到同一份程式碼只要更換package name跟簽名,連各個java檔的package class name都可以不用換就可以上google play。
sourceSet:從檔案結構的角度來看,就是src目錄下面的「main」「androidTest」這兩個目錄。但是打開目前所有的build.gradle檔案都沒提到這幾個關鍵字。
看來得自己做了。

依照Stackoverflow的高手的建議,先宣告flavor再宣告sourceSet。

sourceSets {
        main {
            manifest.srcFile 'src/main/AndroidManifest.xml'
            java.srcDirs = ['src/main/java']
            aidl.srcDirs = ['src/main/java']
            renderscript.srcDirs = ['src/main/java']
            res.srcDirs = ['src/main/res']
            assets.srcDirs = ['src/main/assets']
        }
    }


結果按下debug,發現跳出視窗警告「Error:AndroidManifest.xml doesn't exist or has incorrect root tag」... 暈了...Orz
強行點選Debug,點選「Continue Anyway」,
發現build完之後可以正常執行。看來是Android Studio的bug。看了這篇之後你就不會被嚇到了。XD

這時出現了一個疑問:那可不可以導入其他sourceSet目錄的檔案? 答案是可以的。
直接在MainActivity裡面直接宣告ApplicationTest class看看,以這時的狀態是會找不到class的。
但是把build.gradle做如下的修改:

sourceSets {
        main {
            manifest.srcFile 'src/main/AndroidManifest.xml'
            java.srcDirs = ['src/main/java', 'src/androidTest/java']
            aidl.srcDirs = ['src/main/java']
            renderscript.srcDirs = ['src/main/java']
            res.srcDirs = ['src/main/res']
            assets.srcDirs = ['src/main/assets']
        }
    }


會發現剛剛找不到class的問題不再發生了。這樣看起來sourceset是可以互相導入,而且只要有同樣的目錄結構的話,class也會擺在一起,成為連集。

那接下來就只剩一個問題:假如兩個sourceSet有相同檔案存在的時候,gradle會怎麼處理?
繼續做實驗:把MainActivity.java copy到src/androidTest/java/com/akazukin/chacha目錄,馬上被抱怨Duplicate class...
看來soruceset的宣告,使用逗號加目錄的話,會成為連集。不能重複檔案。
要讓gradle自動選檔看來是行不通。

有趣的是,直接做一個strings.xml在src/flavor1/res/values裡面,執行之後發現app會使用該stings.xml裡面的資源。看來sourceset的設計會自動apply?
所以又做了很多實驗,希望可以搞清楚檔案的使用規則。



總結關於sourceSets / buildType / flavor的結論:連集。是的。就這兩個字...
應用的方式跟流程:
main裡面放的是最基本的設定跟必須的程式碼。
若是有設定flavor的需求,也就是根據不同的flavor給不同的設定的話,以下為flavor的使用方式:
  • 不需要編輯build.gradle去增加sourceSet的設定區塊。照flavor的設定名稱,在src/main同一層增加同名的目錄即可。
  • 要在同樣的資源檔案(AndroidManifest/string/values等等)上面增加設定的話,檔案擺放的目錄結構必須跟main一模一樣。如圖:
  • AndroidManifest.xml:要是有兩個設定值同時存在,值卻不相同的時候,此檔案的處理結果會給error。
    因此若是有針對不同flavor而必須給不一樣的數值的時候(例如googleplay api key),必須參數化,從strings或是values裡面取。無法參數化的設定(例如gcm的permission)就放在各個flavor裡面,main裡面不要放。
  • res/values下面的資料是以resource name為單位,flavor的目錄裡面有定義的話就優先取用。例:flavor1 跟main同時有兩個相同的string name的話,會先取用flavor1所指定的值。
  • 其他的res是以resource file為單位,flavor的目錄裡面有定義的話優先取用。例:flavor1 跟main同時有兩個相同的layout file/drawable file的話,會取用flavor1裡面的layout file/drawable file。
到此總算是對這個系統有初步的認識了。
最後,要在不同的flavor之間切換編輯的對象的話,android studio主視窗的左下角的邊條有一個「build variants」,點擊之後會出現一個小框框,試著操作一下就知道該怎麼切換了。

其他一些遇到的狀況:

  • 若是有getMethod()等等reflect的使用需求的話,可能會需要跳過型態檢查。
    請在project根目錄的build.gradle裡面加上:
    allprojects {
        gradle.projectsEvaluated {
            tasks.withType(JavaCompile) {
                options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
            }
        }
    }
  • 執行時遇到com.android.dex.DexIndexOverflowException:在主project的build.gradle的defaultConfig裡面加上「multiDexEnabled true」。

4 件のコメント:

  1. 最近再用volley
    原本不知道要怎麼安裝
    看到你說的
    "這時再回來看「app」的build.gradle,發現多了一行
    「compile project(':chachalibrary')」"
    如法炮製
    結果就成功了!!
    萬分感謝

    返信削除
  2. 您的文章 是我近期學習Android Studio - Gradle 看過說明得最詳盡的一篇了! 包含您個人學習時的心路歷程 也真的很有共鳴! 謝謝 很有幫助! 謝謝!!

    返信削除