buildType/productFlavorsでアプリ名を分ける方法

  • 35
    Like
  • 0
    Comment

applicationIdSuffixでデバッグ時のみ".debug"とか追加していると、debug用アプリとrelease用アプリを同端末に同時に存在させられて便利なのだが、同じアプリ名だとどっちがデバッグ用か分からなくなってしまうので、アプリ名でも見分けたいよねというお話。

最終目標

debug用アプリには、(d)をアプリ名のprefixに付ける。
例)
デバッグ用=(d)私のアプリ
リリース用=私のアプリ

勿論「私のアプリ(d)」というのも出来るが、これだと長いアプリ名の時にホーム画面で見えなかったりするので、prefixにするのが私流です^^;

前提

AndroidStudio 1.3

Gradleのビルドバージョンも1.3.0にして下さい。

build.gradle(project)
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
    }

方法

1. productFlavorsがなく、buildTypeのみで分ける方法。

app/src/debug/res/values/strings.xml
app/scr/release/res/values/strings.xml

上記でapp_nameを書き分けておけばOK。
言語別にあるなら言語別valuesフォルダを用意して。
当然ながら、manifestファイルでlabelに指定しておいてね。

AndroidManifest.xml
<application
        android:label="@string/app_name"
        省略

2. productFlavorsがあり、全flavorsでアプリ名が同じ場合。

あまりそんなパターンは無いかと思いますが、1.と同じ方法をとればOK.

3. productFlavors毎にアプリ名が異なり、buildType別には分けない場合。

app/src/flavor1/res/values/strings.xml
app/scr/flavor2/res/values/strings.xml

上記ようにflavor毎のxmlで書き分けておけばOK。
言語別にあるなら言語別valuesフォルダを用意する。

4. productFlavor毎ににアプリ名が異なり、buildType別にも分ける場合。

2017/04/07追記
この方法より、manifestPlaceholderを使った方法のほうがスマートかも。まとまったら新しく記事を書きます。

これがやっかいだった。

例えば・・・
flavor1 : app_name = "私のアプリ1" applicationId = "my.app.flavor1"
flavor2 : app_name = "私のアプリ2" applicationId = "my.app.flavor2"

となっている場合、最終目標は、

flavor1Debug : app_name = "(d)私のアプリ1"
flavor1Release : app_name = "私のアプリ1"
flavor2Debug : app_name = "(d)私のアプリ2"
flavor2Release : app_name = "私のアプリ2"

となることである。

これは、一筋縄ではいかない。

(1) 各flavor毎にstrings.xmlでapp_nameを定義する

これは問題あるまい。

(2) debugビルドのmergeResourcesタスク内で、merge済みxmlを強制的に書き換える。

こちらで見つけた方法。
http://stackoverflow.com/questions/22396346/setting-app-label-based-on-buildvariant

ググったときに他にもいくつか手段はあったが、言語別にvaluesフォルダがある場合は、これしか適用できなかった。

まず、app/build.gradleに以下のメソッド?を追加する

build.gradle(app)
// debug用アプリの名称に(d)を付ける
def debugAppRename(variant, labelResource, lang) {
    def flavor = variant.flavorName
    def buildtype = variant.buildType
    // Append buildType name to app label
    if (buildtype.debuggable) {
        variant.mergeResources << {
            def valuesFile = "$buildDir/intermediates/res/merged/${flavor}/${buildtype.name}"
             if (lang.length()==0) {
                // default言語
                valuesFile = valuesFile + "/values/values.xml"
            } else {
                // 言語別
                valuesFile = valuesFile + "/values-${lang}/values-${lang}.xml"
            }
            def values = (new XmlParser()).parse(valuesFile)
            values.string.each { m ->
                def index = m.text().indexOf("(d)")
                    if (index == -1) {
                        m.value = "(d)${m.text()}"
                        new XmlNodePrinter(new PrintWriter(new FileWriter(valuesFile)))
                                .print(values)
                    }
             }
        }
    }
}

android{
省略
}

続いて、android{}内に以下の記述をする。

build.gradle(app)
android{
省略

  applicationVariants.all { variant ->
      debugAppRename(variant, 'app_name', "")
      debugAppRename(variant, 'app_name', "ja")
  }
}

