ライブラリを賢く選択する

アプリの最適化を有効にするには、Android の最適化に対応したライブラリを使用する必要があります。ライブラリが Android 向けに最適化されていない場合(たとえば、関連する keep ルールをバンドルせずにリフレクションを使用している場合)、Android アプリに適していない可能性があります。このページでは、一部のライブラリがアプリの最適化に適している理由を説明し、ライブラリを選択する際の一般的なヒントを紹介します。

ライブラリを選択する際の一般的なヒント

以下のヒントを参考にして、ライブラリがアプリの最適化に対応していることを確認してください。

リフレクションよりもコード生成を優先する

リフレクションではなく、コード生成(codegen)を使用するライブラリを選択します。codegen を使用すると、オプティマイザーは、実行時に実際に使用されるコードと、削除できるコードを判断できます。ライブラリが codegen を使用しているか、リフレクションを使用しているかを判断するのは難しい場合がありますが、いくつかの兆候があります。詳しくは、ヒントをご覧ください。

コード生成とリフレクションの比較について詳しくは、ライブラリ作成者向けの最適化をご覧ください。

リフレクションの使用を確認する(上級者向け)

ライブラリがリフレクションを使用しているかどうかは、コードを調べることで確認できます。ライブラリがリフレクションを使用している場合は、関連する保持ルールが提供されていることを確認します。ライブラリが次の処理を行う場合、リフレクションを使用している可能性があります。

  • kotlin.reflect パッケージまたは java.lang.reflect パッケージのクラスまたはメソッドを使用します。
  • Class.forName 関数または classLoader.getClass 関数を使用します。
  • 実行時にアノテーションを読み取る。たとえば、val value = myClass.getAnnotation() または val value = myMethod.getAnnotation() を使用してアノテーション値を保存し、value で何らかの処理を行う場合。
  • 次の例のように、メソッド名を文字列として使用してメソッドを呼び出します。

    // Calls the private `processData` API with reflection
    myObject.javaClass.getMethod("processData", DataType::class.java)
    ?.invoke(myObject, data)
    

最適化に関する問題を確認する

新しいライブラリを検討する際は、ライブラリの Issue Tracker とオンライン ディスカッションに目を通し、アプリの最適化の縮小または構成に関連する問題がないかどうかを確認します。その場合は、そのライブラリの代替手段を探す必要があります。次の点に注意してください。

  • AndroidX ライブラリや Hilt などのライブラリは、リフレクションではなく codegen を主に使用するため、アプリの最適化に適しています。リフレクションを使用する場合は、必要なコードのみを保持する最小限の keep ルールを指定します。
  • シリアル化ライブラリでは、オブジェクトのインスタンス化やシリアル化の際にボイラープレート コードを回避するために、リフレクションが頻繁に使用されます。リフレクション ベースのアプローチ(JSON の Gson など)ではなく、codegen を使用してこれらの問題を回避するライブラリを探します。たとえば、Kotlin Serialization や codegen を使用した Moshi を使用します。
  • 可能であれば、パッケージ全体の保持ルールを含むライブラリは避けてください。パッケージ全体の keep ルールはエラーの解決に役立ちますが、最終的には、必要なコードのみを保持するように、広範な keep ルールを絞り込む必要があります。詳細については、最適化を段階的に導入するをご覧ください。
  • サードパーティ ライブラリを使用するアプリを公開する前に、R8 構成アナライザを使用して、提供されている keep ルールを監査します。レポートを確認することで、ライブラリの保持ルールが広範すぎて、R8 がコードベースで重要な最適化を実行できないかどうかを確認できます。このチェックにより、選択したライブラリがアプリのパフォーマンス目標に沿っており、不要な構成の肥大化を招かないことを確認できます。
  • ライブラリでは、ドキュメントからプロジェクト内のファイルに保持ルールをコピーして貼り付ける必要はありません。特に、パッケージ全体の保持ルールは必要ありません。これらのルールは、長期的にはアプリ デベロッパーのメンテナンスの負担となり、時間の経過とともに最適化や変更が難しくなります。

新しいライブラリを追加した後に最適化を有効にする

新しいライブラリを追加した場合は、その後で最適化を有効にして、エラーがないかどうかを確認します。エラーがある場合は、そのライブラリの代替手段を探すか、保持ルールを記述します。ライブラリが最適化に対応していない場合は、そのライブラリに関するバグを報告してください。

不適切な保持ルールを除外する(上級者向け)

