Android 基础之 Fragment 面面观
一、生命周期
Activity/Fragment 生命周期上图是 Fragment 和 Activity 比较完整的生命周期函数回调,相对于 Activity,Fragment 也有自己独立的生命周期。
Activity 的七个生命周期回调函数除了 onRestart,Fragment 都有,意义类似于 Activity,此外还多了五个回调函数:
- onAttach(Activity):Fragment 和 Activity 发生关联时调用。当 Fragment 从 layout 文件中加载出来或者在 Activity 中调用 FragmentManager#add 方法之后第一个调用的回调函数;
- onCreate:
- onCreateView(LayoutInflater,ViewGroup,Bundle):创建 Fragment 的 View;
- onActivityCreated(Bundle):Activity#onCreate 方法返回时调用;
- onStart:
- onResume:
- onPause:
- onStop:
- onDestroyView():Fragment 被移除时销毁 View;
- onDestroy:
- 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 实例时进行判断:
- 在 Activity#onAttachFragment 时判断:
@Override
public void onAttachFragment(Fragment fragment) {
super.onAttachFragment(fragment);
if (fragment instanceof OneFragment){
oneFragment = (OneFragment) fragment;
}
}
- 在创建 Fragment 前添加判断,判断是否已经存在:
Fragment tempFragment = getSupportFragmentManager().findFragmentByTag("OneFragment");
if (tempFragment==null) {
oneFragment = OneFragment.newInstance();
ft.add(R.id.fl_content, oneFragment, "OneFragment");
}else {
oneFragment = (OneFragment) tempFragment;
}
- 直接利用 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 之间通信
- 使用 setArguments,getArguments,参数是一个 Bundle 对象;
- 回调函数;
Activity 和 Fragment 通信
- getActivity 获取 Activity 的引用;
- Activity 持有 Fargment 实例的引用;
- 回调函数;