基本的にAndroidで開発して行く上でContextが必ずといっていいほどつきまとう。Application#getApplicationContextで取得できるContextを簡単かつ安全に使えたらってことで今日の話
ネストが深いところでContextが必要になってくると、それ以前の引数にContextを追加しないといけなくなる。 メンテナンスや改良をする際に厄介になることは言うまでもない。つまり、どこでも簡単にApplication#getApplicationContextで得られるContextを使えたら開発者もハッピーになるわけだ。
まずはContextについて
・Contextには「ActivityのContext」と「ApplicationのContext」の2種類が存在する
- ActivityのContextはよく使われるthis。ApplicationContextはApplication#getApplicationContext()
この2つのContextの違いと問題について ActivityのContextは、Activityのライフサイクルに依存する。そのためActivityが破棄される時にContextも破棄しないとメモリリークが発生する。 Contextが保持されたままだとActivityは論理的に終了(onDestroy)するが、ActivityのObject類は破棄されない。finalize()が呼ばれない状態。finalize()とはClassがメモリも含め終了した時にGC実行時に呼ばれる。finalize()はObjectクラスのメンバ。つまりはGCの対象とならない。 Activityは頻繁に破棄、生成される。画面を回転しただけでもデフォルトの設定だとonDestroyされる。デフォルトではシステムが現在のActivityを破棄しその状態を保持しながら新しいActivityを生成しようとするかららしい。なのでActivityのライフサイクルにContextを依存させないといろいろ厄介。
一方ApplicationContextはアプリケーションに依存する。つまりアプリケーションが生きている限り有効。アプリケーションに依存するクラス、複数のクラスから呼ばれたりする時などに使用するのが望ましい。ダイアログなどのContextにApplicationContextは使用できないので注意。
AlertDialog.Builder builder = new AlertDialog.Builder(this.getApplicationContext()); //★ builder.setTitle("test"); ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1); adapter.add("item"); builder.setAdapter(adapter, null); builder.setPositiveButton("OK", null);
★の部分でこんなエラーが発生する(はず)
E/AndroidRuntime(4578): Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application
まとめると ActivityContextはActivityと共同体 ApplicationContextはApplicationと共同体 です。なんでもかんでもApplicationContextを使えばいいってわけでもないので、よく考えて使いましょう。 ここに詳しく書いてあります
で、話を戻してApplicationContextをどこからでも参照できるようにします
public class AppContext { private static AppContext Instance = null; private Context ApplicationContext; // // publicはつけない // このメソッドのアクセスレベルはパッケージローカルとする // 念のため意図しないところで呼び出されることを防ぐ // static void onCreateApp(Context ApplicationContext) { // // Application#onCreateのタイミングでシングルトンが生成される // Instance = new AppContext(ApplicationContext); } private void AppContext(Context ApplicationContext) { this.ApplicationContext = ApplicationContext; } public static AppContext getInstance() { if (Instance == null) { throw new RuntimeException("MyContext should be initialized!"); } return Instance; } } public class Main_Application extends Application { // // Application#onCreateは、ActivityやServiceが生成される前に呼ばれる。 // @Override public void onCreate() { super.onCreate(); AppContext.onCreateApp(getApplicationContext()); } }
こうしておけば
AppContext.getInstance().getApplicationContext();
で参照できる。でも新たにクラスを作るのが嫌だという場合はMain_Applicationクラスの内部でInstanceを保持させる
public class Main_Application extends Application { private static Main_Application Instance = null; @Override public void onCreate() { super.onCreate(); Instance = this; } public static Main_Application getContext() { return Instance; } }
こうすれば
Main_Application.getContext().getApplicationContext(); Main_Application.getContext();
実際、Main_ApplicationはContextとしても使えるので、上の2つどちらの書き方でも問題はない。
となると
AppContext.getInstance().getApplicationContext();
よりも
Main_Application.getContext();
のような書き方の方が、コード量も多少ではあるが少なくなる。ということで先ほどのコードを少し修正
AppContext#getContext()を修正します
public static AppContext getContext() { if (Instance == null) { throw new RuntimeException("MyContext should be initialized!"); } return Instance; }
から
public static Context getContext() { if (Instance == null) { throw new RuntimeException("MyContext should be initialized!"); } return Instance.getApplicationContext(); }
こうしておけば
AppContext.getContext()
でApplicationContextを参照できるようになります。
以上、今日はここまで