https://www.jianshu.com/p/d9143a92ad94
https://www.jianshu.com/p/fd71d65f0ec6
Fragment生命周期
Fragment#onStart,在所在的Activity的onStart 之前执行
1、定义
Fragment 表示 FragmentActivity 中的行为或界面的一部分
2、Fragment优点
与Activity相比,Fragment内存占用低,响应速度快
3、Fragment生命周期
-
onAttach
onAttach()在 fragment与Activity关联之后调用。
初始化fragment参数可以从getArguments()获得,
onAttach 之后,就无法再调用setArguments() -
onCreate
fragment初次创建时调用。
此时只是创建Fragment,此时的 Activity 还没有创建完成,getActivity()可能为null -
onCreateView
在这个fragment 构造布局时调用。 -
onActivityCreated
Activity.OnCreate()结束后,调用此方法。
此时,Activity已经创建完成,在这里才可以使用Activity的所有资源 -
onStart
当到OnStart()时,Fragment对用户就是可见,但还无法与Fragment交互 -
onResume
当这个fragment 对用户可见、且可以交互。这是Fragment与用户交互之前的最后一个回调。
与Activity.onResume是相互绑定的
依赖于包含它的 activity 的 Activity.onResume -
onPause
与 Activity.onPause() 绑定 -
onStop
与 Activity.onStop() 绑定
已停止的Fragment 可以直接返回到OnStart()回调,然后调用OnResume() -
onDestroyView
如果Fragment 即将被结束或保存,将回调onDestoryView(),将onCreateView创建的 View与这个fragment分离
下次这个fragment若要显示,那么将会创建新视图。
在onStop之后、onDestroy之前调用
这个方法的调用同onCreateView是否返回非null视图无关。
在View状态被保存之后、被父View 回收之前调用 -
onDestroy
当这个fragment不再使用时调用。
fragment 在 onDestroy()阶段,能从Activity中找到,因为它还没有Detach -
onDetach
Fragment 生命周期中最后一个回调是onDetach()
调用它以后,Fragment 与 Activity 解绑,不再拥有视图层次结构,它的所有资源都将被释放
二、Fragment的懒加载
1、原理
利用 fragment 的
setUserVisibleHint(boolean isVisibleToUser) //与ViewPager一起使用时,判断加载时机
onHiddenChanged(boolean hidden) //FragmentTransaction 的 show()、hide() 的方法来控制显示
public abstract class LazyFragment extends Fragment {
public boolean isVisible;//是否可见状态
/**
* 标志位,View已经初始化完成,且Activity已经创建。
* onActivityCreated
*/
public boolean isPrepared;
public boolean isFirstLoad = true;//是否第一次加载
/**
* 要实现延迟加载Fragment内容,需要在 onActivityCreated
* isPrepared = true;
*/
public boolean isReadyLazyLoad() {
if (!isPrepared || !isVisible || !isFirstLoad) {
return false;
}
isFirstLoad = false;
return true;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (!isLazy()) {
initData();
} else {
isPrepared = true;
onVisible();
}
}
/**
* 如果是与ViewPager一起使用,调用的是setUserVisibleHint
* @param isVisibleToUser 是否显示出来了
*/
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (getUserVisibleHint()) {
isVisible = true;
onVisible();
} else {
isVisible = false;
onInvisible();
}
}
/**
* 如果是通过 FragmentTransaction 的 show 和 hide 的方法来控制显示,调用的是 onHiddenChanged.
* 若是初始就show的Fragment 为了触发该事件 需要先hide再show
* @param hidden hidden True if the fragment is now hidden, false if it is not visible.
*/
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (!hidden) {
isVisible = true;
onVisible();
} else {
isVisible = false;
onInvisible();
}
}
/**
* 是否为懒加载模式
* 注意:仅仅适用于和viewpager搭配使用
* 否则无法触发回调方法
*
* @return true 懒加载模式
*/
protected boolean isLazy() {
return false;
}
// 可见时回调
protected void onVisible() {
if (isReadyLazyLoad()) {
initData();
}
}
// 不可见时回调
protected void onInvisible() {
}
protected abstract void initData();
}
三、Fragment之间的通信
1、Fragment 与 Activity通信,通过接口回调
- 1)定义一个接口
- 2)Activity实现接口
- 3)Fragment的 onAttach 方法,传入的 Activity 参数转为接口对象引用
通过该引用把数据传给Activity - 4)Activity传入Fragment,在Fragment创建时可以通过Bundle传入数据
在Fragment已经 attach 到Activity后,只能通过Fragment的引用来传入数据
2、Fragment之间通信
Fragment 获取和它相关联的 Activity,通过Activity 去获取另一个Fragment的实例
四、add(), show(), hide(), replace()
1、区别
show(),hide() 是让Fragment 的 View setVisibility(true / false),不会调用生命周期
replace()会销毁视图,即调用onDestoryView、onCreateView等一系列生命周期
add()、replace() 不要在同一个阶级的 FragmentManager里混搭使用
2、使用场景
高频使用的 Fragment,使用show(),hide(),可以提高性能。
如果Fragment 有大量图片,使用 replace(),配合图片框架、在Fragment 视图销毁时,回收其图片
3、onHiddenChanged的回调时机
当使用 add() + show()、hide()跳转新的 Fragment 时,旧的 Fragment 回调onHiddenChanged()
不会回调 onStop()等生命周期方法,新的 Fragment 创建时不会回调onHiddenChanged()
4、Fragment重叠问题
使用 support-v4 24.0.0+
5、同时pop多个Fragment的问题
使用 support-v4 25.4.0+
五、FragmentManager 管理Fragment栈
1、Fragment 及 宿主Activity在创建时,会初始化一个 FragmentManager对象
处理好Fragment嵌套问题的关键,就是理清这些不同阶级的栈视图。
FragmentActivity 的 FragmentManager 栈,管理嵌套在该Activity中的Fragment
Fragment 的 ChildFragmentManager栈,管理嵌套在该Fragment中的子Fragment
2、
1)宿主 Activity 中,getSupportFragmentManager() 获取的是 FragmentActivity的 FragmentManager 对象
2)Fragment中,getFragmentManager() 获取的是父Fragment(如果没有,则是FragmentActivity)的FragmentManager对象
而 getChildFragmentManager()是获取自己的FragmentManager对象
六、FragmentPagerAdapter 与 FragmentStatePagerAdapter 的区别
1、 区别
-
1)FragmentPagerAdapter 中 每一个Fragment 都长存在内存中,适用于比较固定的少量的Fragment
FragmentPagerAdapter 在我们切换Fragment过程中不会销毁Fragment,只是调用事务中的detach方法。
而在detach方法中只会销毁Fragment中的View,而不会销毁Fragment对象。 -
2)FragmentStatePagerAdapter中实现将 只保留当前页面
页面切换时,调用add/remove,当页面不可见,就会被移除,释放其资源
页面需要显示时,生成新的页面移除Fragment 前会保存 Fragment状态,重新展示时,会根据状态重新创建页面
较多 Fragment 时候,为了减少内存可适用
FragmentStatePagerAdapter 在我们切换 Fragment,会把前面的Fragment直接销毁掉
2、 使用 FragmentPagerAdapter+ViewPager 的注意事项
-
1)切换 Fragment时(已经初始化完毕),不回调任何生命周期方法、以及onHiddenChanged(),
只有setUserVisibleHint(boolean isVisibleToUser)会被回调,所以如果你想进行一些懒加载,需要在这里处理 -
2)在 ViewPager 绑定 FragmentPagerAdapter 时,
new FragmentPagerAdapter(fragmentManager) 的 FragmentManager,一定要保证正确
如果 ViewPager 是 Activity 的控件 -> getSupportFragmentManager()
如果 ViewPager 是 Fragment的控件 -> getChildFragmentManager()
只要记住【ViewPager内的 Fragments 是当前组件的子Fragment这个原则】 -
3)不需要考虑在“内存重启”的情况下,去恢复的Fragments的问题,因为 FragmentPagerAdapter 已经帮我们处理了
七、为什么不 new Fragment 的方式传入数据
因为“内存重启”,内存不够时,有可能会回收后台页面,导致数据丢失
使用 setArguments(Bundle args),在 “内存重启”前,系统会保存数据,不会造成数据的丢失
和Activity的Intent恢复机制类似。
八、Fragment坑点、解决方案
1、 getActivity()空指针
原因
- 后台Activity “内存重启”,回到前台时系统恢复Activity,此时 Fragment已经onDetach()
- 在pop了Fragment 后,Fragment 的异步任务仍然在执行,执行完成后调用了getActivity()
解决
- 在Fragment的 onDestroyView()中,结束不必要的异步任务、延时任务
- 如果你需要在Fragment中用到宿主Activity对象,在基类Fragment定义一个Activity的全局变量,
在onAttach中,持有Activity引用,可以有效避免一些意外Crash。
2、 Can not perform this action after onSaveInstanceState
原因
后台Activity “内存重启”,系统调用 onSaveInstanceState() 保存Activity状态和数据
在 Activity onResume()之前,执行Fragment事务,抛出异常。
(一般是其他 Activity 的回调,让当前页面执行事务的情况,会引发该问题)
解决
-
使用 commitAllowingStateLoss()方法提交,但是有可能导致该次提交无效!(宿主Activity被强杀时)
-
对于 popBackStack() 没有对应的 popBackStackAllowingStateLoss()方法
利用onActivityForResult()/onNewIntent(),可以做到事务的完整性,不会丢失事务//ReceiverActivity 或 其子Fragment: void start(){ startActivityForResult(new Intent(this, SenderActivity.class), 100); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == 100 && resultCode == 100) { // 执行Fragment事务 } } // SenderActivity 或 其子Fragment: void do() { // 操作ReceiverActivity(或其子Fragment)执行事务 setResult(100); finish(); }
3、Fragment重叠异常—正确使用hide、show的姿势【v4-24.0.0+ 开始,官方修复了上述 没有保存mHidden的问题】
-
1)触发条件
i. 发生了页面重启(旋转屏幕、内存不足等情况被强杀重启)。
在类onCreate()的方法加载Fragment,
没有判断 saveInstanceState==null 或 if (findFragmentByTag(mFragmentTag) == null),
导致重复加载了同一个Fragment导致重叠(PS:replace情况下,如果没有加入回退栈,则不判断也不会造成重叠,但建议还是统一判断下)
ii.重复 replace|add Fragment 或者 使用 show , hide控制Fragment;
如果 add() 几个Fragment,使用show()、hide()方法控制,比如微信、QQ的底部tab等情景,
如果你什么都不做的话,在“内存重启”后回到前台,app的这几个Fragment界面会重叠。iii.重叠原因
FragmentManager 帮我们管理Fragment,当发生“内存重启”,他会从栈底向栈顶的顺序一次性恢复Fragment
但是因为官方没有保存Fragment 的 mHidden属性,默认为false,即show状态
所以,所有Fragment都是以show的形式恢复,我们看到了界面重叠。(如果是replace,恢复形式和Activity一致,只有当你 pop之后上一个Fragment才开始重新恢复,
所有使用replace不会造成重叠现象) -
2)解决
i. findFragmentByTag()
add()、replace()时绑定一个tag,一般我们是用fragment的类名作为tag,发生“内存重启”时(savedInstanceState != null)),
通过 findFragmentByTag 找到对应的 Fragment,hide()需要隐藏的fragment。@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity); TargetFragment targetFragment; HideFragment hideFragment; if (savedInstanceState != null) { // “内存重启”时调用 targetFragment = getSupportFragmentManager().findFragmentByTag(TargetFragment.class.getName); hideFragment = getSupportFragmentManager().findFragmentByTag(HideFragment.class.getName); // 解决重叠问题 getFragmentManager().beginTransaction() .show(targetFragment) .hide(hideFragment) .commit(); }else{// 正常时 targetFragment = TargetFragment.newInstance(); hideFragment = HideFragment.newInstance(); getFragmentManager().beginTransaction() .add(R.id.container, targetFragment, targetFragment.getClass().getName()) .add(R.id,container,hideFragment,hideFragment.getClass().getName()) .hide(hideFragment) .commit(); } }
ii.手动维护一个 mSupportHidden
public class BaseFragment extends Fragment { private static final String STATE_SAVE_IS_HIDDEN = "STATE_SAVE_IS_HIDDEN"; @Override public void onCreate(@Nullable Bundle savedInstanceState) { if (savedInstanceState != null) { boolean isSupportHidden = savedInstanceState.getBoolean(STATE_SAVE_IS_HIDDEN); FragmentTransaction ft = getFragmentManager().beginTransaction(); if (isSupportHidden) { ft.hide(this); } else { ft.show(this); } ft.commit(); } } @Override public void onSaveInstanceState(Bundle outState) { outState.putBoolean(STATE_SAVE_IS_HIDDEN, isHidden()); } }
iii. support升级到 v4-24.0.0+
4、Fragment的嵌套问题
5、不靠谱的出栈方法remove()
addToBackStack(name) 将Fragment加入回退栈,remove()不能正常将Fragment从栈内移除
会返回到被remove()的Fragment,而且是空白页面。
没有将Fragment加入回退栈,remove方法可以正常出栈。
加入了回退栈,popBackStack()系列方法才能真正出栈
6、多个Fragment同时出栈的深坑BUG 【support-25.4.0 版本修复】
在Fragment库中如下4个方法是可能产生BUG的:
popBackStack(String tag,int flags)
popBackStack(int id,int flags)
popBackStackImmediate(String tag,int flags)
popBackStackImmediate(int id,int flags)
上面4个方法作用是,出栈到 tag/id 的fragment,(一次多个Fragment被出栈)
-
1)FragmentManager 栈中 管理 fragment下标位置的数组 ArrayList mAvailIndeices 的BUG
FragmentManagerImpl.makeActive(Fragment f)
用 ArrayList 模拟的栈,多个Fragment 同时出栈时,数组中对应位置变成了null
新的Fragment再入栈时,栈内还存在null比如:
栈中原始数据: [A,B,C,D]
清除C以及其以上的所有Fragment:popBackStack(C, FragmentManager.POP_BACK_STACK_INCLUSIVE)
清除后栈结构:[A,B,null,null]
添加新的 Fragment: addToBackStack©
添加后的栈结构:[A,B,null,C]导致“内存重启”后,异常和Crash
-
2)popBackStack的坑
popBackStack 是加入到主线程队列的末尾,【等其它任务完成后才开始出栈】
popBackStackImmediate 是队列内的任务立即执行,再将出栈任务放到队列尾(可以理解为立即出栈)。问题:
如果 popBackStack 多个Fragment后,紧接着 beginTransaction() add新的一个Fragment,
接着发生了“内存重启”后,你再执行 popBackStack(),app就会Crash解决方案
postDelay出栈动画时间再执行其它事务,但是根据我的观察不是很稳定。如果你想【出栈多个Fragment,你应尽量使用 popBackStackImmediate(tag/id)】,而不是 popBackStack(tag/id),
如果你想在【出栈后,立刻 beginTransaction()开始一项事务,应该把事务的代码 post/postDelay 到主线程的消息队列】
7、深坑 Fragment转场动画(仅分析v4包下的Fragment)
getFragmentManager().beginTransaction()
.setCustomAnimations(enter, exit)
-
如果有 通过 tag/id 同时出栈多个Fragment,需要搭配 Fragment.onCreateAnimation() 临时取消出栈动画
请谨慎使用.setCustomAnimations(enter, exit, popEnter, popExit)
在support-25.4.0之前出栈多Fragment时,伴随出栈动画,会在某些情况下发生异常 -
如果想给下一个Fragment 设置进栈动画、出栈动画
setCustomAnimations(enter, exit) 只能设置进栈动画,第二个参数并不是设置出栈动画setCustomAnimations(enter, exit, popEnter, popExit),第1个参数对应进栈动画,第4个参数对应出栈动画
setCustomAnimations(进栈动画, exit, popEnter, 出栈动画) -
让出栈动画运作正常的话,使用 Fragment 的 onCreateAnimation 控制动画
1)pop多个Fragment时转场动画 带来的问题 【在support-25.4.0 版本修复】
在使用 pop(tag/id) 同时出栈多个Fragment的这种情况下,将转场动画临时取消、延迟一个动画的时间再去执行其他事务;
原因在于这种情景下【可能会导致栈内顺序错乱】(上文有提到),
如果发生“内存重启”后,因为 Fragment 转场动画没结束时再执行其他方法,
会导致Fragment状态不会被FragmentManager正常保存下来。2)进入新的Fragment、并立刻关闭当前Fragment 时的一些问题
如果从当前Fragment进入一个新的Fragment,并且同时要关闭当前Fragment。
由于数据结构是栈,所以正确做法是先pop,再add,
但是转场动画会有覆盖的不正常现象,需要特殊处理,不然会闪屏 -
Tip:
如果遇到 Fragment 的 mNextAnim 空指针的异常(通常是在你的Fragment被重启的情况下),
1)检查 操作的 Fragment是否为null
2)在 Fragment转场动画还没结束时,是否执行了其他事务等方法解决思路
延迟一个动画时间再执行事务,或者 临时将该Fragment设为无动画
推荐阅读:
Fragment全解析系列(一):那些年踩过的坑 https://www.jianshu.com/p/d9143a92ad94
Google-Fragment概览 https://developer.android.google.cn/guide/components/fragments
Google-与其他Fragment通信 https://developer.android.google.cn/training/basics/fragments/communicating