Android プロジェクトの未使用リソースをコマンドラインや CI で削除する (Remove Unused Resources Plugin)


Android プロジェクトの未使用リソースを一括で削除する Gradle Plugin を公開しました。このプラグインのメリットや使い方を解説します。

> 2023/08/08: v2.1.0 に合わせて更新
> 2023/08/05: Remove Unused Resources Plugin for Android v2.0.0 の内容に更新しました

# Remove Unused Resources Plugin for Android

Remove Unused Resources Plugin により、Gradle Task から Android の未使用リソースを削除できるようになります。

https://github.com/irgaly/android-remove-unused-resources-plugin

## 特徴やメリット

* Android Lint `UnusedResources` ルールの実行結果 `lint-results.xml` をもとにリソースの削除を実行します
    * 未使用リソースの検出は Android Lint に任せているため、Android Studio の `Refactor > Remove Unused Resources...` メニューと同じ結果を期待できます

![Android Studio の Remove Unused Resources メニュー](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17931/51b4ffb2-99e0-12bf-fdb2-0538206feaf4.png)

* マルチモジュールプロジェクト対応
    * Android Lint がマルチモジュールプロジェクトでのリソース検出に対応しています
* DataBinding, Epoxy 対応
    * Android Lint がコード生成解析 (checkGeneratedSources) に対応しているため、正しく解析できます
* Github Actions などの CI での実行に適しています
    * Gradle Task であるためどこでも実行できます