保持ルールは足し算方式で運用されます。つまり、ライブラリの依存関係に含まれる特定のルールを削除できず、アプリの他の部分のコンパイルに影響する可能性があります。たとえば、ライブラリにコードの最適化を無効にするルールが含まれている場合、そのルールによってプロジェクト全体の最適化が無効になります。

本来削除すべきコードを保持する keep ルールを含むライブラリは避ける必要があります。ただし、それらを使用する必要がある場合は、次のコードに示すようにルールを除外できます。

// If you're using AGP 8.4 and higher
buildTypes {
    release {
        optimization.keepRules {
          it.ignoreFrom("com.somelibrary:somelibrary")
        }
    }
}

// If you're using AGP 7.3-8.3
buildTypes {
    release {
        optimization.keepRules {
          it.ignoreExternalDependencies("com.somelibrary:somelibrary")
        }
    }
}

広範なライブラリ ルールの推奨ワークフロー

R8 Configuration Analyzer で、ライブラリが過度に広範な keep ルールをバンドルしていることが判明した場合は、本番環境ビルドではなく、ローカルビルドでのみそれらのルールをフィルタします。ルールがアプリに与える影響をテストする手順は次のとおりです。

  1. ライブラリを更新する: R8 構成アナライザーがフラグを立てたライブラリの最新バージョンを使用していることを確認します。新しいバージョンでは、より狭いコンシューマー保持ルールが適用される可能性があります。最新バージョンを確認するには、ライブラリのドキュメントまたは Maven リポジトリをご覧ください。Android Studio では、多くの依存関係の新しいバージョンにもフラグが付けられます。更新後、R8 構成アナライザーを再度実行して、広範な keep ルールが残っているかどうかを確認します。
  2. ローカルで改善の可能性を確認する: ライブラリにバンドルされているコンシューマー ルールをローカルの keep ルールファイルにコピーし、不適切な keep ルールを除外するに記載されているようにバンドルされたルールを除外して、コピーされたルールを絞り込みます。ルールを絞り込む前後に R8 構成アナライザーを実行して、アプリの圧縮、最適化、難読化のスコアを比較します。
  3. 調査結果を報告する: R8 Configuration Analyzer レポートの実証データ(ワイルドカード ルールによって保持されている参照されていないクラスやメソッドの具体的な数など)を含むバグレポートをライブラリのメンテナーに提出します。

ケーススタディ: Gson が最適化で破損する理由

Gson は、リフレクションを多用するため、アプリの最適化で問題を引き起こすことが多いシリアル化ライブラリです。次のコード スニペットは、Gson の一般的な使用方法を示しています。この方法では、実行時にクラッシュが発生する可能性があります。Gson を使用して User オブジェクトのリストを取得する場合、コンストラクタを呼び出したり、ファクトリを fromJson() 関数に渡したりすることはありません。次のいずれも行わずにアプリ定義のクラスを構築または使用している場合、ライブラリがオープンエンド リフレクションを使用している可能性があります。

  • ライブラリ、標準インターフェース、またはクラスを実装するアプリクラス
  • KSP などのコード生成プラグイン
class User(val name: String)
class UserList(val users: List<User>)

// This code runs in debug mode, but crashes when optimizations are enabled
Gson().fromJson("""[{"name":"myname"}]""", User::class.java).toString()

Gson での R8 の仕組みについては、Gson のコンシューマー ルールをご覧ください。R8 がこのコードを分析し、UserList または User がどこにもインスタンス化されていないと判断すると、フィールドの名前を変更したり、使用されていないと思われるコンストラクタを削除したりして、アプリがクラッシュする可能性があります。同様の方法で他のライブラリを使用している場合は、アプリの最適化に干渉しないことを確認し、干渉する場合は使用を避けてください。

新しい Gson バージョンで @SerializedName を使用する

Gson のコンシューマー ルールと互換性のある方法でデータモデルを定義するには、次のスニペットに示すように、フィールドに @SerializedName アノテーションを付けます。

import com.google.gson.annotations.SerializedName

class User(@SerializedName("name") val name: String)
class UserList(@SerializedName("users") val users: List<User>)

@SerializedName アノテーションを使用すると、R8 は Gson バージョン 2.11.0 以降にバンドルされている保持ルールとモデルクラスを照合できます。R8 は、アノテーション付きのフィールドと必要なコンストラクタを自動的に保持するため、プロジェクトで ProGuard 構成を手動で管理することなく、ライブラリにバンドルされたルールを利用できます。

Room、Hilt、codegen を使用した Moshi は、アプリ定義の型を構築しますが、codegen を使用してリフレクションの必要性を回避します。