1. 程式人生 > >淺析 Fragment 回退棧

淺析 Fragment 回退棧

    在我們使用 fragment 的時候 ,總是會使用到 fragmentTransaction 的 add remove 和 replace 方法, 這些方法對 fragment 生命週期有著不同的影響, 在來個 回退棧, 就更加容易混淆.

    我們通過開啟回退棧和關閉回退棧來分別檢視 fragment 的生命週期來了解 fragment 回退棧對其生命週期的影響.

不使用任何回退棧, 程式碼如下:

private void initialize() {

        FirstFragment firstFragment = FirstFragment.newInstance("1", "2");
        SecondFragment secondFragment = SecondFragment.newInstance("3", "4");

        firstFragmentBtn.setOnClickListener((View v) -> {
            FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
            fragmentTransaction.add(R.id.frame_layout, firstFragment);
//            fragmentTransaction.addToBackStack(null);
            fragmentTransaction.commit();
        });

        secondFragmentBtn.setOnClickListener(v -> {
            FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
            fragmentTransaction.add(R.id.frame_layout, secondFragment);
//            fragmentTransaction.addToBackStack(null);
            fragmentTransaction.commit();
        });

        backBtn.setOnClickListener(v -> {
//            getSupportFragmentManager().popBackStack();
            getSupportFragmentManager().beginTransaction().remove(secondFragment).commit();
        });

    }

    我們通過簡單的 add 方法一直往 frameLayout 中新增 fragment, 其效果就是一直把 fragment 的檢視疊加到 frameLayout 上面, 下層檢視不會移除.

    日誌如下:

-------- 點選第一個按鈕 --------

D/FragmentFirst--->onAttach======: first
D/FirstFragment--->onCreate======: first
D/FirstFragment--->onCreateView======: first   地址:@118390022
D/FirstFragment--->onActivityCreated======: first

-------- 點選第二個按鈕 --------

D/SecondFragment--->onAttach======: second
D/SecondFragment--->onCreate======: second
D/SecondFragment--->onCreateView======: second   地址:@34476387
D/SecondFragment--->onActivityCreated======: second

-------- 點選回退按鈕 --------

D/SecondFragment--->onPause======: second
D/SecondFragment--->onStop======: second
D/SecondFragment--->onDestroyView======: second
D/SecondFragment--->onDestroy======: second
D/SecondFragment--->onDetach======: second

-------- 再點選第二個按鈕 --------

D/SecondFragment--->onAttach======: second
D/SecondFragment--->onCreate======: second
D/SecondFragment--->onCreateView======: second   地址:@34476387
D/SecondFragment--->onActivityCreated======: second


    從日誌中我們可以看到, 當沒有新增到回退棧時, remove 一個 fragment 會呼叫到 onDetach 方法, 如果我們把第二個按鈕的 add 改為 replace .

     日誌如下 :

-------- 點選第一個按鈕 -------

D/FragmentFirst--->onAttach======: first
D/FirstFragment--->onCreate======: first
D/FirstFragment--->onCreateView======: first   地址:@118390022
D/FirstFragment--->onActivityCreated======: first

-------- 點選第二個按鈕 --------

D/SecondFragment--->onAttach======: second
D/SecondFragment--->onCreate======: second

D/FirstFragment--->onPause======: first
D/FirstFragment--->onStop======: first
D/FirstFragment--->onDestroyView======: first
D/FirstFragment--->onDestroy======: first
D/FirstFragment--->onDetach======: first

D/SecondFragment--->onCreateView======: second   地址:@34476387
D/SecondFragment--->onActivityCreated======: second

    我們可以看到, replace 會先把第一個 fragment 移除, 呼叫到 onDetach 方法, 然後再新增第二個, 所以說 replace 是 remove 和 add 的集合.

開啟回退棧:

程式碼:

