Activityのインスタンスはどこで保持されている?的なメモ
この記事のモチベ
Activityの強参照がnullになるとかどうとかで、いろいろ調べようと思ったんだけど
まずベースが曖昧じゃ問題になるので、Activityのインスタンスがフレームワーク周りでどうなってんのか調べようと思った。
何をここでメモるのか
Activityのインスタンスはどこにある?どうやって管理されている?
まぁつまりframework側でonCreateを呼んでいる部分ってどこよ?
でそれっていつ開放(開放自体はgcなのでシステム次第なので、ここでは参照元が消えること)されんの?
ってところ
どうやって調べたか
以下のコードでとりあえず調べると
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); for(StackTraceElement el: Thread.currentThread().getStackTrace()) Log.d(TAG, el.getClassName() + " | " + el.getMethodName()); setContentView(R.layout.activity_main); }
以下のようにonCreateが呼ばれているのがわかる。(ログ加工済み)
app.package.name.MainActivity onCreate android.app.Instrumentation callActivityOnCreate android.app.ActivityThread performLaunchActivity android.app.ActivityThread handleLaunchActivity android.app.ActivityThread access$1500 android.app.ActivityThread$H handleMessage android.os.Handler dispatchMessage android.os.Looper loop android.app.ActivityThread main java.lang.reflect.Method invokeNative java.lang.reflect.Method invoke com.android.internal.os.ZygoteInit$MethodAndArgsCaller run com.android.internal.os.ZygoteInit main dalvik.system.NativeStart main
とりあえずonCreateを直接呼び出してるandroid.app.Instrumentationがactivityを管理してんじゃね?ってことで見てみる。
/** * Perform calling of an activity's {@link Activity#onCreate} * method. The default implementation simply calls through to that method. * * @param activity The activity being created. * @param icicle The previously frozen state (or null) to pass through to onCreate(). */ public void callActivityOnCreate(Activity activity, Bundle icicle) { prePerformCreate(activity); activity.performCreate(icicle); postPerformCreate(activity); }
だめだ。ここじゃねえ。
ってことで、android.app.ActivityThreadを見てみたけど、やっぱりここだった。
※android.app.Instrumentationの詳細はここではほっておくが、Activityの呼び出し処理に中間のクラスかませることで何か利点があるのだろう。
フレームワーク側の都合の部分だろうし、Activityインスタンス管理としては多分あんまり関係ないのでほっておく。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) というメソッド。
ここでまずandroid.app.Instrumentationの中でClassLoader使ってActivityがnewされているのがわかる。
そのあと
ActivityClientRecordオブジェクトのフィールドにactivityの参照を渡して、mActivities(ArrayMapクラス)にtokenをキーにputされている。
こいつと、performLaunchActivityがActivityを返しているのでその呼び出し元二つで参照されるのがわかった。
そのあと同クラス内performDestroyActivity()メソッド内でmActivitiesからtokenをキーにremoveされている。
@Override protected void onDestroy() { super.onDestroy(); for(StackTraceElement el: Thread.currentThread().getStackTrace()) Log.d(TAG, el.getClassName() + " | " + el.getMethodName()); }
でonDestroy()の呼び出し元を探ると案の定ActivityThreadクラスのperformDestroyActivity()メソッドが呼ばれていた。
つまり、残すところperformLaunchActivityを呼び出した側でどうなっているかを確かめるとわかりそう。
・・・・。(調査中)
ん?handleLaunchActivity()メソッド内で呼ばれていたが、ActivityClientRecordオブジェクトの状態変更のトリガーにしているだけだった。
他にはActivityClientRecordオブジェクトからactivityを直接参照している部分があるくらいで、それでもローカルで参照する範囲にとどまっていた。
調査結果
よって、Activityの参照については、onCreate()中からonDestroy()後(中ではない)までがフレームワーク上で参照されている期間と考えるのが妥当そう。
↑図が間抜けだけど。こんな感じ。
まとめ
今回はまぁ序の口というか、まぁ基本レベルな内容で、そうだろうねってところを押さえておいた。
あんまり新しい知識として何か得るものはなかったかな。
Activityの自体のインスタンスの強参照はここ(ActivityThread)のみにして作り、僕らが作るActivity上では弱参照でActivityのインスタンスを保持しておけば
つまり下記コード。
class MyActivity extends AppCompatActivity{ private WeakReference<MyActivity> weakActivity; @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); weakActivity = new WeakReference<>(this); } @Nullable MyActivity getActivity() { // 実際には、単に今がガベージコレクションに適したタイミングであるかもしれないということを提案するだけなので // ここで確実にactivityのインスタンスがnullになる保証はないはず System.gc(); return weakActivity.get(); } }
でActivity自身のリークは阻止しやすくはできんのかなって思うけど、
Collectionクラス周りの参照リークとか、ImageViewのDrawableのリークとか別に阻止できるわけではないからonDestroy()で色々するっきゃないのよねー。
なんかモヤモヤする。