Androidアプリでstaticフィールドは絶対ではない
Androidアプリで、Contextをどこからでも参照する方法として以下のようなコードがよく紹介されている。
- staticでApplication Contextを保持するクラス
public class ContextHolder { private static Context mContext; public static void setContext(Context context) { mContext = context; } public static Context getContext() { return mContext; } }
- アプリのプロセス起動時に実行されるApplicationクラスの
onCreate()
でContextをセット
public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); ContextHolder.setContext(this); } }
あとはContextHolder.getContext()
で任意のクラスからContextを取得できる、というわけだ。
しかし、実はこのコードは問題を孕んでいる。
Android StudioでContextをstaticフィールドに突っ込むと「メモリリークするよ!」と警告が出るが、もっと深刻な問題は知らぬ間にnullになることである。
ご存知の通り、Androidはメモリが逼迫するとバックグラウンドにあるActivityやらServiceやらを殺していく。殺されるのはそういったAndroid特有のオブジェクトだけではなく、アプリのライフサイクルとは無縁の上記ContextHolder
のようなオブジェクトも例外では無い…ように感じる。というのも、実際に上記のようにstaticで参照を保持しているフィールドにアクセスするアプリで、ぬるぽでクラッシュしたレポートがコンスタントに上がってきているので。クラスがUnloadされると、次回必要になった際に再Loadされる。その時に初期値を設定していないstaticフィールドはnullで初期化される。そこで本来はApplicationクラスのonCreate()
でContextを設定したいところが、プロセスまでは殺されていないためにApplicationクラスのonCreate()
が再度呼ばれず、staticフィールドはnullのままになってしまうのである。
以下にちらっと書いてあるが、アプリをロードしたClassLoaderごとGCされることがあると示唆されている。
The class references, field IDs, and method IDs are guaranteed valid until the class is unloaded. Classes are only unloaded if all classes associated with a ClassLoader can be garbage collected, which is rare but will not be impossible in Android.
別の例として、以下のようにApplicationクラス内にstaticで持つコードも見かける。さすがにApplicationクラスはUnloadされないと思うし、されたとしても再Load時にonCreate()
が呼ばれるはずなので、この場合はおそらく問題ないと思う。
public class MyApplication extends Application { private static Context mContext; @Override public void onCreate() { super.onCreate(); mContext = this; } public static Context getContext() { return mContext; } }
ClassLoaderごとUnloadされる状況をあえて作り出すのは容易ではなさそうなので、実際に動作確認したわけではなくあくまで予測ですが…