Androidにてアプリを開発されている方々では常識だと思いますが、デフォルトで、debug/releaseとビルドを分けることができるようになっています。また、Flavorを使って、さらに細かくビルドを分けることができ、これらを使って複数のビルドを作成されている開発者の方も多くいらっしゃると思います。

これらのバリアントごとにNew Relicのキーを分けるための実装方法はいくつもあると思いますが、1つの例をこちらのブログで紹介します。

なお、本ブログは、こちらに記載の方法をわかりやすく解説したブログになります。

また、Android Agentの組み込みに関して、基本的にはドキュメントやUI上にて表示されるインストールガイドに従ってください。それらを行ったうえで、追加でご対応いただくこととなります。

まず、アプリレベルのbuild.gradleと同じフォルダに、newrelic-util.gradleというファイルを作成し、以下のコードをそのまま貼り付けて保存してください。

apply plugin: "com.android.application"

android.applicationVariants.all { variant ->

    def variantName = variant.name.capitalize()
    def newRelicTasksGroup = "newrelic"
    def projectDirPath = project.getProjectDir().absolutePath
    def newRelicPropertyFileName = "newrelic.properties"
    def newRelicPropertyFilePath = "${projectDirPath}/${newRelicPropertyFileName}"

    // Cleanup task for New Relic property file creation process.
    def deleteNewRelicPropertyFile = "deleteNewRelicPropertyFile"
    def taskDeleteNewRelicPropertyFile = project.tasks.findByName(deleteNewRelicPropertyFile)

    if (!taskDeleteNewRelicPropertyFile) {
        taskDeleteNewRelicPropertyFile = tasks.create(name: deleteNewRelicPropertyFile) {
            group = newRelicTasksGroup
            description = "Delete the newrelic.properties file on project dir."

            doLast {
                new File("${newRelicPropertyFilePath}").with {
                    if (exists()) {
                        logger.lifecycle("Deleting file ${absolutePath}.")
                        delete()
                    } else {
                        logger.lifecycle("Nothing to do. File ${absolutePath} not found.")
                    }
                }
            }
        }
    }

    /*
     * Fix for warning message reported by task "newRelicMapUploadVariantName"
     * Message:
     * [newrelic] newrelic.properties was not found! Mapping file for variant [variantName] not uploaded.
     * New Relic discussion:
     * https://discuss.newrelic.com/t/how-to-set-up-newrelic-properties-file-for-project-with-multiple-build-variants/46176
     */

    def requiredTaskName = "assemble${variantName}"
    def taskAssembleByVariant = project.tasks.findByName(requiredTaskName)
    def createNewRelicPropertyFileVariantName = "createNewRelicPropertyFile${variantName}"

    // 0. Searching task candidate to be dependent.
    if (taskAssembleByVariant) {
        logger.debug("Candidate task to be dependent found: ${taskAssembleByVariant.name}")

        // 1. Task creation
        def taskCreateNewRelicPropertyFile = tasks.create(name: createNewRelicPropertyFileVariantName) {
            group = newRelicTasksGroup
            description = "Generate the newrelic.properties file on project dir.\nA key/value propety " +
                    "will be written in file to be consumed by newRelicMapUploadVariantName task."

            logger.debug("Creating task: ${name}")

            doLast {
                def newRelicPropertyKey = "com.newrelic.application_token"
                def newRelicStringResourceKey = "new_relic_key"
                def targetResourceFileName = "api.xml"
                def variantXmlResourceFilePath = "${projectDirPath}/src/${variant.name}/res/values/${targetResourceFileName}"
                def mainXmlResourceFilePath = "${projectDirPath}/src/main/res/values/${targetResourceFileName}"
                def xmlResourceFilesPaths = [variantXmlResourceFilePath, mainXmlResourceFilePath]

                xmlResourceFilesPaths.any { xmlResourceFilePath ->
                    // 1.1. Searching xml resource file.
                    def xmlResourceFile = new File(xmlResourceFilePath)
                    if (xmlResourceFile.exists()) {
                        logger.lifecycle("Reading property from xml resource file: ${xmlResourceFilePath}.")

                        // 1.2. Searching for string name new_relic_key api.xml resource file.
                        def nodeResources = new XmlParser().parse(xmlResourceFile)
                        def nodeString = nodeResources.find {
                            Node nodeString -> nodeString."@name".toString() == newRelicStringResourceKey
                        }

                        // 1.3. Checking if string name new_relic_key was found.
                        if (nodeString != null) {
                            def newRelicApplicationToken = "${nodeString.value()[0]}"
                            logger.lifecycle("name:${nodeString."@name".toString()};" +
                                    "value:${newRelicApplicationToken}")

                            // 1.4 Checking the content of newRelicApplicationToken
                            if (newRelicApplicationToken == "null" || newRelicApplicationToken.allWhitespace) {
                                logger.warn("Invalid value for key ${newRelicStringResourceKey}. " +
                                        "Please, consider configuring a value for key ${newRelicStringResourceKey}" +
                                        " on ${xmlResourceFile.name} resource file for buildVariant: ${variantName}. " +
                                        "The ${newRelicPropertyFileName} will be not created.")
                                return true // break the loop
                            }

                            // 1.5. File creation.
                            File fileProperties = new File(newRelicPropertyFilePath)
                            fileProperties.createNewFile()
                            logger.lifecycle("File ${fileProperties.absolutePath} created.")

                            // 1.6. Writing content on properties file.
                            def fileComments = "File generated dynamically by gradle task ${createNewRelicPropertyFileVariantName}.\n" +
                            "Don\"t change it manually.\n" +
                            "Don\"t track it on VCS."
                            new Properties().with {
                                load(fileProperties.newDataInputStream())
                                setProperty(newRelicPropertyKey, newRelicApplicationToken.toString())
                                store(fileProperties.newWriter(), fileComments)
                            }

                            logger.lifecycle("Properties saved on file ${fileProperties.absolutePath}.")
                            return true // break the loop
                        } else {
                            logger.warn("The key ${newRelicStringResourceKey} was not found on ${xmlResourceFile.absolutePath}.\n" +
                                    "Please, consider configuring a key/value on ${xmlResourceFile.name} resource file for buildVariant: ${variantName}.")
                            return // continue to next xmlResourceFilePath
                        }
                    } else {
                        logger.error("Resource file not found: ${xmlResourceFile.absolutePath}")
                        return // continue next xmlResourceFilePath
                    }
                }
            }
        }

        // 2. Task dependency setup
        // To check the task dependencies, use:
        // logger.lifecycle("Task ${name} now depends on tasks:")
        // dependsOn.forEach { dep -> logger.lifecycle("\tTask: ${dep}") }
        tasks["clean"].dependsOn taskDeleteNewRelicPropertyFile
        taskCreateNewRelicPropertyFile.dependsOn taskDeleteNewRelicPropertyFile
        taskAssembleByVariant.dependsOn taskCreateNewRelicPropertyFile
    } else {
        logger.error("Required task ${requiredTaskName} was not found. " +
                "The task ${createNewRelicPropertyFileVariantName} will be not created.")
    }

    //
}

