Tag: androiddev


Gradle and GCM project integration

又到了難纏的狀況, 跟之前 Google Maps 的狀況很類似, 不過這次需要支援的檔案為 AndroidManifest.xml

GCM 需要在 AndroidManifest.xml 中設定權限, 而這個權限又得跟著 package name 跑, 所以不同的 flavour & buildType 就會有不同的設定值. 但問題是 gradle 並不支援 flavourBuildType (比如 amazonFreeDebug) 的目錄設定, 所以即使它可以幫你整合檔案裡的設定值, 但要怎麼擺放 AndroidManifest.xml 卻是個大問題.

這邊一樣提供 hack 的方法, 首先指定 flavour 所要使用的 AndroidManifest.xml 路徑

project.ext.flavor1 = [
    debugManifest: 'src/flavor1Debug/AndroidManifest.xml',
    releaseManifest: 'src/flavor1Release/AndroidManifest.xml'
]

project.ext.flavor2 = [
    debugManifest: 'src/flavor2Debug/AndroidManifest.xml',
    releaseManifest: 'src/flavor2Release/AndroidManifest.xml'
]

接著在 variant.processManifest 時想辦法讓 gradle 吃到這些檔案, 由於這個 case 的 debug & release 都是不同的 package name, 我們可以利用 flavour 本身並不具有資源設定值的特色, 把新的 AndroidManifest.xml 偷偷地塞進 flavour 的目錄裡, 名正言順的讓 gradle 編譯該 flavour. 最後記得刪除這個偷塞的檔案:

android.applicationVariants.all { variant ->
    variant.processManifest.doFirst {
        if (project.ext.has(variant.<a href="http://productFlavors.name">productFlavors.name</a>)) {
            if (project.ext[variant.<a href="http://productFlavors.name">productFlavors.name</a>].debugManifest != null &amp;&amp;
                project.ext[variant.productFlavors.name].releaseManifest != null) {
                def manifestDirectory = android.sourceSets[variant.productFlavors.name].manifest.srcFile.parentFile
                if (variant.buildType.name.equals("debug")) {
                    copy {
                        from project.ext[variant.productFlavors.name].debugManifest
                        into manifestDirectory
                    }
                } else if (variant.buildType.name.equals("release")) {
                    copy {
                        from project.ext[variant.productFlavors.name].releaseManifest
                        into manifestDirectory
                    }
                }
            }
        }
    }

    variant.processManifest.doLast {
        if (project.ext.has(variant.productFlavors.name)) {
            project.delete android.sourceSets[variant.productFlavors.name].manifest.srcFile
        }
    }
}

至於 AndroidManifest.xml, 只要留 GCM 相關設定即可, 其他 Activity 啥鬼的都不用放進去, 由 gradle 在編譯過程中自行與 main 整合.



    package="com.android.example.free.debug"
    android:versionCode="1"
    android:versionName="1.0" >


        android:name="com.android.example.free.debug.permission.C2D_MESSAGE"
        android:protectionLevel="signature" />






收工, 跟平常一樣 assemble 即可, happy hacking ! 相關的 code 可以參考這 : https://github.com/shakalaca/learning_gradle_android/blob/bef7af864f1d89483df3c611d76a199815f65660/07_tricks/app/build.gradle


Google Maps Android API v2 gradle integration

之前提到 Google Maps API (v2) 在整合至 gradle 時會碰到一些問題, 這邊提供一種可能的解法.

首先來看是怎樣的狀況, Google Maps API 在使用時得請求一組 API key, 而這組 key 得對應到一個 package name. 我自己會申請兩組, 一組給 debug 版用, 另一組給 release 版用, 這是因為 debug 版使用不同的 package name (透過 packageNameSuffix) 在測試時才能於同一支手機安裝 debug & release 版. 這樣的組合透過簡單的 overlay 應用即可輕鬆達成: 獨立出 debug (or release) 使用的 resource folder, 把 API key 放在 strings.xml 即可.

