Fragment API(镜像地址):

http://androiddoc.qiniudn.com/reference/android/support/v4/app/Fragment.html


Fragment/Activity生命周期图

图片来源: https://github.com/xxv/android-lifecycle
【Fragment】 Android Fragment生命周期详解(图文)_第1张图片

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() 方法来限制界面刷新的调用:

    @Override    public void onResume() {        super.onResume();        if (isVisible()){            //界面刷新操作,如网络请求        }    }    @Override    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;@Overridepublic void onAttachFragment(Fragment fragment) {// TODO Auto-generated method stubsuper.onAttachFragment(fragment);Log.d(TAG,"onAttachFragment");}@Overrideprotected void onDestroy() {// TODO Auto-generated method stubsuper.onDestroy();Log.d(TAG,"onDestroy");}@Overrideprotected void onPause() {// TODO Auto-generated method stubsuper.onPause();Log.d(TAG,"onPause");}@Overrideprotected void onResume() {// TODO Auto-generated method stubsuper.onResume();Log.d(TAG,"onResume");}@Overrideprotected void onStart() {// TODO Auto-generated method stubsuper.onStart();Log.d(TAG,"onStart");}@Overrideprotected void onStop() {// TODO Auto-generated method stubsuper.onStop();Log.d(TAG,"onStop");}@Overrideprotected void onCreate(Bundle savedInstanceState) {// TODO Auto-generated method stubsuper.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() {@Overridepublic void onCheckedChanged(RadioGroup group, int checkedId) {// TODO Auto-generated method stubfor (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,因此不会有重叠现象

@Overridepublic void onAttachFragment(Fragment fragment) {// TODO Auto-generated method stubsuper.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;@Overridepublic void onCreate(Bundle savedInstanceState) {// TODO Auto-generated method stubfManager = 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源码:

 @Override  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 onSaveInstanceStateat 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()。

参考文章:FragmentTransaction的commit和commitAllowingStateLoss的区别


参考地址:

Android Fragment 你应该知道的一切

Android解惑 - 为什么要用Fragment.setArguments(Bundle bundle)来传递参数

Android Fragment 真正的完全解析(上)

Android Fragment 真正的完全解析(下)





更多相关文章

  1. Android获取通话状态
  2. LayoutInflater.inflate()方法的介绍
  3. android 笔记 --- 自定义Android主题风格theme.xml方法
  4. Android USB状态监控(解决scheme="file")
  5. android工程下运行main方法的配置方法
  6. Android Studio无法新建创建android工程解决方法
  7. Android Studio设置主题 和 不支持中文的问题解决方法

随机推荐

  1. RenderScript 让你的Android计算速度快的
  2. Android NDK 入门
  3. [入门五]Android的Camera架构介绍
  4. Android TV 焦点控制
  5. Android编译系统分析
  6. Visual Studio 跨平台开发实战(4) - Xama
  7. Android系统启动分析
  8. Android中LCD背光驱动
  9. android:组件化方案
  10. Android 性能分析案例