なお、本コードで行われていることは、主に以下のとおりです。

  • リソースフォルダにあるapi.xmlからnew_relic_key名前に格納されている値(APIキー)を取得する
  • ビルド時にnewrelic.propertiesを作成し、APIキーの内容を保存する
  • clean時にnewrelic.propertiesを削除する

続いて、上記gradleスクリプトを実行するため、アプリレベルのbuild.gradle.ktsに以下のようにapplyを追加します。

plugins {
    id("com.android.application")
    id("newrelic")
}

//add here
apply (from = "./newrelic-util.gradle")

dependencies {
    implementation("com.newrelic.agent.android:android-agent:7.3.1")
}

なお、上記はgradleにKotlin DSLを利用した場合となります。Groovyを利用されている場合は、以下をコピーしてください。

apply from: "./newrelic-util.gradle"

続いて、APIキーの内容をリソースファイルに保存します。対象のプロジェクトの各ビルドバリアント用のフォルダのres/values配下にapi.xmlを作成してください。

続いて、api.xmlを以下のように編集して、対象のビルドバリアントで利用するAPIキーを設定してください。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="new_relic_key">(New RelicのUIからコピーしたモバイルアプリのAPIキー)</string>
</resources>

続いて、Agentの初期化も上記リソースから読み込むように修正します。Agentの初期化するコードをLaunch activityに設定されていると思いますが、そちらを以下のように修正してください。

        NewRelic.withApplicationToken(
                getResources().getString(R.string.new_relic_key)
        ).start(this.getApplicationContext());

これで、終了です。APIキーがビルド時及び、起動時の両方にて、api.xmlに保存したキーが利用されるようになりました。アプリをビルドして、期待通りに動作する確認してみてください。

注意事項

複数のバリアントのビルドを並行して実行しないようにしてください。

newrelic-util.gradleにて設定されたスクリプトを理解された方はわかると思いますが、ビルド時にnewrelic.propertiesのファイルを作成しています。そのため、複数のバリアントのビルドを同時に実行した場合、異なるバリアントのキーが適用されることになり、Mapファイルのアップロードで利用されるキーが想定していたキーとは異なるキーが利用される可能性があります。

なお、mapファイルのアップロードはデフォルトではReleaseビルドのみとなっています。Debugビルドでもmapファイルをアップロードされる場合は、こちらのドキュメントに従い、設定を変更してください。