但如果你的 app 有免費版與付費版呢 ? 套用我的設定就有 4 種 package name 配 4 組 key, 理論上分 4 個 resource overlay 就可以解決. 可以問題來了, gradle 只能讓你設定 flavor 或者 buildtype 的 sourceSets, 例如 free, paid, debug, release, 但並不提供 freedebug, freerelease, paiddebug, paidrelease 的設定方式, 因此就算你把 folder 區分為前述的樣子, 或者異想天開在 sourceSets 分兩層方式設定 flavor.buildtype 的 res.srcDirs, 都無法順利分開 API key 所使用的 strings.xml.

目前看到的解法是透過修改 AndroidManifest.xml 的方式, 在 variant.processManifest.doLast 時複製一份方便修改的版本到編譯中介目錄, 然後置入正確 API key 的設定.

我稍微研究 Android Tools Project Site 的文件, 試著找出另一種不用修改檔案的方式達到同樣的效果. 其中這一段吸引我的注意:

The following rules are used when dealing with all the sourcesets used to build a single APK:

  • All source code (src/*/java) are used together as multiple folders generating a single output.
  • Manifests are all merged together into a single manifest. This allows Product Flavors to have different components and/or permissions, similarly to Build Types.
  • All resources (Android res and assets) are used using overlay priority where the Build Type overrides the Product Flavor, which overrides the main sourceSet.
    - Each Build Variant generates its own R class (or other generated source code) from the resources. Nothing is shared between variants.

第三點提到 build type 的優先權最高, 如果我們在整合 resource 之前把 build type 的 folder 指定到我想要用的目錄呢 ? android gradle plugin 有提供 variant.mergeResources, 那麼試著在那之前把 build type 的 sourcesets 替換, 應該就行了吧 ? 花了點時間修改, 很幸運的跑起來沒有問題, 這邊提供給各位參考, 有使用 Google Maps API 且跟我有類似需求的朋友可以參考看看 :)

相關 code 可參考這 https://github.com/shakalaca/learning_gradle_android/blob/a7a37a7b94cc79fd5f6cac834935c178f50a4061/07_tricks/app/build.gradle

如果要定義額外的 resource file, 透過 project.ext.flavorname 定義, 其中 debugRes 為 debug 版的 resource, releaseRes 為 release 版的 resource, 沒有定義會自動使用 default.

Rename APK in gradle: Android Studio friendly solution

延續之前討論的 rename APK 的問題, 那樣的寫法在 CLI 使用 gradle 時一切正常, 但如果把 project 匯入 Android Studio, 在 build 的時候會有奇怪的事情發生:

  • 如果跟我一樣在檔名安插 gitHash, commit 新的 change 之後在 Android Studio compile & run 時會抱怨檔案找不到, 此時會發現 Android Studio 要求的檔名為上一次的 gitHash 值, 但實際上新的檔案已經產生在 build/apk 目錄下.

- 就算沒有使用動態檔名, 偶爾也會發生產生的檔名會回復成原本的名稱.

目前懷疑 Android Studio 會 cache 輸出的檔案路徑, 其實也算合理, 因為沒事除了切換 variant 檔名的確不會改變.

我自己採用折衷的方法: 把編譯好的 apk 移到另一個目錄並且更名. 一方面對 Android Studio 而言, 一切如常, 如果要調出檔案分享到指定的目錄找尋即可; 另一方面我還是可以享受編譯好檔案即更名的好處.

相關 code 可參考這裡 https://gist.github.com/shakalaca/6422811

Rename APK files in gradle

gradle 在 zipAlign 為 true 時, 會產生兩個檔案: 一個為 unaligned 一個為 aligned (好像是廢話)

而在 android.applicationVariants 裡面, variant.packageApplication.outputFile 指向的是原始版本 (unaligned) 而 variant.outputFile 才是 zipAlign 過後的版本.

因此如果在 "保留兩種版本的檔案" 為前提的狀況下針對輸出的 apk 檔案更名, 應該要如連結中這麼做: variant.packageApplication.outputFile 一律更名, 而碰到 zipAlign 為 true 時, 額外更名 variant.outputFile.

相關 code 可參考這 https://gist.github.com/shakalaca/6414702