FragmentTransaction管理的Fragment生命週期狀態
阿新 • • 發佈:2019-01-03
在我們Android中,對Fragment的操作都是通過FragmentTransaction來執行的。而如果從Fragment的結果來看,FragmentTransaction中對Fragment的操作大致可以分為兩類:
1、顯示操作:add()、 replace()、 show()、 attach()
2、隱藏操作:remove() 、hide() 、detach()
對於每一組方法,雖然最後產生的效果有點類似,但方法背後帶來的副作用以及對Fragment的生命週期的影響都不盡相同。
add() VS replace()
只有在Fragment數量大於等於2的時候,呼叫add()還是replace()的區別才能體現出來。當通過add()連續兩次新增Fragment的時候,每個Fragment生命週期中的onAttach()-onResume()都會被各呼叫一次,而且兩個Fragment的View會被同時attach到containerView中。E/Fragment***0: onAttach E/Fragment***0: onCreate E/Fragment***0: onCreateView E/Fragment***0: onViewCreated E/Fragment***0: onActivityCreated E/Fragment***0: onViewStateRestored E/Fragment***0: onStart E/Fragment***0: onResume E/Fragment***1: onAttach E/Fragment***1: onCreate E/Fragment***1: onCreateView E/Fragment***1: onViewCreated E/Fragment***1: onActivityCreated E/Fragment***1: onViewStateRestored E/Fragment***1: onStart E/Fragment***1: onResume
同樣,退出Activty時,每個Fragment生命週期中的onPause() - onDetach()也會被各呼叫一次。
E/Fragment***0: onPause E/Fragment***1: onPause E/Fragment***0: onStop E/Fragment***1: onStop E/Fragment***0: onDestroyView E/Fragment***0: onDestroy E/Fragment***0: onDetach E/Fragment***1: onDestroyView E/Fragment***1: onDestroy E/Fragment***1: onDetach
但是當使用replace()來新增Fragment的時候,第二次新增的Fragment會導致第一個Fragment被銷燬(在不使用回退棧的情況下),即執行第二個Fragment的onAttach()方法之前會先執行第一個Fragment的onPause()-onDetach()方法,與此同時containerView會detach掉第一個Fragment的View。
E/Fragment***0: onAttach E/Fragment***0: onCreate E/Fragment***0: onCreateView E/Fragment***0: onViewCreated E/Fragment***0: onActivityCreated E/Fragment***0: onViewStateRestored E/Fragment***0: onStart E/Fragment***0: onResume E/Fragment***0: onPause E/Fragment***0: onStop E/Fragment***0: onDestroyView E/Fragment***0: onDestroy E/Fragment***0: onDetach E/Fragment***1: onAttach E/Fragment***1: onCreate E/Fragment***1: onCreateView E/Fragment***1: onViewCreated E/Fragment***1: onActivityCreated E/Fragment***1: onViewStateRestored E/Fragment***1: onStart E/Fragment***1: onResume
show() & hide() VS attach() & detach()
呼叫show() & hide()方法時,Fragment的正常生命週期方法並不會被執行,僅僅是Fragment的View被顯示或者隱藏,並視情況呼叫onHiddenChanged()。而且,儘管Fragment的View被隱藏,但它在父佈局中並未被detach,仍然是作為containerView的childView存在著。
// 注意如果Fragment本來就是顯示的狀態,呼叫 show()不會產生任何影響,也不會回撥onHiddenChanged()
E/Fragment***0: onHiddenChanged hidden = false
// 隱藏狀態下呼叫hide()也不會重複觸發onHiddenChanged()
E/Fragment***1: onHiddenChanged hidden = true
相比較之下,attach() & detach()做的就更徹底一些。一旦一個Fragment被detach(),它的onPause()-onDestroyView()週期都會被執行,同時Fragment的View也會被detach,但是不會執行onDestroy()和onDetach(),也就是說Fragment的例項還是在記憶體中的。E/Fragment***0: onPause
E/Fragment***0: onStop
E/Fragment***0: onDestroyView
在重新呼叫attach()後,onCreateView()-onResume()週期也會被再次執行。
E/Fragment***0: onCreateView
E/Fragment***0: onViewCreated
E/Fragment***0: onActivityCreated
E/Fragment***0: onViewStateRestored
E/Fragment***0: onStart
E/Fragment***0: onResume
remove()方法
相對應add()方法執行onAttach()-onResume()的生命週期,remove()就是完成剩下的onPause()-onDetach()週期。而且replace()其實就是remove()+add()。
E/Fragment***0: onPause
E/Fragment***0: onStop
E/Fragment***0: onDestroyView
E/Fragment***0: onDestroy
E/Fragment***0: onDetach
狀態儲存前面的log中有一個onViewStateRestored()的生命週期,是用來恢復儲存的狀態的。它對應的儲存回撥是onSaveInstanceState(),此回撥會在app進入後臺 、熄屏和 其他系統認為需要儲存狀態的情況下呼叫
// 進入後臺或關閉螢幕
E/Fragment***1: onPause
E/Fragment***1: onSaveInstanceState
E/Fragment***1: onStop
PagerAdapter管理下的生命週期
目前一般使用FragmentPagerAdapter和FragmentStatePagerAdapter來配合ViewPager、TabLayout實現Fragment管理。
FragmentPagerAdapter
FragmentPagerAdapter實際上是使用add(),attach()和detach()來管理Fragment的,所以影響的基本生命週期和上文中相關說明是一致的。緩衝範圍內的從onAttach() - onResume(),超出快取範圍onPause() - onDestroyView()。
需要注意的是所有例項化過的Fragment例項都會儲存在記憶體中,所以適合頁面數量不多且固定的app首頁等情況。
檢視原始碼可知,FragmentPagerAdapter會add設定快取數量(預設為前後各1個,首頁和末頁只快取1個)的Fragment例項,超出快取範圍的使用detach清除。
// 省略部分原始碼突出重點
// 例項化item
@Override
public Object instantiateItem(ViewGroup container, int position) {
...
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
...
// 如果不空則attach
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
...
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
}
...
return fragment;
}
// 清除快取範圍外item
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
...
mCurTransaction.detach((Fragment)object);
}
FragmentStatePagerAdapter
由原始碼可知,FragmentStatePagerAdapter使用add()和remove()管理Fragment,所以快取外的Fragment的例項不會儲存在記憶體中,適合分頁多,資料動態的情況。
// 省略部分原始碼突出重點
// 例項化item
@Override
public Object instantiateItem(ViewGroup container, int position) {
...
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
...
// 直接add
mCurTransaction.add(container.getId(), fragment);
return fragment;
}
// 清除快取範圍外item
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment) object;
...
// 使用remove直接銷燬Fragment
mCurTransaction.remove(fragment);
}
顯示和隱藏
對幹快取數量外的Fragment會被detach或remove,我們可以根據其常規生命週期進行開發,但是快取數量內的顯隱並不會影響生命週期,那我們怎麼知道某個Fragment是否顯示了呢?
當然,在Activity中可以通過Tab的position來判斷,那Fragment內部呢?那麼我們來看下日誌情況。
E/Fragment***0: setUserVisibleHint isVisibleToUser = false
E/Fragment***1: setUserVisibleHint isVisibleToUser = false
E/Fragment***0: onAttach
E/Fragment***0: onCreate
E/Fragment***0: setUserVisibleHint isVisibleToUser = true
E/Fragment***0: onCreateView
E/Fragment***0: onViewCreated
E/Fragment***0: onActivityCreated
E/Fragment***0: onViewStateRestored
E/Fragment***0: onStart
E/Fragment***0: onResume
E/Fragment***1: onAttach
E/Fragment***1: onCreate
E/Fragment***1: onViewCreated
E/Fragment***1: onActivityCreated
E/Fragment***1: onViewStateRestored
E/Fragment***1: onStart
E/Fragment***1: onResume
在Fragment中,還有一個setUserVisibleHint(boolean isVisibleToUser)的回撥,頁面的顯隱就是通過該回調通知Fragment的。isVisibleToUser為true時顯示、為false是隱藏。
從日誌中我們可以看出,快取數量內的Fragment0和Fragment1的isVisibleToUser首先會被設定成false,然後分別進行onAttach() - onResume()的生命週期,其中需要顯示的Fragment在onCreate()之後,會將isVisibleToUser置為true,然後顯示出來。由此可見setUserVisibleHint()有可能在onAttach()之前呼叫,並且到顯示前可能呼叫2,開發時需注意。再之後的顯隱就是設定isVisibleToUser並回調通知了。和上文中的onHiddenChanged()一樣,顯隱狀態沒有變化時,也是不會回撥的。
// 從0切換到1
E/Fragment***0: setUserVisibleHint isVisibleToUser = false
E/Fragment***1: setUserVisibleHint isVisibleToUser = true
// 從1切換到0
E/Fragment***1: setUserVisibleHint isVisibleToUser = false
E/Fragment***0: setUserVisibleHint isVisibleToUser = true
// 從0切換到1
E/Fragment***0: setUserVisibleHint isVisibleToUser = false
E/Fragment***1: setUserVisibleHint isVisibleToUser = true
// 從1切換到0
E/Fragment***1: setUserVisibleHint isVisibleToUser = false
E/Fragment***0: setUserVisibleHint isVisibleToUser = true
// 從1切換到2
E/Fragment***0: setUserVisibleHint isVisibleToUser = false
E/Fragment***2: setUserVisibleHint isVisibleToUser = true