* Android リソースのすべての形式に対応しています
    * [Resource Type](https://developer.android.com/guide/topics/resources/providing-resources#ResourceTypes) のすべてに対応しています
    * R.string などの XML tag の削除も、R.layout などのリソースファイル削除も対応しています
    * drawable-{density} や values-v26 などの Alternative リソースも正しく検出します
    * 9-patch ファイルも正しく検出します
* もとの XML をなるべく維持する
    * XML 内のリソースを削除するときに、もとの XML のインデントやスペースを維持します
    * `&#{unicode}` などの文字列参照なども元の XML の表現をそのまま残して処理します
* 高速動作
    * ファイルや XML tag の削除以外の無駄な処理はしていません
    * Android Lint タスクで未使用リソースを検出する仕組みのため、実行時間のほとんどは Android Lint に割かれると思います

## 前提

Android マルチモジュールで Gradle Kotlin Script (*.gradle.kts) 構成のプロジェクトを前提として説明します。

Gradle や Android Gradle Plugin のバージョンなど必要要件は [README.md](https://github.com/irgaly/android-remove-unused-resources-plugin) を参照してください。

## 使い方

このプラグインでは新しい Gradle Plugin Management に対応しています。settings.gradle.kts へ以下の設定を追加します。

`settings.gradle.kts`

```kotlin
// ...
pluginManagement {
  repositories {
    google()
    mavenCentral()
    gradlePluginPortal() // Remove Unused Resources Plugin は Gradle Plugin Portal へ登録されています
  }
}
// ...
```

Android アプリモジュールへ Remove Unused Resources Plugin を適用します。

`app/build.gradle.kts`

```kotlin
plugins {
  id("io.github.irgaly.remove-unused-resources") version "{最新バージョンを指定}"
}
```

DataBinding や Epoxy などのコード生成を使っている場合は、生成コードも解析対象に含めるために、コード生成をしているモジュールごとに checkGeneratedSources を設定します。

`app/build.gradle.kts`

```kotlin
android {
  lint {
    // コード生成を使っているモジュールでは checkGeneratedSources を有効にする
    checkGeneratedSources = true
  }
}
```

`othermodule/build.gradle.kts`

```kotlin
android {
  lint {
    // コード生成を使っているモジュールでは checkGeneratedSources を有効にする
    // rur.lint.onlyUnusedResources オプションを使用する場合はモジュール個別の定義は不要になります
    checkGeneratedSources = true
  }
}
```

これで Remove Unused Resources Plugin を実行する準備は整いました。

Android Lint を実行して `UnusedResource` を検出し、`app/build/reports/lint-results-{variant}.xml` を生成します。

ここでは variant = debug とします。

```shell
% ./gradlew :app:lintDebug
```

Android Lint の結果にもとづいてリソースを削除します。削除されるリソースを確認するために Dry Run で実行してみます。Dry Run モードでは削除対象を列挙するだけで実際には削除を実行しません。

（オプションの `rur` は Remove Unused Resources の頭文字です）

```shell
% ./gradlew :app:removeUnusedResourcesDebug -Prur.dryRun
...
> Task :app:removeUnusedResourcesDebug
> Task :app:removeUnusedResourcesDebug
> report from: /src/project/app/src/main/res/values/colors.xml
[dry run] delete resource element: R.color.black
[dry run] delete resource element: R.color.unused_color
[dry run] delete resource element: R.color.unused_color_with_night_theme
skip because it has tools:override: R.color.unused_override
> report from: /src/project/app/src/main/res/values/strings.xml
[dry run] delete resource element: R.string.unused_string
> report from: /src/project/app/src/main/res/values-night/colors.xml
[dry run] delete resource element: R.color.unused_color_with_night_theme
[dry run] delete resource file because of empty: /src/project/app/src/main/res/values-night/colors.xml
...
```

結果を確認して問題なさそうであれば `-Prur.dryRun` 引数を消して実際に削除を実行します。

```shell
% ./gradlew :app:removeUnusedResourcesDebug
...
> Task :app:removeUnusedResourcesDebug
> report from: /src/project/app/src/main/res/values/colors.xml
delete resource element: R.color.black
delete resource element: R.color.unused_color
delete resource element: R.color.unused_color_with_night_theme
skip because it has tools:override: R.color.unused_override
> report from: /src/project/app/src/main/res/values/strings.xml
delete resource element: R.string.unused_string
> report from: /src/project/app/src/main/res/values-night/colors.xml
delete resource element: R.color.unused_color_with_night_theme
delete resource file because of empty: /src/project/app/src/main/res/values-night/colors.xml
...
```

未使用のリソースファイルや、未使用リソースの XML tag が削除されました。

たとえば、values 以下のファイルの XML tag は以下のように削除されます。

```diff
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -6,8 +6,6 @@
     <color name="teal_700">#FF018786</color>
     <color name="black">#FF000000</color>
     <color name="white">#FFFFFFFF</color>
-    <color name="unused_color">#FFFFFF</color>
-    <color name="unused_color_with_night_theme">#FFFFFF</color>
     <color name="unused_exclude_color">#FFFFFF</color>
     <color name="unused_exclude_pattern_color">#FFFFFF</color>
 </resources>
```

## `UnusedResources` ルールだけの Android Lint を実行する

Android Lint をそのまま実行すると `UnusedResources` 以外の Lint も実行してしまうため時間がかかってしまいます。

未使用リソースを検出するだけでよければ `UnusedResources` ルールだけのチェックをすると効率的です。

Remove Unused Resources Plugin を適用していれば以下のオプションを指定することで `UnusedResources` ルールだけをチェックすることができます。

```kotlin
% ./gradlew :app:lintDebug -Prur.lint.onlyUnusedResources
```

このオプションを指定すると、以下の設定で lintOptions を上書きすることになります。

```kotlin
android {
  lint {
    // -Prur.lint.onlyUnusedResources オプションを付けると、以下の設定が自動で適用されます
    xmlReport = true // app project のみ
    checkDependencies = true // app project のみ
    checkGeneratedSources = true
    checkOnly.clear()
    checkOnly.add("UnusedResources")
    warning.add("UnusedResources")
  }
}
```

オプションについて、詳しくは [README.md](https://github.com/irgaly/android-remove-unused-resources-plugin) を参照してください。

## CI からリソース削除を実行する

Github Actions などの CI 環境で自動的に不要リソースを削除し、不要リソース削除の PR や commit を生成するようにすると便利です。

CI で実行する場合はたとえば以下のコマンドのようになります。

```shell
% ./gradlew :app:lintDebug -Prur.lint.onlyUnusedResources
% ./gradlew :app:removeUnusedResourcesDebug
```

このコマンドは以下の処理を実行します。

* `UnusedResources` ルールのみで Android Lint を実行する
* Android Lint の結果 `app/build/reports/lint-results-debug.xml` に従って、未使用リソースを削除する

## リソース削除の除外設定

Android Lint で検出されても削除したくないリソースを設定しておくことができます。

`app/build.gradle.kts`

```kotlin
removeUnusedResources {
  // 文字列 "R.{type}.{resource name}" との完全一致で除外リソースを指定する
  excludeIds.add("R.color.unused_exclude_color")

  // 文字列 "R.{type}.{resource name}" に対して正規表現(完全一致)で除外リソースを指定する
  excludeIdPatterns.add("R\\..*exclude_pattern.*")

  // 削除対象のリソースファイルに対して File glob 形式で、除外リソースを指定する
  // ここで指定したファイルやファイル内の XML tag は削除対象から除外されます
  excludeFilePatterns.add("**/values/exclude_colors.xml")
}
```

設定について、詳しくは [README.md](https://github.com/irgaly/android-remove-unused-resources-plugin) を参照してください。