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ファイルをアップロードされる場合は、こちらのドキュメントに従い、設定を変更してください。
本ブログに掲載されている見解は著者に所属するものであり、必ずしも New Relic 株式会社の公式見解であるわけではありません。また、本ブログには、外部サイトにアクセスするリンクが含まれる場合があります。それらリンク先の内容について、New Relic がいかなる保証も提供することはありません。