Fragmentで引数なしコンストラクタを消したらハマった話


頭から結論ですが、「Fragmentを実装するとき、引数なしコンストラクタは必ず実装するように」という話です。

以前、Fragmentで実装された画面部品からActivityが保持する画面部品に対して操作をさせようとするために、Listenerを実装してActivityからFragmentにコンストラクタ経由で渡すように実装したところ、罠にはまりました。

というのも、Fragmentはメモリが枯渇すればActivityとは別のタイミングで破棄されるので、Fragmentが必要になれば、いつでも再生成させるようになっています。
そのときOS側が再生成するのに利用するのは、該当Fragmentの【引数なし】コンストラクタです。

そうとも知らず、Fragmentの引数ありコンストラクタを実装して空のコンストラクタを削除すると、以下のようなエラーが【たまに】出るようになります。

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.sample.SampleActivity}: android.support.v4.app.Fragment$InstantiationException: Unable to instantiate fragment com.sample: make sure class name exists, is public, and has an empty constructor that is public
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2194)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2244)
    at android.app.ActivityThread.access$600(ActivityThread.java:149)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1246)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:213)
    at android.app.ActivityThread.main(ActivityThread.java:5092)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:511)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:797)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:564)
    at dalvik.system.NativeStart.main(Native Method)
Caused by: android.support.v4.app.Fragment$InstantiationException: Unable to instantiate fragment com.sample: make sure class name exists, is public, and has an empty constructor that is public
    at android.support.v4.app.Fragment.instantiate(SourceFile:413)
    at android.support.v4.app.FragmentState.instantiate(SourceFile:97)
    at android.support.v4.app.FragmentManagerImpl.restoreAllState(SourceFile:1790)
    at android.support.v4.app.FragmentActivity.onCreate(SourceFile:213)
    at android.support.v7.app.ActionBarActivity.onCreate(SourceFile:97)
    at com.sample.SampleActivity.onCreate(SourceFile:100)
    at com.sample.SampleActivity.onCreate(SourceFile:100)
    at android.app.Activity.performCreate(Activity.java:5104)
    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1080)
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2158)
    ... 11 more
Caused by: java.lang.InstantiationException: can't instantiate class com.sample; no empty constructor
    at java.lang.Class.newInstanceImpl(Native Method)
    at java.lang.Class.newInstance(Class.java:1319)
    at android.support.v4.app.Fragment.instantiate(SourceFile:402)
    ... 20 more

このエラーの厄介なところはシステムが一旦Fragmentを破棄し、再び利用されたときにしか再現しないので、普通に端末テストをしただけでは再現しないということです。

ちなみにこの話はあんざいゆき先生のブログでも紹介されている。

Android Fragment で setArguments() してるサンプルが多いのはなぜ?

ただ、AndroidStudioではFragmentに引数なしコンストラクタがないとエラーを表示するようになったので、この罠を踏む人は少なくなるのかもしれない。

ちなみに、このハマった問題はやっぱりあんざい先生のブログからヒントを得て改善しました。
※あんざい先生はこの方法は好きではないとおっしゃっていますが。。。

Fragment から Activity にコールバックする方法

この場を借りてあんざい先生に感謝。