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」。

2015年3月21日土曜日

[Event]2014-12-13 whisky luxe 2014

在whisky live官方協辦五年收手之後,大型的威士忌展覽...
某天突然搜尋到whisky luxe的消息,第一屆已經結束了。Orz
之後開始緊盯消息,這次總算沒有錯過。
入場費$2000也不算便宜,還蠻期待能夠喝到一些不一樣的威士忌。





下午兩點入場,中午照慣例先吃個飯再過去。
選擇剛開沒多久的麵屋釀鳴。跟麵屋緣是關係店。提供的料理也跟麵屋緣一樣,
品質也沒問題。特色是把之前麵屋緣推出過的限定菜單:炒沾麵納入固定菜單。
個人非常喜歡麵屋緣的炒沾麵,吃過一次就列入拉麵的推薦名單。
麵屋釀鳴就直接進個人的推薦店家嘍。


然後前往會場。在att 4 fun的5樓。雖然提早了半小時到會場,但是5樓的到達方法卻不得其門而入,後來才發現從商店區的4F是到不了的。只後在下樓到樓外找。最後看到很多人在排隊的地方,加上隊伍裡面有人在填理性飲酒同意書,應該就是這裡了吧。


看起來場地只能從建築物外面的側邊直達5樓的樣子。
跟著排了20分鐘入場,因為電梯只有一部,不過一次可以容納約50人左右。旁邊有樓梯,
下次要是場地還是這裡的話,建議直接走樓梯進會場。
(剛剛查了一下,2015年換場地了,改在8/15~16兩天,而且還跟死灰復燃的Whisky L撞期...有趣了。山崎白州參展哪邊我就去哪邊...)


因為你不走樓梯的話,開場時就可以用籌碼換的酒款就喝不到了。
是的。20分鐘就被換光了... Orz

這次的小規則:比較好的試飲酒需要用籌碼換。一人給三顆籌碼。


入場時已經是這般榮景。人真的很多,多到蠻難走動的,不過並沒有參觀一般展覽會被人潮擠壓的程度,就一種很舒適的感覺。
用入場費區隔客群果然有差。


會場內大約有20個攤子,爵士樂團固定時間表演,
還有一盞價值不菲的水晶燈。

這次預備開瓶的花格眾。


除了酒牌攤位區之外,場地的吧台區也擺了很多單杯付費款。

得先購買籌碼,籌碼還有最小購買單位。
每一隻的單杯價都不一樣。從3顆10顆籌碼不等...
還是喝喝一般款就好。


當然一看就知道這些款式就算單杯買也值得的。


布納哈本的攤位展出的40年精裝款。

領了一杯18年,清爽依然,還有淡淡的海味。

格蘭花格的駿馬系列。
沒喝過花格,初嘗17年,順口不嗆辣,也有著不錯的花香,有趣的是後勁不小。難怪粉絲眾多。

當天有限量籌碼兌換款。既然一般的都被換完了,等到時間到了就來試看看吧。


聽別人討論才知道這家是供應藍牌基酒的...
在這裡用了第一顆籌碼,試喝過也覺得相當好,有著讓你感覺得出來,比格蘭傑經典款更重的花香。
雪莉桶的甘味也很強。本場次普飲款MVP就給你了。

因為會場內人實在是太多,就想要躲到角落去。這家sansibar剛好就在角落。
也是全場唯一有擺蘭姆酒的攤位。我還蠻喜歡蘭姆酒的,5隻全給它試過一輪。
右1 PANAMA 10y 有點嗆,一般蘭姆酒的感覺。
右2 GRANADA 10y 跟前一隻的味道接近。
中  BRAZIL14y 多了一股野格的藥草味,有點嗆。
左2 GUADELOUPE 15y 一般蘭姆酒的感覺。味道比10y的重。
左1 GUYANA 18y 夠濃,也有點嗆。
蘭姆酒我覺得有趣在低年份有新鮮的原料香氣,高年份有烈酒的濃厚感,都很棒啊~~~
結果在這攤就喝掉快一小時...


等待人潮比較沒那麼擠的時候開始移動。
格蘭路思。兩款新酒:
vintage reserve:淡淡花香,雪莉桶味,有新鮮的氣息。
sherry cask reserve:vintage的兩倍價,但是不只兩倍好喝。 濃甜雪莉味,給我蠻深刻的印象。
不愧是號稱用一次雪莉桶。
在這裡用了第一顆籌碼:換了1988年來喝。很嗆... 不太喜歡。看來是喝多了...該休息啦。


橡木桶這次展出威雀跟陀崙特。



開始到處拍拍場內。



順便坐著休息。


這時發現快到花格的限時兌換款的時間了,趕快來去排隊。
還好有先去排,要不然像圖上這樣的盛況,大概是換不到了。

換到的是這款駿馬系列1995。比剛剛喝過的17年要濃郁跟嗆,多了股煙硝味。


邊喝著換來的花格邊坐著休息。(還算休息嗎?)
突然圍了一圈人過來...然後出現了一位西裝男士拿著一瓶酒,大家開始舉杯討酒,
一直喊「虎哥」...

等到大家都要得差不多之後,手上的花格也喝完了。就順便要了一點來試看看。
瓶身長這樣:

看來是TTL南投酒廠的作品。感想:相當有趣的一款酒。
硝味,煙燻味,泥煤味,雪莉味全都有,還帶有曬過的大麥的麥香。
很活潑的感覺。有趣到會讓人想去參觀到底是怎麼釀的才能釀出這味道。
有機會去南投的話一定要排入行程。


雲頂也是沒喝過的牌子。初次認識的感想:10年就很柔順。也帶了點煙硝味。

... 我也常用馬克杯喝威士忌XD




有一家看起來是代理商,擺了眾多廠牌也都可以試,當然也要全給它試一下。
LAGAVULIN:瓶上的ISLAY就告訴你說他是重泥煤了。也沒錯...

OBAN:有點花香,有點嗆。

CAOL ILA:酒體輕,雪莉桶的感覺,輕泥煤。

Daiwhinnie:雪莉味。已經不知道在喝啥了XDD

最後就用布納哈本+生蠔做結束吧。帶有海味的布納哈本果然是很配。
體驗的時間有點趕... 品味是急不得的。



之後開始有比較強烈的頭暈的感覺了。就坐著休息,直到展覽結束再離開。
可以盡興試酒的場合不多,就算每一款都試個5ml,也喝了100ml去了。