Android 基础之 Fragment 面面观

一、生命周期

Activity/Fragment 生命周期

上图是 Fragment 和 Activity 比较完整的生命周期函数回调,相对于 Activity,Fragment 也有自己独立的生命周期。
Activity 的七个生命周期回调函数除了 onRestart,Fragment 都有,意义类似于 Activity,此外还多了五个回调函数:

  1. onAttach(Activity):Fragment 和 Activity 发生关联时调用。当 Fragment 从 layout 文件中加载出来或者在 Activity 中调用 FragmentManager#add 方法之后第一个调用的回调函数;
  2. onCreate:
  3. onCreateView(LayoutInflater,ViewGroup,Bundle):创建 Fragment 的 View;
  4. onActivityCreated(Bundle):Activity#onCreate 方法返回时调用;
  5. onStart:
  6. onResume:
  7. onPause:
  8. onStop:
  9. onDestroyView():Fragment 被移除时销毁 View;
  10. onDestroy:
  11. onDetach():Fragment 和 Activity 解除关联;

一些场景的生命周期:

  • add:onAttach --> onCreate --> onCreateView --> onActivityCreated --> onStart --> onResume
  • remove:onPause --> onStop --> onDestroyView --> onDestroy --> onDetach
  • replace:实质上是调用 remove 方法和 add 方法
  • hide:不执行任何生命周期函数
  • show:不执行任何生命周期函数
  • remove 或 replace 后 addToBackStack:不执行 onDestroy 和 onDetach 方法
  • remove 或 replace 后从 BackStack 返回:不执行 onAttach 和 onCreate 方法
  • 锁屏、弹出 DIalog、Home 键回主屏等类似于 Activity

二、使用

静态使用

在布局文件中作为一个普通控件添加

动态使用

通过 FragmentManager 操作 FragmentTransition 执行 add、remove、replace、hide、show 等一系列操作

replace 和 hide 的区别:
hide 和 show 只是显示和隐藏,不会回调生命周期函数,可以通过
onHiddenChanged 函数执行一些操作,而 replace 方法不会保存 Fragment 的状态。

addToBackStack:添加事务到回退栈,按返回键时会返回当前事务
当 Fragment 嵌套时需要使用 getChildFragmentManager 来获取 FragmentManager

三、状态保存恢复

当按 Home 键回主屏、锁屏等操作使 Fragment 回调了 onPause 方法,而非主动调用 replace、remove 等方法,Fragment 的 onSaveInstanceState 方法会执行对 View 进行状态保存,当系统资源不足杀死 Fragment 后,在 onActivityCreated 方法中进行状态恢复,如果 View 都实现了保存和恢复状态的方法,那么只需要对成员变量的状态进行恢复和保存。
如果 remove 或 replace 之后,事务加入了回退栈,会调用 onDestroyView 销毁 Fragment 的 View,在从回退栈返回时,Fragment 内部会调用 View 的状态保存和恢复方法进行处理,而 Fragment 的 onDestroy 方法没有被回调,Fragment 的实例还存在,Fragment 的成员变量也存在,这种情况下,不需要作任何操作来保存和恢复状态。

四、getActivity()

1、getActivity() 为 null

这种情况多数是因为 Fragment 已经和 Activity 解除了关联,宿主 Activity 已销毁。
解决方法:在异步回调是进行判空(fragment.isAdd(),getActivity()!=null 等),或者在 Fragment 实例创建时就通过
getActivity().getApplicationContext() 方法保存整个应用的上下文对象

2、内存泄漏。如果 Fragment 持有宿主 Activity 的引用,会导致宿主 Activity 无法回收,造成内存泄漏。

解决方法:在 onAttach(Context) 方法中获取 Context 对象

五、Fragment 重叠

当 Activity 销毁重建时,会恢复其关联的 Fragment,而 Fragment 实例是自身也会销毁重建,这个时候就会造成 Fragment 重叠。
解决方案:在 Activity 创建 Fragment 实例时进行判断:

  1. 在 Activity#onAttachFragment 时判断:
@Override
public void onAttachFragment(Fragment fragment) {
    super.onAttachFragment(fragment);
    if (fragment instanceof  OneFragment){
        oneFragment = (OneFragment) fragment;
    }
}
  1. 在创建 Fragment 前添加判断,判断是否已经存在:
Fragment tempFragment = getSupportFragmentManager().findFragmentByTag("OneFragment");
if (tempFragment==null) {
    oneFragment = OneFragment.newInstance();
    ft.add(R.id.fl_content, oneFragment, "OneFragment");
}else {
    oneFragment = (OneFragment) tempFragment;
}
  1. 直接利用 savedInstanceState 判断即可:
if (savedInstanceState==null) {
    oneFragment = OneFragment.newInstance();
    ft.add(R.id.fl_content, oneFragment, "OneFragment");
}else {
    oneFragment = (OneFragment) getSupportFragmentManager().findFragmentByTag("OneFragment");
}

六、懒加载

典型代码:

public abstract class LazyFragment extends Fragment {

    private boolean isVisible;
    private boolean isViewCreated;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(getLayoutID(), container, false);
        Log.e("blink","onCreateView");
        isViewCreated = true;
        return view;
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if(getUserVisibleHint()) {
            isVisible = true;
            loadForData();
        } else {
            isVisible = false;
            onInvisible();
        }
        if (isViewCreated && isVisible)
            loadForUI();
    }

    protected abstract void onInvisible();

    protected abstract void loadForData();

    protected abstract void loadForUI();

    // provide layout id
    public abstract int getLayoutID();

}

需要显式调用 setUserVisibleHint(true)

七、通信

Fragment 之间通信

  1. 使用 setArguments,getArguments,参数是一个 Bundle 对象;
  2. 回调函数;

Activity 和 Fragment 通信

  1. getActivity 获取 Activity 的引用;
  2. Activity 持有 Fargment 实例的引用;
  3. 回调函数;

推荐阅读更多精彩内容