Fragment全解析系列(二):正确的使用姿势
作为一个稳定的app,从后台且回到前台,一定会在任何情况都能恢复到离开前的页面,并且保证数据的完整性。
如果你没看过本系列的第一篇,为了方便后面文章的介绍,先规定一个“术语”,安卓app有一种特殊情况,就是 app运行在后台的时候,系统资源紧张的时候导致把app的资源全部回收(杀死app的进程),这时把app再从后台返回到前台时,app会重启。这种情况下文简称为:“内存重启”。(屏幕旋转等配置变化也会造成当前Activity重启,本质与“内存重启”类似)
1、一些使用建议
1、对Fragment传递数据,建议使用setArguments(Bundle args)
,而后在onCreate
中使用getArguments()
取出,在 “内存重启”前,系统会帮你保存数据,不会造成数据的丢失。和Activity的Intent原理一致。
2、使用newInstance(参数)
创建Fragment对象,优点是调用者只需要关系传递的哪些数据,而无需关心传递数据的Key是什么。
3、如果你需要在Fragment中用到宿主Activity对象,建议在你的基类Fragment定义一个Activity的全局变量,在onAttach
中初始化,这不是最好的解决办法,但这可以有效避免一些意外Crash。详细原因参考第一篇的“getActivity()空指针”部分。
protected Activity mActivity;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
this.mActivity = activity;
}
2、add(), show(), hide(), replace()的那点事
1、区别show()
,hide()
最终是让Fragment的View setVisibility
(true还是false),不会调用生命周期;
replace()
的话会销毁视图,即调用onDestoryView、onCreateView等一系列生命周期;
add()
和 replace()
不要在同一个阶级的FragmentManager里混搭使用。
2、使用场景
如果你有一个很高的概率会再次使用当前的Fragment,建议使用show()
,hide()
,可以提高性能。
在我使用Fragment过程中,大部分情况下都是用show()
,hide()
,而不是replace()
。
注意:如果你的app有大量图片,这时更好的方式可能是replace,配合你的图片框架在Fragment视图销毁时,回收其图片所占的内存。
3、onHiddenChanged的回调时机
当使用add()
+show(),hide()
跳转新的Fragment时,旧的Fragment回调onHiddenChanged()
,不会回调onStop()
等生命周期方法,而新的Fragment在创建时是不会回调onHiddenChanged()
,这点要切记。
4、Fragment重叠问题
使用show()
,hide()
带来的一个问题就是,如果你不做任何处理,在“内存重启”后,Fragment会重叠;
有些小伙伴可能就是为了避免Fragment重叠问题,而选择使用replace()
,但是使用show()
,hide()
时,重叠问题是完全可以解决的,有两种方式解决,详情参考上一篇。
3、关于FragmentManager你需要知道的
1、FragmentManager栈视图
(1)每个Fragment以及宿主Activity(继承自FragmentActivity)都会在创建时,初始化一个FragmentManager对象,处理好Fragment嵌套问题的关键,就是理清这些不同阶级的栈视图。
下面给出一个简要的关系图:

(2)对于宿主Activity,getSupportFragmentManager()
获取的FragmentActivity的FragmentManager对象;
对于Fragment,getFragmentManager()
是获取的是父Fragment(如果没有,则是FragmentActivity)的FragmentManager对象,而getChildFragmentManager()
是获取自己的FragmentManager对象。
2、恢复Fragment时(同时防止Fragment重叠),选择getFragments()还是findFragmentByTag()
6月12日更:
可以直接跳过,看我的解决方案,9行代码解决所有情况的Fragment重叠,传送门
(1)选择getFragments()
对于一个Activity内的多个Fragment,如果Fragment的关系是“流程”,比如登录->注册/忘记密码->填写信息->跳转到主页Activity。这种情况下,用getFragments()的方式是最合适的,在你的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)选择findFragmentByTag()
恢复
如果你的Activity的Fragments,不是“流程”关系,而是“同级”关系,比如QQ的主界面,“消息”、“联系人”、“动态”,这3个Fragment属于同级关系,用上面的代码就不合适了,恢复的时候总会恢复最后一个,即“动态Fragment”。
正确的做法是在onSaveInstanceState()
内保存当前所在Fragment的tag或者下标,在onCreate()
是恢复的时候,隐藏其它2个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.class.getName);
contactFragment = getSupportFragmentManager().findFragmentByTag(ContactFragment.class.getName);
meFragment = getSupportFragmentManager().findFragmentByTag(MeFragment.class.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);
}
当然在“同级”关系中,使用getFragments()恢复也是可以的。
6月12日更:
我的解决方案,9行代码解决所有情况的Fragment重叠,传送门
4、使用FragmentPagerAdapter+ViewPager的注意事项
1、使用FragmentPagerAdapter+ViewPager时,切换回上一个Fragment页面时(已经初始化完毕),不会回调任何生命周期方法以及onHiddenChanged()
,只有setUserVisibleHint(boolean isVisibleToUser)
会被回调,所以如果你想进行一些懒加载,需要在这里处理。
2、在给ViewPager绑定FragmentPagerAdapter时,new FragmentPagerAdapter(fragmentManager)
的FragmentManager,一定要保证正确,如果ViewPager是Activity内的控件,则传递getSupportFragmentManager()
,如果是Fragment的控件中,则应该传递getChildFragmentManager()
。只要记住ViewPager内的Fragments是当前组件的子Fragment这个原则即可。
3、你不需要在“内存重启”的情况下,去恢复的Fragments,有FragmentPagerAdapter的存在,不需要你去做恢复工作。
5、Fragment事务和动画,你可能不知道的坑
1、如果你在使用popBackStackImmdiate()
方法后,紧接着直接调用类似如下事务的方法,出栈动画还没完成就进行下一个事务方法,这会导致在内存重启后按返回键报错问题。

