Fragment/Activity生命周期图
![](https://i-blog.csdnimg.cn/blog_migrate/c4d84611372ecd11d4297eb5f291a7d8.png)
Fragment状态变化常用方法
1. onHiddenChanged(boolean hidden)
Activity内部通过show和hide方法切换Fragment时,引发的状态变迁。
2. setUserVisibleHint(boolean isVisibleToUser)
ViewPager和Fragment一起使用时,ViewPager就是利用这个方法控制Fragment的显示与隐藏的,所以通过这个方法可以实现ViewPager中Fragment的懒加载模式。
3. isVisible()
如果该Fragment对象对用户可见,那么就返回true。这就意味着:1.已经被添加到Activity中;2.它的View对象已经被绑定到窗口中;3.没有被隐藏。
isVisible()方法需要注意和getUserVisibleHint()方法的区别。
isVisible() 方法可用于fragment的 onResume() 生命周期方法中,用于判断当app从其他界面进入(初次创建或者返回)fragment所attach的activity时,当前Fragment是否可见。配合使用 onHiddenChanged方法,常用于Fragment界面的刷新。
特别说明:存在这样一种情况,当从其他 Activity 通过setResult方法返回到 包含多个Fragment的Activity,并且在目标Activity的onActivityResult方法中指定显示处于隐藏状态的一个Fragment时,如果仅仅依据isVisible方法是无法区分所要显示的Fragment的onHiddenChanged和onResume方法,因为onActivityResult方法要优先于onResume方法,所以此时需要用到 isResumed() 方法来限制界面刷新的调用:
public void onResume() { super.onResume(); if (isVisible()){ //界面刷新操作,如网络请求 } } public void onHiddenChanged(boolean hidden) { super.onHiddenChanged(hidden); if (isVisible() && isResumed()){ //界面刷新操作,如网络请求 } }
4. setArguments(Bundle args)
Fragment使用常见问题
1. Fragment切换导致UI重叠问题
问题演示:
![]()
问题模拟重现:
1.首先打开,默认选中的是第一个tab,如上面的一张图片正常那样。
2.切换到tab2,并把tab1 hide掉;
3.再切回到tab1,并不会触发tab1对应fragment的任何生命周期;
4.然后home键进入后台,我在activity的onPause()中手动对IndexFragment赋空,模拟长时间后台,系统销毁了该引用:IndexFragment=null;
5.再次启动,其实tab1 的fragment实例在内存中还在,只是他的引用被销毁了。
6.再切到tab2,这里其实是先把tab1的hide,在show tab2,但是tab1 的fragment引用为空,所以无法hide,就出现了tab2叠在tab1上的花屏情况。
7.再切到tab1,tab1就会重复创建对象。问题分析:
fragment的切换有两种方式:
1. replace方式
transaction.replace(R.id.content, IndexFragment);
使用replace方式不会出现上述问题,但是会重复创建对象,每次replace会把生命周期全部执行一遍,如果在这些生命周期函数 里拉取数据的话,就会不断重复的加载刷新数据。
2. add - hide - show方式
transaction.add(R.id.content, IndexFragment); transaction.hide(otherfragment); transaction.show(thisfragment);
public class TestFragmentTab extends FragmentActivity { private static final String TAG = "TestFragmentTab"; private int tabIds[] = new int[]{ R.id.home_tab_main, R.id.home_tab_search, R.id.home_tab_category, }; private FragmentTab1 mTab1; private FragmentTab2 mTab2; private FragmentTab3 mTab3; public void onAttachFragment(Fragment fragment) { // TODO Auto-generated method stub super.onAttachFragment(fragment); Log.d(TAG, "onAttachFragment"); } protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); Log.d(TAG, "onDestroy"); } protected void onPause() { // TODO Auto-generated method stub super.onPause(); Log.d(TAG, "onPause"); } protected void onResume() { // TODO Auto-generated method stub super.onResume(); Log.d(TAG, "onResume"); } protected void onStart() { // TODO Auto-generated method stub super.onStart(); Log.d(TAG, "onStart"); } protected void onStop() { // TODO Auto-generated method stub super.onStop(); Log.d(TAG, "onStop"); } protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); Log.d(TAG, "onCreate"); setContentView(R.layout.layout_test_fragment_tab); RadioGroup tabButtonGroup = (RadioGroup) findViewById(R.id.bottom_bar); tabButtonGroup.setOnCheckedChangeListener( new OnCheckedChangeListener() { public void onCheckedChanged(RadioGroup group, int checkedId) { // TODO Auto-generated method stub for ( int i = 0; i < tabIds.length; i++) { if (tabIds[i] == checkedId) { setSelection(i); break; } } } }); setSelection( 0); } private void setSelection(int position){ FragmentManager fm = getSupportFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); hideAllFragments(ft); switch (position) { case 0: if (mTab1 == null) { mTab1 = new FragmentTab1(); ft.add(R.id.content, mTab1); } else { ft.show(mTab1); } break; case 1: if (mTab2 == null) { mTab2 = new FragmentTab2(); ft.add(R.id.content, mTab2); } else { ft.show(mTab2); } break; case 2: if (mTab3 == null) { mTab3 = new FragmentTab3(); ft.add(R.id.content, mTab3); } else { ft.show(mTab3); } break; default: break; } ft.commit(); } private void hideAllFragments(FragmentTransaction ft){ if (mTab1 != null) { ft.hide(mTab1); } if (mTab2 != null) { ft.hide(mTab2); } if (mTab3 != null) { ft.hide(mTab3); } } }
使用第二种方式做Fragment的切换时,经常会出现上图所示Fragment重叠问题。直接back键退出应用再进入时,则没有出现该问题。当应用被强行关闭后(通过手机管家软件手动强关,或系统为节省内存自动关闭应用),再次进入应用时,每次都会出现这种花屏现象。
通过分析发现,正常back键退出应用时,Activity及所属的Fragment对象均会被销毁,因此再次进入时会在切换到Tab时创建对应的Fragment对象。
但是当强行关闭应用后,Activity虽然被回收,但Fragment对象仍然保持,再次进入应用时,系统会分别调用Fragment的onAttach方法将其附加到Activity上,后面会分别调用两个fragment的onCreateView方法,因此这两个Fragment对应的View层次结构都会加到Activity的View层次中。
虽然切换Fragment时会把所有fragment先隐藏再显示选中的对象,但由于此时Activity中Fragment对象的成员变量还未初始化,因此会再次实例化fragment对象,之后add、show及hide的都是在第二次创建的对象上操作的,而之前被保持的fragment对象的视图层次已经反映到Activity视图中并且不会被hide,因此发生了上述重叠现象。解决办法有三种:
第一种:
在Activity的onAttachFragment方法中,有一个fragment参数,它就是onAttach方法对应的Fragment对象,
通过判断这个fragment对象,如果属于我们的FragmentTabX类并且该类还未被实例化过,则将Activity的成员变量mFragmentTabX指向该fragment对象,这样就可以在原来的fragment对象上操作add/show/hide,因此不会有重叠现象
public void onAttachFragment(Fragment fragment) { // TODO Auto-generated method stub super.onAttachFragment(fragment); Log.d(TAG, "onAttachFragment"); if (mTab1 == null && fragment instanceof FragmentTab1) { mTab1 = (FragmentTab1)fragment; } else if (mTab2 == null && fragment instanceof FragmentTab2) { mTab2 = (FragmentTab2)fragment; } else if (mTab3 == null && fragment instanceof FragmentTab3) { mTab3 = (FragmentTab3)fragment; } }
第二种:调用三个参数的add函数,添加Tag参数用于标记:
transaction.add(R.id.content, IndexFragment,”Tab1″);
然后,在fragment初始化前,添加引用判断,找打对应的引用,如果依旧为空,再调用fragment构造函数进行初始化:
IndexFragment=FragmentManager.findFragmentByTag(“Tab1″);
第三种:
在进入onCreate函数时,先去判断savedInstanceState是否为null,如果不为null,则表示里面有保存这四个fragment。则不再重新去add这四个fragment,而是通过Tag从前保存的数据中直接去读取。
FragmentManager fManager; public void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub fManager = getFragmentManager(); if (savedInstanceState != null) { allFrg = (AllOfficialAccountFragment) fManager.findFragmentByTag( "allFrg"); movieFrg = (MovieOfficialAccountFragment) fManager.findFragmentByTag( "movieFrg"); newsFrg = (NewsOfficialAccountFragment) fManager.findFragmentByTag( "newsFrg"); otherFrg = (OtherOfficialAccountFragment) fManager.findFragmentByTag( "otherFrg"); } super.onCreate(savedInstanceState); }
2.Fragment not attached to Activity异常
出现该问题,主要是因为在Fragment还没有被关联到Activity的时候或者被Attach的Activity已经destroy了的时候,调用了需要上下文Context的函数,常见的如fragment的getResource()方法。解决办法主要有:
一:将调用的代码移动到onStart()方法中;
三:使用被Attach的Activity的相应方法,如getActivity().getResource...等;
二:在调用的代码前添加Fragment的isAdd()方法进行判断,推荐使用这种方式!
3. Fragment中onActivityResult()注意问题
通常,我们会在FragmentActivity中嵌套一层Fragment使用,甚至在Fragment中再次层层嵌套Fragment,此时,会出现第二层及更深层次的子Fragment对象无法接收到onActivityResult()事件。查看FragmentActivity源码:
protected void onActivityResult(int requestCode, int resultCode, Intent data) { mFragments.noteStateNotSaved(); int index = requestCode>> 16; if (index != 0) { index--; if (mFragments.mActive == null || index < 0 || index >= mFragments.mActive.size()) { Log.w(TAG, "Activity result fragment index out of range: 0x" + Integer.toHexString(requestCode)); return; } Fragment frag = mFragments.mActive.get(index); if (frag == null) { Log.w(TAG, "Activity result no fragment exists for index: 0x" + Integer.toHexString(requestCode)); } else { frag.onActivityResult(requestCode& 0xffff, resultCode, data); } return; } super.onActivityResult(requestCode, resultCode, data); }
可以看出,FragmentActivity没有处理嵌套Fragment的情况,也就是说,只是回调到第一级的Fragment中,然后没有继续分发下去。所以,我们需要在第一级的Fragment的onActivityResult()中控制分发onActivityResult事件。
4. FragmentTransaction的commit问题
FragmentTransaction有commit()和commitAllowingStateLoss()两个提交方法,通过源码看他们的区别:commit():
Schedules a commit of this transaction. The commit does not happen immediately; it will be scheduled as work on the main thread to be done the next time that thread is ready. A transaction can only be committed with this method prior to its containing activity saving its state. If the commit is attempted after that point, an exception will be thrown. This is because the state after the commit can be lost if the activity needs to be restored from its state. See commitAllowingStateLoss() for situations where it may be okay to lose the commit. Returns: Returns the identifier of this transaction's back stack entry, if addToBackStack(String) had been called. Otherwise, returns a negative number.
commitAllowingStateLoss():Like commit but allows the commit to be executed after an activity's state is saved. This is dangerous because the commit can be lost if the activity needs to later be restored from its state, so this should only be used for cases where it is okay for the UI state to change unexpectedly on the user.
顾名思义,可以看出,使用commit()方法,如果在Activity保存状态后发生了commit行为,将抛出异常:
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState at android.support.v4.app.FragmentManagerImpl.checkStateLoss(Unknown Source) at android.support.v4.app.FragmentManagerImpl.enqueueAction(Unknown Source) at android.support.v4.app.BackStackRecord.commitInternal(Unknown Source) at android.support.v4.app.BackStackRecord.commit(Unknown Source)
而commitAllowingStateLoss()方法允许这种调用,但是当Activity回复状态时,之前fragment的提交状态将丢失。所以,如果你允许这种丢失commit状态的话,可以使用commitAllowingStateLoss()。