title: Fragment使用总结
tags: 学习笔记,Android,Fragment
原文:
Fragment全解析系列(一):那些年踩过的坑
Fragment全解析系列(二):正确的使用姿势
Fragment之我的解决方案:Fragmentation
“内存重启”:Android app有一个种特殊情况,就是app运行在后台的时候,系统资源紧张的时候导致把app的资源全部回收(杀死app的进程),这时把app再从后台返回到前台时,app会重启。
Fragment使用技巧
使用建议:
- 对Fragment传递数据,建议使用
setArgument(Bundle args)
,而后在onCreate
中使用getArgument()
取出,在“内存重启”前,系统会帮助你保存数据,不会造成数据的丢死。 - 使用
newInstance(参数)
创建Fragment对象,优点是调用者只需要关心传递的哪些数据,而无需关心传递数据的Key是什么。 - 如果你需要在Fragment中用到宿主Activity对象,建议在你的基类Fragment定义一个Activity的全局变量,在
onAttach
中初始化。原因getActivity()
可能空指针,在OnCreateView()
内出现getAcitvity()
的代码很可能是危险的。
getActivity()空指针
大多数情况的原因:你在调用了getActivity()时,当前的Fragment已经onDetach()
了宿主Activity。比如:你在pop了Fragment之后,getFragment的异步任务仍然在执行,并且在执行完成后调用了getActivity()方法,这样就会空指针。
add(),show(),hide(),replace()
- 区别:
show()
,hide()
最终是让Fragment的ViewsetVisibility
(true还是false),不会调用生命周期;replace()
的话会销毁视图,即调用onDestoryView
,onCreateView
等一系列生命周期;add()
和replace()
不要在同一个阶级的FragmentManager里混搭使用。 - 使用场景:如果你有一个很高的概率会再次使用当前的Fragment,建议使用
show()
,hide()
,可以提高性能。 - onHiddenChanged的回调时机:当使用
add()
+show()
,hide()
跳转新的Fragment时,旧的Fragment回调onHiddenChanged()
,不会回调onStop()
等生命周期方法,re新的Fragment在创建时时不会回调onHiddenChanged()
,这点要切记。 - Fragment重叠问题:使用
show()
,hide()
带来的一个问题就是,如果你不做任何处理,在“内存重启”后,Fragment会重叠;
两种解决方案:(一下不考虑Fragment嵌套的情况)
-
findFragmentByTag
:
即在add()
或者replace()
时绑定一个tag,一般用fragment的类名作为tag,然后在发生“内存重启”时,通过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.getClass().getName);
hideFragment = getSupportFragmentManager().findFragmentByTag(hideFragment.getClass().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();
}
}
如果你想恢复到用户离开时的那个Fragment的界面,你还需要在onSaveInstanceState(Bundle outState)
里保存离开时的那个界面的tag或者下标,在onCreate
“内存重启”代码块中,取出tag/下标,进行恢复。
- 使用getSupportFragmentManager().getFragments()恢复
使用getFragments()
可以获取到当前FragmentManager管理的站内所有Fragment。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
TargetFragment targetFragment;
HideFragment hideFragment;
if (savedInstanceState != null) { // “内存重启”时调用
List<Fragment> fragmentList = getSupportFragmentManager().getFragments();
for (Fragment fragment : fragmentList) {
if(fragment instanceof TartgetFragment){
targetFragment = (TargetFragment)fragment;
}else if(fragment instanceof HideFragment){
hideFragment = (HideFragment)fragment;
}
}
// 解决重叠问题
getFragmentManager().beginTransaction()
.show(targetFragment)
.hide(hideFragment)
.commit();
}else{ // 正常时
targetFragment = TargetFragment.newInstance();
hideFragment = HideFragment.newInstance();
// 这里add时,tag可传可不传
getFragmentManager().beginTransaction()
.add(R.id.container)
.add(R.id,container,hideFragment)
.hide(hideFragment)
.commit();
}
}
从代码看起来,这种方式比较复杂,但是这种方式在一些场景下比第一种更加简便有效。
关于FragmentManager你需要知道的
-
FragmentManager栈视图
1.1. 每个Fragment以及宿主Activity(继承自FragmentActivity)都会在创建时,初始化一个FragmentManager对象,处理好Fragment嵌套问题的关键,就是理清这些不同层级的栈视图。 简要关系图:
1.2. 对于宿主Activity,
getFragmentManager()
是获取的是父Fragment(如果没有,则是FragmentActivity)的FragmentManager对象,而getChildFragmentManager()
是获取自己的FragmentManager对象。 -
恢复Fragment时(同时防止Fragment重叠),选择getFragments()还是findFragmentByTag()
2.1. 选择getFragment()
: 对于一个Activity内的多个Fragment,如果Fragment的关系是“流程”,比如登录->注册/忘记密码->填写信息->跳转到主页Activity。这种情况下,用gerFragments()的方式是最合适的,在你的Activity内(更好的方式是在你的所有“流程”基类Activity里),写下如下代码:@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState != null) { List<Fragment> fragments = getSupportFragmentManager().getFragments(); if (fragments != null && fragments.size() > 0) { boolean showFlag = false; FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); for (int i = fragments.size() - 1; i >= 0; i--) { Fragment fragment = fragments.get(i); if (fragment != null) { if (!showFlag) { ft.show(fragments.get(i)); showFlag = true; } else { ft.hide(fragments.get(i)); } } } ft.commit(); } } }
上面恢复Fragment的方式,不仅提高性能,同时避免了Fragment重叠现象,最重要的事,不需要关心Activity容器里有哪些Fragment。
2.2. 选择
findFragmentByTag()
恢复:如果你的Activity的Fragments,不是“流程”关系,而是“同级”关系,比如QQ的主界面,“消息”、“联系人”、“动态”,这3个Fragment属于同级关系,用上面的代码就不合适了,恢复的时候总会恢复最后一个,即“动态Fragment”。
正确的做法是在onSaveInstanceState()
内保存当前所在Fragment的Tag或者下标,在onCreate()
恢复的时候,隐藏其它Fragment。@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity); MsgFragment msgFragment; ContactFragment contactFragment; MeFragment meFragment; if (savedInstanceState != null) { // “内存重启”时调用 msgFragment = getSupportFragmentManager().findFragmentByTag(msgFragment.getClass().getName); contactFragment = getSupportFragmentManager().findFragmentByTag(contactFragment.getClass().getName); meFragment = getSupportFragmentManager().findFragmentByTag(meFragment.getClass().getName); index = saveInstanceState.getInt(KEY_INDEX); // 根据下标判断离开前是显示哪个Fragment, // 这里省略判断代码,假设离开前是ConactFragment // 解决重叠问题 getFragmentManager().beginTransaction() .show(contactFragment) .hide(msgFragment) .hide(meFragment) .commit(); }else{ // 正常时 msgFragment = MsgFragment.newInstance(); contactFragment = ContactFragment.newInstance(); meFragment = MeFragment.newInstance(); getFragmentManager().beginTransaction() .add(R.id.container, msgFragment, msgFragment.getClass().getName()) .add(R.id.container, contactFragment, contactFragment.getClass().getName()) .add(R.id,container,meFragment,meFragment.getClass().getName()) .hide(contactFragment) .hide(meFragment) .commit(); } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); // 保存当前Fragment的下标 outState.putInt(KEY_INDEX, index); }
使用ViewPager + Fragment的注意事项
使用ViewPager + Fragment时,切换不同ViewPager页面,不会回调任何生命周期方法以及
onHiddenChanged()
,只有setUserVisibleHint(boolean isVisibleToUser)
会被回调,所以如果你想进行一些懒加载,需要在这里处理。在给ViewPager绑定FragmentPagerAdapter时,
new FragmentPagerAdapter(fragmentManager)
的FragmentManager,一定要保证正确,如果ViewPager是Activity内的控件,则传递getSupportFragmentManager()
,如果是Fragment的控件中,则应该传递getChildFragmentManager()
。只要记住ViewPager内的Fragments是当前组件的子Fragment这个原则即可。如果使用ViewPager + Fragment,不要要在“内存重启”的情况下,去恢复Fragments,有FragmentPagerAdapter的存在,不需要你去做恢复工作。
Fragment事务
- 如果你在使用
popBackStackImmdiate()
方法后,紧接着直接调用类似如下事务的方法,因为他们运行在消息队列的问题,还没来得及出栈就运行事务的方法了,这可能会导致不正常现象。
getSupportFragmentManager().popBackStackImmdiate();
getSupportFragmentManager().beginTransaction()
.add(R.id.container, fragment , tag)
.hide(currentFragment)
.commit;
正确的做法是使用主线程的Handler,将事务放到Runnable里运行。
getSupportFragmentManager().popBackStackImmdiate();
new Handler().post(new Runnable(){
@Override
public void run() {
// 在这里执行Fragment事务
}
});
- 给Fragment设定Fragment转场动画时,如果你没有一整套解决方案,应避免使用
.setTransition(transit)
以及.setCustomAnimations(enter, exit, popEnter, popExit)
,而只使用.setCustomAnimation(enter, exit)
这个方法。