debugAppRenameを言語数分呼び出す。
なお、debug以外では置き換えられないのでご安心を。

ちなみに、私は「release用apkのファイル名を変更して特定フォルダにコピーする」というのも書いているので、最終的なbuild.gradleはこんな感じ。

build.gradle(app)
apply plugin: 'com.android.application'

// debug用アプリの名称に(d)を付ける
def debugAppRename(variant, labelResource, lang) {
    def flavor = variant.flavorName
    def buildtype = variant.buildType
    // Append buildType name to app label
    if (buildtype.debuggable) {
        variant.mergeResources << {
            def valuesFile = "$buildDir/intermediates/res/merged/${flavor}/${buildtype.name}"
             if (lang.length()==0) {
                // default言語
                valuesFile = valuesFile + "/values/values.xml"
            } else {
                // 言語別
                valuesFile = valuesFile + "/values-${lang}/values-${lang}.xml"
            }
            def values = (new XmlParser()).parse(valuesFile)
            values.string.each { m ->
                if (m.@name == labelResource) {
                    def index = m.text().indexOf("(d)")
                    if (index == -1) {
                        m.value = "(d)${m.text()}"
                        new XmlNodePrinter(new PrintWriter(new FileWriter(valuesFile)))
                                .print(values)
                    }
                }
            }
        }
    }
}

android {
    signingConfigs {
        config {
            keyAlias 'aaaa'
            keyPassword 'bbbb'
            storeFile file('app.jks')
            storePassword 'cccc'
        }
    }
    compileSdkVersion 22
    buildToolsVersion "22.0.1"
    defaultConfig {
        applicationId "my.app"
        minSdkVersion 14
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.config
        }
        debug {
            applicationIdSuffix '.debug'
        }
    }
    productFlavors {
        flavor1 {
            applicationId 'my.app.flavor1'
            versionCode 1
            versionName '1.0.0'
        }
        flavor2 {
            applicationId 'my.app.flavor2'
            versionCode 1
            versionName '1.0.0'
        }
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_7
        targetCompatibility JavaVersion.VERSION_1_7
    }

    // flavorName_r(versionCode)_v(versionName)_timestamp.apk というファイル名でapkを出力
    // gradle.properties内のdeployToを任意のフォルダに書き換えると、そのフォルダにapkが出力されます
    applicationVariants.all { variant ->
        if (variant.buildType.name.equals("release")) {
            variant.outputs.each { output ->
                if (output.outputFile != null && output.outputFile.name.endsWith('.apk')) {
                    // Rename APK
                    def versionCode = variant.versionCode
                    def versionName = variant.versionName
                    def appName = variant.flavorName
                    def date = new java.text.SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date())
                    def newName = "MyApp_${appName}_r${versionCode}_v${versionName}_${date}.apk"

                    def publish = project.tasks.create("publish_${appName}")

                    // Move and Rename APK
                    def task = project.tasks.create("publish${variant.name.capitalize()}Apk", Copy)
                    task.from(output.outputFile)
                    task.rename(output.outputFile.name, newName)
                    task.into(deployTo)

                    task.dependsOn variant.assemble
                    publish.dependsOn task
                }
            }
        }
        debugAppRename(variant, 'app_name', "")
        debugAppRename(variant, 'app_name', "ja")
    }
}

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

    compile 'com.android.support:support-v4:23.+'
    compile 'com.google.android.gms:play-services-analytics:7.8.+'
}
gradle.properties
# apkをコピーしたいフォルダを記述
deployTo=../../out

余談

これまでのproductFlavorに関する調査で、かなりのことができるようになった。
AndroidStudio1.3でproductFlavor
特定のproductFlavor/buildTypeにだけActivityを追加する
特定のproductFlavor/buildTypeでだけ使用するライブラリプロジェクトを作る
ルートフォルダの異なるプロジェクト/moduleをimportせずに参照する

あとは、publishXXXを全flavorに対して自動的に呼べるタスクが書けると更に楽になりそうだ。
今は、以下のようなshを書いてターミナルから実行しているが、Gradleタスクに追加する方法をご存じでしたら教えて頂きたく^^;

publishAllFlavors.sh
#!/bin/bash

./gradlew publish_flavor1
./gradlew publish_flavor2

