36
31

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ApplicationContextはいつどこでつくられているのか? ~SDKのソースコードを読む~

Last updated at Posted at 2016-08-22

知った気になってて、実は調べてないってこと意外と結構ありますよね。

今日はHandlerのhadleMessageあたりを調べていると
ふとApplicationContextが気になったので調べてみました。

手がかりはやはり。getApplicationContext()の実装です。
ではまずgetApplicationContext()はどこで実装されているのでしょう?

Activityの継承関係について

まずActivityの継承関係をまとめてみます。

Activity -> ContextThemeWrapper -> ContextWrapper -> Context

普通に継承関係をたどるとこうなります。

そこで実際にgetApplicationContextが実装してあるのはどこでしょう?

getApplicationContext()を実装してあるところを探せ!

ありました。意外とあっけないです。すぐでした。
それはどこかというとContextWrapperです。
ContextWrapperの関係のある部分を書き出してみると以下になります。

public class ContextWrapper extends Context {
    Context mBase;

    public ContextWrapper(Context base) {
        mBase = base;
    }

    @Override
    public Context getApplicationContext() {
        return mBase.getApplicationContext();
    }
}

なるほど、このコードだとmBaseの実装がgetApplicationContext()のきっと中身を渡してくれているのでしょう。

mBaseに代入しているころがキーになりそうです。

ContextWrapperのmBaseはどこからくる?

ではActivityが生成されるなかでmBaseが代入されるのはContextWrapperをnewしているところでしょうか?
いいえ違います。ContextWrapperをnewしているところは今のところ見つかってません。
そのかわり別のところで代入しているのをみつけました。

ここの答えはContextWrapperのattachBaseContext()メソッドです。

attachBaseContext()メソッドは、親のContextにはなくContextWrapperで定義されたメソッドの様です。
ということは、ActivityかContextThemeWrapperで呼ばれているのでしょう。
これも簡単。attachBaseContext()メソッドはActivityのattach()メソッドの中でよばれていました。

ここまでのまとめ

ここまでまとめるとこうです。

Activityの継承関係は下記のようになっていて、

Activity -> ContextThemeWrapper -> ContextWrapper -> Context
getApplicationContextはActivityのattach()メソッドからわたされるContext引数のようです。

ここからとうとうActivityがどう生成されてくるのかを追うことになります。

Activityはどこで生成されるのか?

ではここでActivityが生成される流れについて考えていきます。
みなさんはonCreateがどこから呼ばれているのか知ってますか。
簡単に知ることができます。

プロジェクトを生成して作ったMainActivityの中でonCreateの中で以下のコードを書きましょう。するとログにあるものがでてきます。

  @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
        for(StackTraceElement e: stacktrace) {
            String className =e.getClassName();
            String methodName = e.getMethodName();
            Log.d("MainActivity", className + "#" + methodName);
        }
    }

これを実行すると以下のログが出力されます。

      D/MainActivity: dalvik.system.VMStack#getThreadStackTrace
      D/MainActivity: java.lang.Thread#getStackTrace
      D/MainActivity: com.example.MainActivity#printStackTrace
      D/MainActivity: com.example.MainActivity#onCreate
      D/MainActivity: android.app.Activity#performCreate
      D/MainActivity: android.app.Instrumentation#callActivityOnCreate
      D/MainActivity: android.app.ActivityThread#performLaunchActivity
      D/MainActivity: android.app.ActivityThread#handleLaunchActivity
      D/MainActivity: android.app.ActivityThread#-wrap11
      D/MainActivity: android.app.ActivityThread$H#handleMessage
      D/MainActivity: android.os.Handler#dispatchMessage
      D/MainActivity: android.os.Looper#loop
      D/MainActivity: android.app.ActivityThread#main
      D/MainActivity: java.lang.reflect.Method#invoke
      D/MainActivity: com.android.internal.os.ZygoteInit$MethodAndArgsCaller#run
      D/MainActivity: com.android.internal.os.ZygoteInit#main

ここでActivityThreadというクラスが出てきました。ログから推測すると
どうやらHandleクラスからMessageを取得して、handleMessageでMessageの内容を振り分け
その結果handleLaunchActivity()メソッドが呼ばれているようです。

handleLaunchActivity()を日本語に置き換えると、Activityの起動をハンドル()
きっとここがActivityを生成するところなのでしょう!

Activityが生成され初期化される流れ

ActivityThreadクラスのhandleLaunchActivity()ではActivityClientRecordという謎のクラスオブジェクトの参照をhandleMessage()メソッドのMessageオブジェクトからもらってきているようです。

ここで一気にActivityのインスタンスが生成されているところを探してみましょう。

            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);

ありました。performLaunchActivity()メソッドの中に上記の様なコードがありました。

ここでClassLoaderを利用してActivityの参照をデバイスのメモリ上に展開するようです。

そのご

            Application app = r.packageInfo.makeApplication(false, mInstrumentation);

でApplicationクラスのインスタンス(オブジェクトの参照)が生成されています。

※LoadedApkクラスのmakeApplication()メソッドの実装をみると、すでにApplicationインスタンスが生成されていればそれを返すので、おそらく、一度アプリを起動して、MainActivityから他のActivityを呼ぶときは、二度も生成しないということなんでしょうね。なるほど。

