こんにちは。 Androidエンジニアの@syarihuです。
突然ですが、あなたのアプリでは削除された機能のリソースや過去のキャンペーンなどで使ったリソースをきちんと削除していますか? クラスは消したけれどレイアウトや文字列リソース、画像リソースなどをそのままにしていませんか?
私が開発を担当しているAndroidアプリではそういった過去の機能で使われていた未使用のリソースがたくさん残っていました。 文字列リソースなどは残していても大きな影響はありませんが、特に画像リソースは不要なものが積み重なるとapkのサイズが数MBレベルで変わってきてしまうため、消しておくに越したことはありません。
しかし、不要なリソースをすべて特定するのはなかなか大変ですし、一気に消すのは怖いですよね。
そこで今回は@konifarさんが開発されているgradle-unused-resources-remover-pluginを利用して未使用リソースを一括削除する方法を紹介します。
gradle-unused-resources-remover-pluginとは
gradle-unused-resources-remover-pluginは、gradleコマンドを実行するだけで簡単に未使用リソースを削除できるGradleプラグインです。 除外ファイルを設定することができるため、Android Studioの機能にある「Remove unused resources…」よりもより柔軟に未使用リソースの削除を行うことができ、意図しないリソースの削除を防ぐことができます。
gradle-unused-resources-remover-pluginを導入する
それでは実際に導入方法を説明します。
build.gradleの設定
プロジェクトの build.gradle に次のように設定します。
buildscript {
repositories {
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
classpath "gradle.plugin.com.github.konifar.gradle:plugin:0.3.3"
}
}
app/build.gradleに次のように設定します。
apply plugin: "com.github.konifar.gradle.unused-resources-remover"
これで準備は完了です。
このままGradleコマンドを実行すれば未使用のリソースを削除することができますが、古くからある大規模なプロジェクトではそのまま実行すると使用されているリソースまで削除されてしまうことがあるため、安全に削除できる方法を順に説明していきます。
未使用リソースの削除対象から一旦すべて除外する
gradle-unused-resources-remover-pluginでは、app/build.gradle に次のように excludeNames にファイル名を記述することで未使用リソースの削除対象から除外できます。
unusedResourcesRemover {
excludeNames = [
"strings.xml",
]
}
影響の小さそうなものから順に削除していくため、一旦すべてのリソースを削除対象から除外します。 次の除外対象ファイルは一例のため、足りない場合は適宜追加してください。
unusedResourcesRemover {
excludeNames = [
"strings.xml",
"attrs.xml",
"bools.xml",
"floats.xml",
"ids.xml",
"integers.xml",
"styles.xml",
"themes.xml",
"res/layout",
"res/anim",
"res/menu",
"res/mipmap",
"res/drawable",
]
}
ResourcesのgetIdentifierメソッドを使用している箇所を除外設定に追加する
使用しているリソースが未使用リソースとして削除されてしまうケースはResourcesのgetIdentifierを使用していることが主な原因です。 そのため、まずはgetIdentifierを使用している箇所をgrepし、除外設定に追加していきます。 たとえば、drawableをgetIdentifierで取得している次のようなコードが見つかったとします。
resources.getIdentifier("ic_hoge_" + id, "drawable", packageName)
この場合は、excludeNamesに次のように除外設定を追加します。
unusedResourcesRemover {
excludeNames = [
"ic_hoge_",
]
他にも、次のように colors.xml に連番で定義されていることがあります。
<color name="color_0">#AAAAAA</color>
<color name="color_1">#BBBBBB</color>
<color name="color_2">#CCCCCC</color>
<color name="color_3">#DDDDDD</color>
<color name="color_4">#EEEEEE</color>
この場合は次のように別ファイルに切り出し、切り出したファイルを除外設定に追加します。
<color name="color_0">#AAAAAA</color>
<color name="color_1">#BBBBBB</color>
<color name="color_2">#CCCCCC</color>
<color name="color_3">#DDDDDD</color>
<color name="color_4">#EEEEEE</color>
unusedResourcesRemover {
excludeNames = [
"exclude_colors.xml",
]
このように必要な除外設定を追加していきます。
最初に設定したファイルの除外設定を1つずつ外していき、順番に削除する
除外設定ができたらあとは最初に設定したファイルの除外設定を1ずつ外していき、順番に削除していきます。 未使用リソースを削除するには次のgradleタスクを実行します。
./gradlew removeUnusedResources
たとえば、dimens.xml を除外設定から外してgradleタスクを実行、実行が終わったらコミットして次の除外設定を外す、みたいな作業を繰り返し行っていきます。
毎回実行する必要はありませんが、差分を見て削除項目数が多くなった場合は次のように assembleDebug を行うことで、削除されてしまったけれど実は使っていたファイルなどがあった場合にすぐに修正できます。
./gradlew assembleDebug
一気に消すと一度に修正するファイルが多くなってしまうため、このように順番に削除を行うことによって影響範囲を見ながら安全に削除できます。 layoutやdrawableなどは削除したあとに他のリソースが未使用になるみたいなケースがわりとあるので、少し手間ですが除外設定を1つ外すごとに最低2回実行は実行しておくと確実に未使用リソースを削除できます。 最終的に本当に必要な除外設定以外を全部消すことができたら削除完了です。
実際に一括削除するとどうなるのか
私が開発を担当しているAndroidアプリで一括削除を行った結果、約420ファイルを消すことができました。

apkの差分で見ると約1.5MBほどリソースを削減でき、削除した効果がかなりあったことが分かります。スッキリしましたね!

おわりに
過去の未使用リソースを一括で削除したことにより、今後は何か機能を削除したら gradle-unused-resources-remover-plugin を実行して削除漏れを無くしていくようにしていけば、未使用のリソースが増えていくことを防げるようになりました。
アプリの規模がそんなに大きくなければここまでしなくてもプラグインを入れてタスクを実行するだけで済むかもしれませんが、大規模なアプリでは影響範囲を見つつ少しずつ削除していかないとどこでエラーが出るか分かりません。
もし未使用リソースを削除したいと思っていたけれど、影響範囲が怖くて手が出せてなかった…という方は今回紹介した方法を参考にしてみてください。 マネーフォワードでは、エンジニアを募集しています。 ご応募お待ちしています。
【採用サイト】 ■マネーフォワード採用サイト ■Wantedly
【マネーフォワードのプロダクト】 ■お金の見える化サービス 『マネーフォワード ME』 iPhone,iPad Android
■ビジネス向けバックオフィス向け業務効率化ソリューション 『マネーフォワード クラウド』