1. 程式人生 > >Fragment操作方法和生命週期的關係

Fragment操作方法和生命週期的關係

相信對Android開發比較熟悉的同學或多或少都用到過Fragment,Fragment附著在Activity上,有自己獨特的生命

週期。FragmentTransaction提供了很多操作Fragment的方法,如add()、replace()、attach()等,呼叫這些方法會觸發

Fragment不同的生命週期。呼叫了這些方法卻不知道Fragment當於什麼狀態是一件危險的事情,因此,有必要

對Fragment的操作方法和生命週期的對應關係理一理。

通過FragmentTransaction操作Fragment主要有以下幾種方式:

add()

新增一個Fragment到Activity中

remove()

從Activity中移除一個Fragment,如果被移除的Fragment沒有被新增到回退棧,這個Fragment例項將會被銷燬。

replace()

使用另一個Fragment替換當前的,實際上是先呼叫remove()再呼叫add()

hide()

隱藏當前的Fragment,設定為不可見,但是並不會銷燬

show()

顯示之前隱藏的Fragment,設定為可見

detach()

將Fragment從Activity中分離,會銷燬其View,但不會銷燬Fragment的例項

attach()

將從Activity中分離的Fragment,重新關聯到Activity,重新建立View

       總體看來,Fragment的操作方式主要可以分為兩類:

顯示:add() 、replace() 、show() 、attach()

隱藏:remove() 、hide() 、detach()

       下面通過例子來詳細分析這幾種方法的不同。

1、add方法

public class MainActivity extends FragmentActivity {

    FragmentA fragmentA = new FragmentA();
    FragmentB fragmentB = new FragmentB();
    private Button btn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
        fragmentTransaction.add(R.id.main_framelayout, fragmentA);
        fragmentTransaction.commit();

        btn = (Button) findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
                fragmentTransaction.add(R.id.main_framelayout, fragmentB);
                fragmentTransaction.commit();
            }
        });
    }
}
我們還定義了兩個Fragment,就是在各個生命週期中列印日誌,觀察生命週期的呼叫情況,程式碼就不在這裡列出了。執行結果如下:


兩個 Fragment依次呼叫從onAttach到onResume的生命週期方法,FragmentB的新增並沒有影響到FragmentA。這時雖然添加了兩個Fragment,但是我們只能看到FragmentB,FragmentA被覆蓋了。按返回鍵返回桌面,看列印日誌。

2、remove方法

只對上面的程式碼做一行修改,將

fragmentTransaction.add(R.id.main_framelayout, fragmentB);

改為

fragmentTransaction.remove(fragmentA);

即把FragmentA add到Activity之後,又將其remove,看一下發生了什麼。


可以看出通過remove方法,fragment的View被從頁面上移除,同時Fragment在記憶體中的物件也被銷燬了。這與我們之前給出的remove()方法的說明是一致的。

3、replace方法

還是隻改那一行程式碼,將

fragmentTransaction.add(R.id.main_framelayout, fragmentB);

改為

fragmentTransaction.replace(R.id.main_framelayout, fragmentB); 執行結果怎樣呢?


像我們前面說的,replace方法相當於先執行了remove再執行add。FragmentA先被從頁面上移除並銷燬了,然後FragmentB被建立並顯示在了頁面上。

4、detach和attach方法

這一次我們要驗證兩個方法,先把Fragment從頁面上detach,然後再將其attach回去,所以將之前的那一行程式碼改成了兩行,

fragmentTransaction.detach(fragmentA);
fragmentTransaction.attach(fragmentA);

執行結果如下:


日誌中顯示detach的過程只執行了onPause()、onStop()和onDestroyView()三個方法,並沒有執行onDestroy()和onDetach()方法,也就是說Fragment物件並沒有銷燬,只是從Activity中將View移除。而attach執行相反的過程。

5、show和hide方法

這兩個方法並不會觸發任何Fragment的生命週期,只是將該Fragment設定為是否可見。

瞭解了上面這些內容,下面通過一個例子來加深一下理解。

很多APP的首頁都是支援左右滑動的,它的實現就是通過ViewPager加一個介面卡,對應於使用Fragment的情況,常用的有FragmentPagerAdapter和FragmentStatePagerAdapter兩種。

它們有什麼區別呢?先來看看原始碼。首先是FragmentPagerAdapter

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        final long itemId = getItemId(position);

        // Do we already have this fragment?
        String name = makeFragmentName(container.getId(), itemId);
        Fragment fragment = mFragmentManager.findFragmentByTag(name);
        if (fragment != null) {
            if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
            mCurTransaction.attach(fragment);
        } else {
            fragment = getItem(position);
            if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
            mCurTransaction.add(container.getId(), fragment,
                    makeFragmentName(container.getId(), itemId));
        }
        if (fragment != mCurrentPrimaryItem) {
            fragment.setMenuVisibility(false);
            fragment.setUserVisibleHint(false);
        }

        return fragment;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
                + " v=" + ((Fragment)object).getView());
        mCurTransaction.detach((Fragment)object);
    }
從上面程式碼發現,FragmentPagerAdapter在呼叫instantiateItem時,如果Fragment物件還不存在,就建立一個add到Activity上,如果已經存在就直接呼叫attach()方法,把Fragment物件attach到Activity上。FragmentPagerAdapter 會將所有生成的 Fragment 物件通過 FragmentManager 儲存起來備用,以後需要該 Fragment 時,都會從 FragmentManager 讀取,而不會再次呼叫 getItem() 方法。在destroyItem方法中執行的是detach()方法,那麼這個物件並不會銷燬,還可以通過Tag找到它。
    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        // If we already have this item instantiated, there is nothing
        // to do.  This can happen when we are restoring the entire pager
        // from its saved state, where the fragment manager has already
        // taken care of restoring the fragments we previously had instantiated.
        if (mFragments.size() > position) {
            Fragment f = mFragments.get(position);
            if (f != null) {
                return f;
            }
        }

        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        Fragment fragment = getItem(position);
        if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
        if (mSavedState.size() > position) {
            Fragment.SavedState fss = mSavedState.get(position);
            if (fss != null) {
                fragment.setInitialSavedState(fss);
            }
        }
        while (mFragments.size() <= position) {
            mFragments.add(null);
        }
        fragment.setMenuVisibility(false);
        fragment.setUserVisibleHint(false);
        mFragments.set(position, fragment);
        mCurTransaction.add(container.getId(), fragment);

        return fragment;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        Fragment fragment = (Fragment)object;

        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
                + " v=" + ((Fragment)object).getView());
        while (mSavedState.size() <= position) {
            mSavedState.add(null);
        }
        mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment));
        mFragments.set(position, null);

        mCurTransaction.remove(fragment);
    }

與FragmentPagerAdapter不同的是,FragmentStatePagerAdapter是通過add和remove方法新增Fragment的,Fragment的例項會被反覆的銷燬和重建。

FragmentPagerAdapter更多的用於相對靜態的、少量介面的ViewPager,劃過的fragment會儲存在記憶體中,如果載入的fragment較多會佔用大量的記憶體。而FragmentStatePagerAdapter適用於資料動態性較大、頁面比較多的情況,它並不會儲存所有的fragment,預設情況下只會儲存當前fragment以及上一個和下一個,其他會被銷燬掉。

       這裡提到的預設儲存多少個頁面,可以通過ViewPager的 setOffscreenPageLimit (int limit) 來設定,limit引數表示保持當前頁面前面的limit個頁面和後面limit個頁面。如果這個值設定的很大,頁面不會被反覆銷燬重建,效能比較好,但是會佔用很多的記憶體。如果設定的很小則剛好相反。