private void initialize() {

        FirstFragment firstFragment = FirstFragment.newInstance("1", "2");
        SecondFragment secondFragment = SecondFragment.newInstance("3", "4");

        firstFragmentBtn.setOnClickListener((View v) -> {
            FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
            fragmentTransaction.add(R.id.frame_layout, firstFragment);
            fragmentTransaction.addToBackStack(null);
            fragmentTransaction.commit();
        });

        secondFragmentBtn.setOnClickListener(v -> {
            FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
            fragmentTransaction.replace(R.id.frame_layout, secondFragment); // 注意此處為 replace, 如果為 add 則依然是疊加效果, 看不到回退棧的意義
            fragmentTransaction.addToBackStack(null);
            fragmentTransaction.commit();
        });

        backBtn.setOnClickListener(v -> {
            getSupportFragmentManager().popBackStack();
//            getSupportFragmentManager().beginTransaction().remove(secondFragment).commit();
        });

    }

    日誌:

-------- 點選第一個按鈕 --------

D/FragmentFirst--->onAttach======: first
D/FirstFragment--->onCreate======: first
D/FirstFragment--->onCreateView======: first   地址:@118390022
D/FirstFragment--->onActivityCreated======: first

-------- 點選第二個按鈕 --------

D/SecondFragment--->onAttach======: second
D/SecondFragment--->onCreate======: second

D/FirstFragment--->onPause======: first
D/FirstFragment--->onStop======: first
D/FirstFragment--->onDestroyView======: first

D/SecondFragment--->onCreateView======: second   地址:@34476387
D/SecondFragment--->onActivityCreated======: second

-------- 回退一次 --------

D/SecondFragment--->onPause======: second
D/SecondFragment--->onStop======: second
D/SecondFragment--->onDestroyView======: second
D/SecondFragment--->onDestroy======: second
D/SecondFragment--->onDetach======: second

D/FirstFragment--->onCreateView======: first   地址:@118390022
D/FirstFragment--->onActivityCreated======: first

-------- 回退第二次 --------

D/FirstFragment--->onPause======: first
D/FirstFragment--->onStop======: first
D/FirstFragment--->onDestroyView======: first
D/FirstFragment--->onDestroy======: first
D/FirstFragment--->onDetach======: first

    我們可以看到, 如果添加了回退棧, 當我們使用 replace 方法時, 之前的 fragment 不會呼叫到 onDetach 方法, 只銷毀了其 view, 然後我們進行一次回退操作, 可以看到 fragment 2 正常被銷燬, 呼叫到了 onDetach 方法, 此時 fragment 1 會重新出顯示, 如果沒有采用回退棧, 用 replace 方法替換之後, 之前的 fragment 1 是不會顯示的, 因為其已經和 activity 接觸了關聯. 然後我們進行第二次回退操作, 同樣的是 fragment 1 被銷燬.

    在這裡有個很歧義的地方, 筆者通過多次打日誌檢視地址發現, 當一個 fragment 呼叫到 onDetach 方法之後, 其實列物件並沒有銷燬重建, 其地址還是一樣的, 我們依然可以把這個例項物件重新新增到容器中和 activity 建立關聯, 所以不太理解為什麼那麼多部落格都說到呼叫到 onDetach 之後, fragment 會銷燬, 例項會重新建立.

     但是筆者認為,其實並不是這樣, 銷燬一個 fragment ,只是和 activity 解除了關聯, 比呼叫到 onDestoryView 多呼叫了幾個方法罷了, 其例項物件並沒有得到重新建立.

    通過上面的方法我們發現無論如何都會銷燬其檢視狀態重新建立檢視, 這樣做固然有好處,可以節省很多檢視佔用的記憶體, 但是存在一個很致命的地方, 那就是使用者的檢視狀態得不到儲存, 比如 recyclerView 滑動的位置. 因此如果我們想要儲存其檢視狀態, 可以使用 hide 方法, 並且在 onHiddenChange 方法中監聽當前 fragment 是否隱藏.