LoginSignup
129
104

More than 5 years have passed since last update.

Androidの勉強:Contextについて

Posted at

Java経験者だけどAndroid初心者の私が実装をしていて、あれ?? となったところをシリーズで解説していく「Androidビギナーズ」の第1回です。今回は何かと利用機会の多いContextを取り上げます。

developer.android.com の Contextページ

Contextはabstractなクラスで実体はサブクラスに定義されています。上記developerページに説明があるように、Contextはアプリ周りの環境にアクセスするためのインターフェースを担っており、リソース取得、Activityの起動、intentの送受信など幅広い用途に関わります。

よく使うサブクラスは Activity や Application かと思いますが、Contextの実体って意識して使い分けていますか?

Contextの取得方法としては以下があります。

(1) Activity の this
(2) Activity や Application の getApplicationContext
(3) View や Fragment の getContext

※他にApplicationなどContextWrapperのサブクラスであればgetBaseContext()というメソッドもありますが、これは使う機会が限られそうなので割愛します。

上記の3つの方法で取得したContextの実体は、次のようになります。
(1) Activity
(2) Application
(3) Activity

Contextを受けるメソッドでは、(1)(3)と(2)で同じように使えるかと思いきや、そうではないケースがあります。

例えば intentを使ってActivityを起動する場合ですが、(2)のgetApplicationContextを使って渡すとAndroidの古いバージョンではエラーが出る時があります。

Exception android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?

これを回避するには、Activity Context を渡すか、intentにFLAG_ACTIVITY_NEW_TASKをセットする必要があります。intentの動作制約がないので可能なら前者を選ぶのが良さそうです。何らかの理由でFLAGをセットする場合には以下のようにします。

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

参考までに、私が試した時は targetSDK=24 以降では前述のエラー(outside of an Activity context)が発生しませんでした。調べてみると android.app.ContextImplの実装がtargetSDK=23,24の間で以下のように変わっていました。

targetSDK=23


@Override
public void startActivity(Intent intent, Bundle options) {
    warnIfCallingFromSystemProcess();
    if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
        throw new AndroidRuntimeException(
                "Calling startActivity() from outside of an Activity "
                + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                + " Is this really what you want?");
    }
    mMainThread.getInstrumentation().execStartActivity(
            getOuterContext(), mMainThread.getApplicationThread(), null,
            (Activity) null, intent, -1, options);
}

targetSDK=24

    @Override
    public void startActivity(Intent intent, Bundle options) {
        warnIfCallingFromSystemProcess();

        // Calling start activity from outside an activity without FLAG_ACTIVITY_NEW_TASK is
        // generally not allowed, except if the caller specifies the task id the activity should
        // be launched in.
        if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0
                && options != null && ActivityOptions.fromBundle(options).getLaunchTaskId() == -1) {
            throw new AndroidRuntimeException(
                    "Calling startActivity() from outside of an Activity "
                    + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                    + " Is this really what you want?");
        }
        mMainThread.getInstrumentation().execStartActivity(
                getOuterContext(), mMainThread.getApplicationThread(), null,
                (Activity) null, intent, -1, options);
    }

targetSDK=24のソースコメントを読むと、FLAG_ACTIVITY_NEW_TASKを指定するかタスクIDを明示したケースに限り Activityの外からでも startActivityの実行を許可したとのことですが、実際には optionsのnullチェックが加わったことで判定結果が変わり Application Contextでも Activityを起動できる場合があるようです(検証した際は、optionsがnullだったので例外をスローしませんでした)。そうするとコメントの内容と動作が違っているので、現状が意図した動作なのかよく分からなくなりましたが。。

intentを使ったActivityの起動以外では、次のようなダイアログ表示も Application Contextではエラー(WindowManager$BadTokenException)になりますね。

AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage("test message");
builder.show();

もうややこしいから用途の広い Activity Contextを使えばいいのでは? と言うと そうもいかなくて、
{Activityのライフサイクル = Activity Contextのライフサイクル} の関係性があるため、Activityが終了したのにもかかわらず そのContextへの参照が残っているとGCがお掃除できなくてメモリリークが発生することになります。なのでActivityのライフライクルを超える用途でContextを渡すのであれば、Application Contextの利用を検討することになります。

逆にView周りのメソッドでContextを使う場合、Application Contextを利用するとActivityに設定したテーマが適用されませんので、Activity Contextを選ぶことになります。

Contextって最初から多用するので、もっと初心者に分かり易く設計してくれればいいのに・・と思わないでもないですが、その奥深さゆえにContextの探求はまだまだ楽しく?続きそうです。

129
104
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
129
104