getSupportFragmentManager().popBackStackImmdiate();
getSupportFragmentManager().beginTransaction()
.add(R.id.container, fragment , tag)
.hide(currentFragment)
.commit;
正确的做法是使用主线程的Handler,将事务放到Runnable里运行。
getSupportFragmentManager().popBackStackImmdiate();
new Handler().postDelay(new Runnable(){
@Override
public void run() {
// 在这里执行Fragment事务
}
}, 你的出栈动画时间);
2、给Fragment设定Fragment转场动画时,如果你的app有使用popStackBackxx(tag/id,flags)出栈多个Fragment时,应避免直接使用.setCustomAnimations(enter, exit, popEnter, popExit)
,需要配合onCreateAnimtation方法
将出栈动画临时取消,本系列最后一篇给出了我的解决方案,解决了该问题,有兴趣可以自行查看 :)
(注意:如果你想给下一个Fragment设置进栈动画和出栈动画,.setCustomAnimations(enter, exit)只能设置进栈动画,第二个参数并不是设置出栈动画;
请使用.setCustomAnimations(enter, exit, popEnter, popExit),这个方法的第1个参数对应进栈动画,第4个参数对应出栈动画,所以是.setCustomAnimations(进栈动画, exit, popEnter, 出栈动画))
另外一提:谨慎使用popStackBack(String tag/int id,int flasg)
系列的方法,原因在上一篇中已经描述。
Tip:
如果你遇到Fragment的mNextAnim空指针的异常(通常是在你的Fragment被重启的情况下),那么你首先需要检查是否操作的Fragment是否为null;其次在你的Fragment转场动画还没结束时,你是否就执行了其他事务等方法;解决思路就是延迟一个动画时间再执行事务,或者临时将该Fragment设为无动画
6、是使用单Activity+多Fragment的架构,还是多模块Activity+多Fragment的架构?
单Activity+多Fragment:
一个app仅有一个Activity,界面皆是Frament,Activity作为app容器使用。
优点:性能高,速度最快。参考:新版知乎 、google系app
缺点:逻辑比较复杂,尤其当Fragment之间联动较多或者嵌套较深时,比较复杂。
多模块Activity+多Fragment:
一个模块用一个Activity,比如
1、登录注册流程:
LoginActivity + 登录Fragment + 注册Fragment + 填写信息Fragment + 忘记密码Fragment
2、或者常见的数据展示流程:
DataActivity + 数据列表Fragment + 数据详情Fragment + ...
优点:速度快,相比较单Activity+多Fragment,更易维护。
我的观点:
权衡利弊,我认为多模块Activity+多Fragment是最合适的架构,开发起来不是很复杂,app的性能又很高效。
当然。Fragment只是官方提供的灵活组件,请优先遵从你的项目设计!真的特别复杂的界面,或者单个Activity就可以完成一个流程的界面,使用Activity可能是更好的方案。
最后
如果你读完了第一篇和这篇文章,那么我相信你使用多模块Activity+多Fragment的架构所遇到的坑,大部分都应该能找到解决办法。
但是如果流程较为复杂,比如Fragment A需要启动一个新的Fragment B并且关闭当前A,或者A启动B,B在获取数据后,想在返回到A时把数据交给A(类似Activity的startActivityForResult
),又或者你保证在Fragment转场动画的情况下,使用pop(tag\id)
从栈内退出多个Fragment,或者你甚至想Fragment有一个类似Activity的SingleTask
启动模式,那么你可以参考下一篇,我的解决方案库,Fragmentation。它甚至提供了一个让你在开发时,可以随时查看所有阶级的栈视图的UI界面。
原文链接:http://www.jianshu.com/p/fd71d65f0ec6
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
Fragment全解析系列(二):正确的使用姿势的更多相关文章
- Fragment全解析系列
(一):那些年踩过的坑 开始之前 最新版知乎,单Activity多Fragment的架构,响应可以说非常“丝滑”,非要说缺点的话,就是没有转场动画,并且转场会有类似闪屏现象.我猜测可能和Fragmen ...
- Fragment全解析系列(一):那些年踩过的坑
开始之前 最新版知乎,单Activity多Fragment的架构,响应可以说非常"丝滑",非要说缺点的话,就是没有转场动画,并且转场会有类似闪屏现象.我猜测可能和Fragment转 ...
- Fragment全解析系列(三):Fragment之我的解决方案:Fragmentation
源码地址:Github,欢迎Star,Fork. Demo网盘下载(V_0.7.13)Demo演示:单Activity + 多Fragment,项目中有3个Demo. 流式的单Activity+多Fr ...
- 【转载】Fragment 全解析(1):那些年踩过的坑
http://www.jianshu.com/p/d9143a92ad94 Fragment系列文章:1.Fragment全解析系列(一):那些年踩过的坑2.Fragment全解析系列(二):正确的使 ...
- PHP 类与对象 全解析( 二)
目录 PHP 类与对象 全解析( 一) PHP 类与对象 全解析( 二) PHP 类与对象 全解析(三 ) 7.Static关键字 声明类成员或方法为static,就可以不实例化类而直接访问.不能通过 ...
- ETL利器Kettle实战应用解析系列二
本系列文章主要索引如下: 一.ETL利器Kettle实战应用解析系列一[Kettle使用介绍] 二.ETL利器Kettle实战应用解析系列二 [应用场景和实战DEMO下载] 三.ETL利器Kettle ...
- java基础解析系列(二)---Integer
java基础解析系列(二)---Integer 前言:本系列的主题是平时容易疏忽的知识点,只有基础扎实,在编码的时候才能更注重规范和性能,在出现bug的时候,才能处理更加从容. 目录 java基础解析 ...
- Telegram学习解析系列(二):这我怎么给后台传输数据?
写在前面: 在iOS开发的过程中,有很多时候我们都在和数据打交道,最基本的就是数据的下载和上传了,估计很多很多的小伙伴都在用AFNetworking与后台数据打交道,可有没有想过,哪天AFNetwor ...
- Android图片加载框架最全解析(二),从源码的角度理解Glide的执行流程
在本系列的上一篇文章中,我们学习了Glide的基本用法,体验了这个图片加载框架的强大功能,以及它非常简便的API.还没有看过上一篇文章的朋友,建议先去阅读 Android图片加载框架最全解析(一),G ...
随机推荐
- Notification中使用Glide
之前一直在琢磨Glide自定义使用中的一些经验:今天简单的分享下Notification中使用Glide去加载icon方法: 我们都知道使用android通知时,一般可能会有如下代码: Notific ...
- MVC4与JSON交互的知识总结
一:jquery传递JSON给MVC4后台 1.JSON传递单个参数给Controller某个Action方法 [前台js] $(document).ready(function () { var p ...
- LeetCode Find All Numbers Disappeared in an Array
原题链接在这里:https://leetcode.com/problems/find-all-numbers-disappeared-in-an-array/ 题目: Given an array o ...
- MyBatis基于注解的动态SQL——概览
- js暂停的函数
// numberMillis 毫秒 function sleep(numberMillis) { var now = new Date(); var exitTime = now.getTime() ...
- Android Studio中获取查看签名SHA1证书指纹数据或MD5的方法
原来在Eclipse中获取SHA1或者MD5,在IDE界面上就可以查找到. 切换到Android Studio后,如何查看呢?找了半天没找到.那就老办法命令行. 第一步.打开Android Studi ...
- iOS -Swift 3.0 -for(循环语句用法)
// // ViewController.swift // Swift-循环语句 // // Created by luorende on 16/12/08. // Copyright © 2016年 ...
- 偶遇到 java.util.ConcurrentModificationException 的异常
今天在调试程序 遇到了如此问题 贴上代码来看看稍后分析 List<String> list = null;boolean isUpdate = false;try { list = JSO ...
- WPF绑定xml数据源
1.界面 <UserControl x:Class="HKDCMS.Client.Demo.UIViews.UIControls.AboutUsControl" xmlns= ...
- C++实现黄金分割数列(斐波纳奇数列)(非递归)
斐波纳奇数列: 定义为F0=0,F1=1,......Fn=F(n-1)+F(n-2) 1,1,2,3,5,8,13,21,34,55....... void FibonacciOdd(int n) ...