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方法
我們還定義了兩個Fragment,就是在各個生命週期中列印日誌,觀察生命週期的呼叫情況,程式碼就不在這裡列出了。執行結果如下: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依次呼叫從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個頁面。如果這個值設定的很大,頁面不會被反覆銷燬重建,效能比較好,但是會佔用很多的記憶體。如果設定的很小則剛好相反。