InflateExceptionの解読
android.view.InflateExceptionは、汎用的なラッパーだと考えてください。これは、XMLコードをJavaやKotlinのオブジェクトに変換しようとした際に、LayoutInflaterがクラッシュしたことを示しています。エラーはレイアウト内の特定の行(例えば12行目など)を指していますが、真の原因は通常、スタックトレースのより深い場所に隠れています。
ログにError inflating classと表示されている場合、システムがカスタムビューのインスタンス化に失敗した可能性があります。これは、コンストラクタの不足、パッケージパスのタイポ、あるいはビュー自身の初期化コード内でのクラッシュなど、いくつかの理由で発生します。「Caused by」セクションを見つけるために、エラーの最初の数行よりも先を確認する必要があります。
主な原因と修正方法
1. (Context, AttributeSet) コンストラクタの不足
これは最も頻繁に発生する原因です。XMLでビューを定義すると、Androidシステムは単純な(Context)コンストラクタを呼び出しません。代わりに、ContextとAttributeSetの両方を受け取るコンストラクタを探します。この2番目の引数が欠けていると、インフレートは即座に失敗します。
修正方法 (Kotlin): @JvmOverloadsアノテーションを使用します。これにより、必要なすべてのコンストラクタが自動的に生成されます。
class CustomView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
// 初期化ロジック
}
修正方法 (Java): 2つの引数を持つコンストラクタを明示的に記述する必要があります。多くの開発者は、念のため3つすべてを実装します。
public class CustomView extends View {
public CustomView(Context context) { super(context); }
// XMLからのインフレートにはこのコンストラクタが必要です
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
2. リファクタリング後のパッケージパスの不整合
ファイルの移動は開発において一般的な作業です。しかし、CustomView.ktをcom.example.uiからcom.example.widgetsに移動した場合、Android StudioがすべてのXMLファイルを更新するとは限りません。完全なクラスパスに1文字でもタイポがあると、インフレーターは動作しなくなります。
XMLを再確認してください。タグが現在のパッケージ構造と完全に一致しているか確認します。
<!-- フルパスは100%正確である必要があります -->
<com.example.widgets.CustomView
android:id="@+id/myCustomView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
3. initブロック内でのクラッシュ
ときにはXMLが完璧であっても、ビュー内部のコードが壊れていることがあります。initブロックやコンストラクタがnullオブジェクトや存在しないリソースにアクセスしようとすると、システムはそれをインフレートエラーとして報告します。これは、テーマに依存するIDを使用してResources.getColor()を呼び出そうとしたが、そのIDがまだ利用できない場合などによく発生します。
Logcatで2番目または3番目の「Caused by」行を確認してください。NullPointerExceptionやResources$NotFoundExceptionが表示されている場合、問題はレイアウトファイル自体ではなく、ロジックにあります。
4. R8およびProguardによるリソース削減
アプリがデバッグモードでは動作するのに、リリースモードでクラッシュしませんか?これは通常、R8(コードシュリンカー)が、カスタムビューのコンストラクタがどこからも使われていないと判断して削除してしまったことを意味します。インフレートはリフレクションを使用するため、R8はXMLとコードの間のつながりを常に認識できるわけではありません。
ビューを保護するために、proguard-rules.proファイルにkeepルールを追加してください。
# すべてのビューとそのコンストラクタを保持する
-keep class com.example.widgets.** { *; }
# または、特定のクラスのみを正確に保持する場合
-keep class com.example.CustomView { <init>(...); }
根本原因を素早く見つける方法
推測で時間を無駄にしないでください。以下のワークフローに従ってバグを特定しましょう。
- **スタックトレースをスキャンする:** **一番最後**の「Caused by」エントリを探してください。これが連鎖反応を引き起こした実際の原因です。
- **行番号を確認する:** ログに`line #14`とある場合は、XMLを開いて数えてください。その行は本当にカスタムビューですか?
- **レイアウトプレビューを確認する:** Android Studioのデザインタブでビューが表示されない場合、問題はコンストラクタまたは`onDraw`メソッドにある可能性が高いです。
- **標準ビューでテストする:** 一時的にカスタムビューを`FrameLayout`に置き換えてみてください。クラッシュが止まれば、問題は間違いなくカスタムクラスの内部にあります。
最終確認
修正を適用したら、以下の手順で問題が解決したか確認します。
- **クリーンとリビルド:** `Build > Clean Project`を使用して、古いアーティファクトを消去します。
- **異なるAPIレベルでのテスト:** リソースの処理方法の違いにより、一部のインフレートエラーは古いAndroidバージョン(API 21とAPI 33など)でのみ発生することがあります。
- **Logcatを確認する:** 問題が発生していた画面に移動します。ログにエラーが出なければ、解決です。

