言いたいことはそれだけか

KotlinとかAndroidとかが好きです。調べたことをメモします。٩( 'ω' )و

FragmentManager#getFragments()で取得するListの要素がnullになっていたのでFrameworkのソースコードを読んでみた

FragmentManager#getFragments() の挙動がなんか思っていたのと違ったので調べてみたメモ。

具体例を挙げると、Fragment A がattachされている状態で、Fragment B をFragmentTransaction#add() -> remove() した時に、
FragmentManager#getFragments() で得られるリストの要素数は1だと思ったけど、残念2でした〜〜という話。
0番目には予想どおり Fragment A が、1番目にはnullが入っていた。removeしたって言ったじゃないか。解せぬ。

というわけでいつも通りFrameworkのコードを読んでいきます。まずはgetFragments() で何が返ってきているのか確認。

    ArrayList<Fragment> mActive;

    @Override
    public List<Fragment> getFragments() {
        return mActive;
    }

この mActive って名前のArrayListにactiveな状態のfragmentが管理されていそうなもんなのに、なんでactiveでなくなったfragmentを保持していたであろう要素がremoveされずにnullになっているのか?

activeじゃなくなる時のコードを見てみましょう。

    void makeInactive(Fragment f) {
        if (f.mIndex < 0) {
            return;
        }
        
        if (DEBUG) Log.v(TAG, "Freeing fragment index " + f);
        mActive.set(f.mIndex, null);
        if (mAvailIndices == null) {
            mAvailIndices = new ArrayList<Integer>();
        }
        mAvailIndices.add(f.mIndex);
        mHost.inactivateFragment(f.mWho);
        f.initState();
    }

ここでnullがセットされています。
気になるのはその下、 mAvailIndices にinactiveになったfragmentのindexがaddされているところですね。
mAvailIndices はIntergerのArrayListで、

    ArrayList<Integer> mAvailIndices;

使われるのはFragmentがactiveになった時。

    void makeActive(Fragment f) {
        if (f.mIndex >= 0) {
            return;
        }
        
        if (mAvailIndices == null || mAvailIndices.size() <= 0) {
            if (mActive == null) {
                mActive = new ArrayList<Fragment>();
            }
            f.setIndex(mActive.size(), mParent);
            mActive.add(f);
            
        } else {
            f.setIndex(mAvailIndices.remove(mAvailIndices.size()-1), mParent);
            mActive.set(f.mIndex, f);
        }
        if (DEBUG) Log.v(TAG, "Allocated fragment index " + f);
    }

mAvailIndices Listの最後の要素をactiveにしたいfragmentのindexにセットしています。

つまり、 removeしたfragmentのindexを mAvailIndices に退避しておいて、 次にaddなりしてactiveになったfragmentにそのindexを割り当てるということをしています。

例えば、 3つのFragmentがActiveな場合、

index Fragment
0 A
1 B
2 C

ここで Fragment B をremoveすると、index 1 がnullになって、mAvailIndices にindex 1 が退避されます。

index Fragment
0 A
1 null
2 C

この状態で Fragment D をaddすると、退避されていたindex 1 が使用される。

index Fragment
0 A
1 D
2 C

indexの効率化が目的だったのかなー。