pooh3's memo

Android周りを中心に。軽めに、いい加減さ50%程度で

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()後(中ではない)までがフレームワーク上で参照されている期間と考えるのが妥当そう。

f:id:pooh3mobi:20160329024606p:plain
↑図が間抜けだけど。こんな感じ。

まとめ

今回はまぁ序の口というか、まぁ基本レベルな内容で、そうだろうねってところを押さえておいた。
あんまり新しい知識として何か得るものはなかったかな。
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()で色々するっきゃないのよねー。
なんかモヤモヤする。

余談

ソースコードを見ていて思ったのはHandler周りはHandlerのメッセージを使ったコマンダーパターンとかの実装するときに見てみるといいかもしれんね。
onCreateの呼び出し元のはるか上の方にZygoteってのが出てくるけど、こいつはJavaだと起動がどうしても遅くなるので、クラスの中身を先読みして
クラス自体の初期化と分離することで早くする的なやつだった気がするけど、適当です。以上。

次回予定

次回はFragment周りのフレームワークがわのFragmentだったりActivityの参照管理と、実装上の懸念点について押さえていこうと思います。
Activity周りのこれどうなってんのかって疑問があったら調べるので、Twitterやなんかで調査依頼でもしてみてください。


いつかメモリリークの原因について「あるある的なもの」をまとめて、対策をブログで特集してみたいですねー。

なんかはじめてそれっぽく書いたっきがする。