知った気になってて、実は調べてないってこと意外と結構ありますよね。
今日は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のマメ知識を書こうと思います。