PC パソコン

Android Studio「ZoomableImageView must extend android.app.Activity [Instantiatable]」の解決方法

Android Studioでreleaseビルドを実行したとき、Lintが「ZoomableImageView must extend android.app.Activity [Instantiatable]」を報告して停止することがあります。典型的な原因は、カスタムViewをAndroidManifest.xmlの<activity>として誤登録していることです。この記事では、エラーの読み方から修正、確認までを整理します。

代表的なエラーログ

Lint found fatal errors while assembling a release target.

AndroidManifest.xml:54: Error: This class should provide a default constructor
(a public constructor with no arguments)
(jp.co.joker.kazuki.dangerous.substance.lite.ZoomableImageView)
[Instantiatable]
    android:name=".ZoomableImageView"
                  ~~~~~~~~~~~~~~~~~~

Error: ZoomableImageView must extend android.app.Activity [Instantiatable]

原因

<activity android:name=".ZoomableImageView">と書くと、AndroidはZoomableImageViewをActivityコンポーネントとして扱います。しかし実体がViewやAppCompatImageViewのサブクラスなら、android.app.Activityを継承していません。LintのInstantiatable検査は、Manifestに登録されたコンポーネントをシステムが生成できるか確認するため、Activity継承エラーとコンストラクタのエラーを同時に報告することがあります。

これは特定バージョンのAndroid StudioやAndroid Gradle Pluginの既知不具合ではなく、Manifestとクラス種別の不一致です。そのため「修正版への更新」ではなく、登録内容を直すのが解決策です。

発生条件

  • カスタムViewがView、ImageView、AppCompatImageViewなどを継承している。
  • そのクラスをAndroidManifest.xmlの<activity>に指定している。
  • lintReleaseやassembleReleaseでfatalなLintエラーが有効になっている。
  • product flavor、別のsource set、依存ライブラリから同じ誤登録がManifestマージされる場合もある。

推奨する解決手順

1. 誤ったActivity登録を削除する

次のような記述をManifestから削除します。

<activity
    android:name=".ZoomableImageView"
    android:exported="false" />

2. カスタムViewはレイアウトで使用する

完全修飾クラス名をレイアウトXMLのタグとして指定します。

<jp.co.joker.kazuki.dangerous.substance.lite.ZoomableImageView
    android:id="@+id/zoomableImageView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

3. XMLから生成できるコンストラクタを用意する

カスタムViewをXMLで使う場合は、ContextとAttributeSetを受け取れるコンストラクタを用意します。

class ZoomableImageView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : AppCompatImageView(context, attrs, defStyleAttr)

Manifestへの誤登録を残したまま、引数なしコンストラクタだけを追加する方法は推奨されません。クラスの役割の不一致が解消されないためです。

4. 画面として起動したい場合はActivityを別に作る

class ZoomableImageActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_zoomable_image)
    }
}

ManifestにはViewではなく、このActivityを登録します。

<activity
    android:name=".ZoomableImageActivity"
    android:exported="false" />

代替策

カスタムViewはZoomableImageView(context)のようにコードから生成することもできます。別source setやライブラリ由来のManifest項目が原因なら、Android Studioの「Merged Manifest」で追加元を特定し、可能なら追加元のManifestを修正してください。マージ規則による削除は最後の手段で、必要な正規Activityまで削除しないよう確認が必要です。

修正できたか確認する方法

  1. Android StudioのMerged ManifestでZoomableImageViewが<activity>配下に残っていないことを確認します。
  2. Viewを含むレイアウトを表示し、XML inflationが成功することを確認します。
  3. 次を実行してLintとreleaseビルドを再確認します。
./gradlew lintRelease assembleRelease

注意点

  • エラーを消すためにカスタムViewをActivity継承へ変更しないでください。ViewとActivityは役割もライフサイクルも異なります。
  • @SuppressLint("Instantiatable")やLint baselineで隠すと、実行時に起動不能となる構成ミスを見逃します。
  • debugで通ってreleaseだけ失敗する場合も、まずvariantごとのMerged Manifestを確認してください。
  • クラス名を変更した後の古いManifest記述が残っていないかも確認してください。

まとめ

ZoomableImageView must extend android.app.Activity [Instantiatable]は、カスタムViewをActivityとしてManifestへ登録したことが主因です。<activity>からViewを削除し、レイアウトまたはコードで使用します。画面遷移先が必要ならActivityを別クラスとして作成・登録し、最後にMerged ManifestとlintRelease assembleReleaseで確認してください。

公式情報