makeApplication()メソッド内をよーくみると
ContextImplクラスのインスタンスが生成されているようにみえます!

            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);

ここです。この部分です。
newApplication()メソッドの実装を追ってみると、ここでApplicationインスタンスからattach()メソッドが呼ばれ、上のappContextをattach()メソッドの引数にとっています。
つまり、まず作られるのはActivity。その次がContextImplで、その次にApplicationインスタンスが生成されていなければ生成し、生成時にはContextImplインスタンスをApplicationインスタンスに渡しています!

getApplicationContextとはこのContext(ContextImpl)なのでしょうか?

でもちょっとまって、落ち着いて次の流れを見ていきましょう。

performLaunchActivity()メソッドまで戻ってくると、

                Context appContext = createBaseContextForActivity(r, activity);

と生成したContextを

  activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor);

先ほど生成したactivityのインスタンスに渡しています。
なんと、このContextがActivityクラスの祖父であるContextWrapperクラスのmBaseの正体です!
先ほどのApplicationのattachメソッドで渡されているContextの実装は別ものだったようです。
(注意がありますActivityはContextWrapperの孫にあたりますが、ApplicationクラスはContextWrapperの子供、つまりActivityのおじさんにあたるのがApplicationで、どっちもmBaseを持ちます。)

しかし、やっとたどりつきました。
ここまで長い道のりでした。

ということは先ほどのperformLaunchActivity()メソッド内のコード

                Context appContext = createBaseContextForActivity(r, activity);

ここでくつられたContextがgetApplicationContext()で渡されるものの正体を握っていることになります。

ここまでのまとめ2

またここでいったん頭の中を整理しましょう。

・ActivityのgetApplicationContextはContextWrapperのmBase.getApplicationContext()メソッドが返すインスタンスでした。

・そしてそのContextはContextImplインスタンスで、ActivityThreadのperformLaunchActivity()の流れ中で生成されていました。

・そして実はApplicationクラスが保持するContextもContextImpleのインスタンスで、二つのContextImplは別ものです。二つのContextImplのインスタンスがこの流れで生成されることがわかりました。(Applicationインスタンスが既に生成されていればActivityが保持するインスタンスのみになります。)

いよいよ佳境です。

getApplicationContext()で渡される参照の正体

ではここまでの流れだとContextImplがgetApplicationContext()の実装担当者です。
早速見てみましょう!

    @Override
    public Context getApplicationContext() {
        return (mPackageInfo != null) ?
                mPackageInfo.getApplication() : mMainThread.getApplication();
    }

読み通りでした。しかしmPackageInfoがnullだった場合とそうでない場合で返すものが違います。
ここで二つのContextImplインスタンスの生成を見直します。

まずApplicationが保持する方のContextImpl

            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);

次にActivityが保持する方のContextImpl

        ContextImpl appContext = ContextImpl.createActivityContext(
                this, r.packageInfo, displayId, r.overrideConfig);

そうです。
どっちもpackageInfoインスタンスは渡してますから、きっとmPakageInfo.getApplication()です!
そして、mPakageInfoはどこからきたでしょう?
それはperformLaunchActivity()メソッドの引数の一つActivityClientRecodeのメンバです。
つまり、二つのContextImplインスタンスが保持しているmPackageInfo(LoadedApkクラスのインスタンス)は全く同じものです!

そうこれが!ApplicationContextです。
一つのアプリに一つしか持てないLoadedApkのmApplicationインスタンスがgetApplicationContext()で返されるインスタンスの正体だったんです!
わかってよかったです!

ということは。

    Context appContext = MainActivity.this.getApplication().getApplicationContext();

    Context appContext = MainActivity.this.getApplicationContext();

は全く同じインスタンスが返ります。

ですが。

    Context baseContext = MainActivity.this.getApplication().getBaseContext();

    Context baseContext = MainActivity.this.getBaseContext();

そしてAcitivity生成時ContextImplは毎回生成されるのだから。

    Context baseContext = SecondActivity.this.getBaseContext();

は別ものです!

そしてActivityにAddされているFragmentのgetContext()は、そのActivity自身です。(なぜそうなるか気になる方はFragmentHostCallbackクラスの実装をみてください!)

いつどこでつくられているか?

え?結局どこでつくられているのかって?わすれてました。はい。
LoadedApkクラスのmakeApplication()メソッドの中です。
返すmApplicationインスタンスがnullであれば

            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);

によって作られています。Instrumentationでは

        Application app = (Application)clazz.newInstance();
        app.attach(context);

とnewされず、引数で呼ばれたClass>がnewInstance()しているだけでした。

さいごに

長文駄文で申し訳ないです。
ここまで読んでくださった方、ありがとうございました!

SDKのソースコードはどこでみつければいいのかなどは、別の誰かさんが書いてくれていると思いますので省いてます。
それでもわからないときは、AndroidSDKの中にあるsourceフォルダの中をのぞいてみて、それをAndroidStudioにD&Dすれば見れるはずです。

今回のコードはAndroid/sdk/sources/android-23/以下を参照してます。

次回はHandlerのマメ知識を書こうと思います。

36
31
1

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
36
31

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?