Android開発では、Context型のオブジェクトを使い回すことがやたら多い。
問題はActivity(もしくはService)の中で利用できるContext型のオブジェクトはActivity(Service)のインスタンス自身と、getApplicationContext() で取得できるApplicationのインスタンスの二種類があるということだ。
// 例えばActivity内でトーストを表示するとき、以下2通りのContextを渡せる。 Toast.makeText(this, "Activity", Toast.LENGTH_SHORT).show(); Toast.makeText(getApplicationContext(), "Application", Toast.LENGTH_SHORT).show();
このあたりについては各所でまとめられている。例えばこちらのブログなんかにうまくまとまっている。
http://individualmemo.blog104.fc2.com/blog-entry-41.html
また、こちらのサイトではわかりやすくActivityやApplicationの違いや役割が図解されている。
http://magpad.jugem.jp/?eid=197
で、どちらかというと「Applicationのほうを使いましょう」というのが基本だ。特に独自クラスなんかで半永久的にContextを保持しておくような場合はなおさらだ。先のサイトでも言及されているが、ActivityやServiceは常にkillされ破棄される可能性があり、変に保持しっぱなしにするとメモリーリークの原因になる他、いつのまにか実体がいなくなって例外を吐いたりなどなど。
よって、独自クラスでContextを持つ必要があるときは大体getApplicationContext()の戻り値を渡すようにする。が、しかし、そういうルールの下で開発を進めていくと、ついつい「contextの実体はほとんどApplicationContextだ」と思い込んでしまう。実際問題として、たまーにActivityが渡されてくる場合もあるので注意が必要である。
例えば、独自で実装した拡張ViewクラスをレイアウトXMLに定義した場合、独自Viewクラスのコンストラクタで渡されるcontextはsetContentViewしたActivityインスタンスだったりする。どういうことかというと、
public class RedButton extends Button { // XMLでlayout定義してsetContentViewでinflateしてもらうにはこのコンストラクタが必要くさい public RedButton(Context context, AttributeSet attrs) { super(context, attrs); super.setBackgroundColor(0xFFFF0000); //デフォルトで赤くする } }
こんな感じで新たに定義した独自ボタンクラスをレイアウトXMLで配置してアクティビティで呼び出した場合の上記contextの実体は呼び出したActivityオブジェクトが渡される。勿論、ActivityにセットしたViewなのだからこのRedButtonのインスタンスがActivityへの参照を保持しておくのは問題無い(アクティビティが死ねばボタンも消えるだろう)。しかしこのRedButtonクラスの中でクラス変数に代入…、いやそこまでじゃなくとも、別クラスに渡してしまうと厄介である。RedButtonのインスタンスとともに破棄されるインスタンス変数に渡すのなら問題ないのだが、Activityのライフサイクルを越えて保持される危険のあるオブジェクトに渡すのは危険である。
Contextの実体に注意していればそのような渡し方はしないだろうが、普段からcontextにApplicationContextを使うようにして「context イコール ApplicationContextだ」という認識にはまってしまった場合、「ActivityのインスタンスをApplicationContextのつもりで保持してしまう」ということをうっかりやってしまう可能性がある。
ということで、contextを貰ってきてそれを使い回す可能性があるのであれば、なるべくその実体がなんであるか明示して保持しておくようにしたい。例えば上記RedButtonクラスではActivityが渡って来るとわかっているのだから、
public class RedButton extends Button { private Context mAppContext; // XMLでlayout定義してsetContentViewでinflateしてもらうにはこのコンストラクタが必要くさい public RedButton(Context context, AttributeSet attrs) { super(context, attrs); super.setBackgroundColor(0xFFFF0000); //デフォルトで赤くする mAppContext = context.getApplicationContext(); } }
最初からこうしておけば後から追記する際もきちんと区別出来るだろう。貰ってきたcontextの実体がApplicationContextでもcontext.getApplicationContext()の戻り値は同じ(はず)なので、contextを保持したい場合は必ずこのメソッドを呼んでおくことで保証出来る。
まぁこういったトラブル回避策は人それぞれ好みがあるのでなんともいえないけども。
追記:2011/7/1
なんでもかんでもApplication渡せばいいってもんじゃないことを思い出したので、そのことについて次書きます。
「[Android] Contextの持ち方~ApplicationContext」への1件のフィードバック