追記1(2015/08/31)

app/build.gradleが長くなったので分離してみた。

(projRoot)
+ scripts/
  + publish.gradle
+ app/

上記のようにスクリプトを配置。

scripts/publish.gradle
// debug用アプリの名称に(d)を付ける
def debugAppRename(variant, labelResource, lang) {
    def flavor = variant.flavorName
    def buildtype = variant.buildType
    // Append buildType name to app label
    if (buildtype.debuggable) {
        variant.mergeResources << {
            def valuesFile = "$buildDir/intermediates/res/merged/${flavor}/${buildtype.name}"
            if (lang.length() == 0) {
                // default言語
                valuesFile = valuesFile + "/values/values.xml"
            } else {
                // 言語別
                valuesFile = valuesFile + "/values-${lang}/values-${lang}.xml"
            }
            def values = (new XmlParser()).parse(valuesFile)
            values.string.each { m ->
                if (m.@name == labelResource) {
                    def index = m.text().indexOf("(d)")
                    if (index == -1) {
                        m.value = "(d)${m.text()}"
                        new XmlNodePrinter(new PrintWriter(new FileWriter(valuesFile)))
                                .print(values)
                    }
                }
            }
        }
    }
}

android {
    // (appName)_(flavorName)_r(versionCode)_v(versionName)_timestamp.apk というファイル名でapkを出力
    // gradle.properties内の【appName】を書き換える
    // gradle.properties内の【deployTo】を任意のフォルダに書き換えると、そのフォルダにapkが出力されます
    applicationVariants.all { variant ->
        if (variant.buildType.name.equals("release")) {
            variant.outputs.each { output ->
                if (output.outputFile != null && output.outputFile.name.endsWith('.apk')) {
                    // Rename APK
                    def versionCode = variant.versionCode
                    def versionName = variant.versionName
                    def flavorName = variant.flavorName
                    //noinspection UnnecessaryQualifiedReference
                    def date = new java.text.SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date())
                    def newName = "${appName}_${flavorName}_r${versionCode}_v${versionName}_${date}.apk"

                    def publish = project.tasks.create("publish_${flavorName}")

                    // Move and Rename APK
                    def task = project.tasks.create("publish${variant.name.capitalize()}Apk", Copy)
                    //noinspection GroovyAssignabilityCheck
                    task.from(output.outputFile)
                    //noinspection GroovyAssignabilityCheck
                    task.rename(output.outputFile.name, newName)
                    task.into(deployTo)

                    //noinspection GroovyAssignabilityCheck
                    task.dependsOn variant.assemble
                    publish.dependsOn task
                }
            }
        }
        debugAppRename(variant, 'app_name', "")
        debugAppRename(variant, 'app_name', "ja")
    }
}

build.gradle(app)
apply plugin: 'com.android.application'
apply from: '../scripts/publish.gradle'

android {
    compileSdkVersion 22
    buildToolsVersion "22.0.1"

    lintOptions {
        abortOnError false
    }

    signingConfigs {
        config {
            keyAlias 'aaaa'
            keyPassword 'bbbb'
            storeFile file('app.jks')
            storePassword 'cccc'
        }
    }
    compileSdkVersion 22
    buildToolsVersion "22.0.1"
    defaultConfig {
        applicationId "my.app"
        minSdkVersion 14
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
    }


    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
            debuggable false
            //noinspection GroovyAssignabilityCheck
            signingConfig signingConfigs.release
        }
        debug {
            applicationIdSuffix '.debug'
        }
    }

    productFlavors {
        flavor1 {
            applicationId 'my.app.flavor1'
            versionCode 1
            versionName '1.0.0'
        }
        flavor2 {
            applicationId 'my.app.flavor2'
            versionCode 1
            versionName '1.0.0'
        }
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_7
        targetCompatibility JavaVersion.VERSION_1_7
    }

}

dependencies {
    compile 'com.android.support:support-v4:23.+'
    compile 'com.google.android.gms:play-services-analytics:7.8.+'
}

gradle.properties
# apkをコピーしたいフォルダを記述
deployTo=../../out

# publishタスクで出力するapkのファイル名Prefix
appName = MyAppName

すっきりしたうえにpublish.gradleを使い回せるようになって更に便利。