Fragment系列文章:
1、Fragment全解析系列(一):那些年踩过的坑
2、Fragment全解析系列(二):正确的使用姿势
3、Fragment之我的解决方案:Fragmentation
本篇主要介绍一些Fragment使用技巧。
Fragment是可以让你的app纵享丝滑的设计,如果你的app想在现在基础上性能大幅度提高,并且占用内存降低,同样的界面Activity占用内存比Fragment要多,响应速度Fragment比Activty在中低端手机上快了很多,甚至能达到好几倍!如果你的app当前或以后有移植平板等平台时,可以让你节省大量时间和精力。
简陋的目录
1、一些使用建议
2、add(), show(), hide(), replace()的那点事
3、关于FragmentManager你需要知道的
4、使用FragmentPagerAdapter+ViewPager的注意事项
5、是使用单Activity+多Fragment的架构,还是多模块Activity+多Fragment的架构?
作为一个稳定的app,从后台且回到前台,一定会在任何情况都能恢复到离开前的页面,并且保证数据的完整性。
如果你没看过本系列的第一篇,为了方便后面文章的介绍,先规定一个“术语”,安卓app有一种特殊情况,就是 app运行在后台的时候,系统资源紧张的时候导致把app的资源全部回收(杀死app的进程),这时把app再从后台返回到前台时,app会重启。这种情况下文简称为:“内存重启”。(屏幕旋转等配置变化也会造成当前Activity重启,本质与“内存重启”类似)
<h1 id="1"> 1、一些使用建议 </h1>
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;
}
<h1 id="2">2、add(), show(), hide(), replace()的那点事</h1>
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会重叠;(该BUG在support-v4 24.0.0+以上 官方已修复)
有些小伙伴可能就是为了避免Fragment重叠问题,而选择使用replace()
,但是使用show()
,hide()
时,重叠问题很简单解决的:
- 如果你在用24.0.0+的版本,不需要特殊处理,官方已经修复该BUG;
- 如果你在使用小于24.0.0以下的v4包,可以参考9行代码让你App内的Fragment对重叠说再见
<h1 id="3">3、关于FragmentManager你需要知道的</h1>
FragmentManager栈视图:
(1)每个Fragment以及宿主Activity(继承自FragmentActivity)都会在创建时,初始化一个FragmentManager对象,处理好Fragment嵌套问题的关键,就是理清这些不同阶级的栈视图。
下面给出一个简要的关系图:
栈关系图.png(2)对于宿主Activity,getSupportFragmentManager()
获取的FragmentActivity的FragmentManager对象;
对于Fragment,getFragmentManager()
是获取的是父Fragment(如果没有,则是FragmentActivity)的FragmentManager对象,而getChildFragmentManager()
是获取自己的FragmentManager对象。
<h1 id="4">4、使用FragmentPagerAdapter+ViewPager的注意事项</h1>
-
使用FragmentPagerAdapter+ViewPager时,切换回上一个Fragment页面时(已经初始化完毕),不会回调任何生命周期方法以及
onHiddenChanged()
,只有setUserVisibleHint(boolean isVisibleToUser)
会被回调,所以如果你想进行一些懒加载,需要在这里处理。 -
在给ViewPager绑定FragmentPagerAdapter时,
new FragmentPagerAdapter(fragmentManager)
的FragmentManager,一定要保证正确,如果ViewPager是Activity内的控件,则传递getSupportFragmentManager()
,如果是Fragment的控件中,则应该传递getChildFragmentManager()
。只要记住ViewPager内的Fragments是当前组件的子Fragment这个原则即可。 -
你不需要考虑在“内存重启”的情况下,去恢复的Fragments的问题,因为FragmentPagerAdapter已经帮我们处理啦。
<h1 id="5">5、是使用单Activity+多Fragment的架构,还是多模块Activity+多Fragment的架构?</h1>
单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界面。
网友评论
"获取Activity对象然后强转调用它的方法获取Activity里面的数据" 这种方式在 页面被强杀重启时,有可能造成数据丢失情况, 解决方式是:对于数据,在onSaveInstanceState()里保存,在 onCreate -> saveInstanceState !=null 时恢复, 即手动做保存和恢复
推荐下,源码圈 300 胖友的书单整理:http://c7.gg/rSTv
灿
pagerAdapter = new SimpleFragmentPagerAdapter(supportFragmentManager, mainActivity, list);
class SimpleFragmentPagerAdapter extends FragmentPagerAdapter {}
如果换成您说的getChildFragmentManager()就会报错,因为FragmentPagerAdapter是v4包的,而getChildFragmentManager()得到的FM不是v4包下的,请问是怎么回事?
原文:
在给ViewPager绑定FragmentPagerAdapter时,
new FragmentPagerAdapter(fragmentManager)的FragmentManager,一定要保证正确,如果ViewPager是Activity内的控件,则传递getSupportFragmentManager(),如果是Fragment的控件中,则应该传递getChildFragmentManager()。只要记住ViewPager内的Fragments是当前组件的子Fragment这个原则即可。
-----------------------------------------------------------------------------------------------------------------
继承FragmentPagerAdapter这个类是v4包下,设置参数要求是v4包的FragmentManager,您说的如果ViewPager是Fragment的View,则传递getChildFragmentManager(),但是得到的FragmentManager并不是v4的,程序会报错啊!!!
可以考虑换种方式来实现,比如建议加入回退栈~
java.lang.IllegalStateException: Activity has been destroyed
at android.support.v4.app.FragmentManagerImpl.enqueueAction(SourceFile:1470)
at android.support.v4.app.BackStackRecord.commitInternal(SourceFile:634)
at android.support.v4.app.BackStackRecord.commitAllowingStateLoss(SourceFile:617)
at android.support.v4.app.FragmentStatePagerAdapter.finishUpdate(SourceFile:161)
at android.support.v4.view.ViewPager.populate(SourceFile:1106)
at android.support.v4.view.ViewPager.populate(SourceFile:952)
at android.support.v4.view.ViewPager.onMeasure(SourceFile:1474)
at android.view.View.measure(View.java:16896)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5413)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:332)
at android.view.View.measure(View.java:16896)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5413)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1616)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:729)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:601)
在网上搜索,全都是要在onDetach中把mChildFragmentManager设置成null,我们用的23.0.1的v4包,这个操作其实fragmentmanager已经做了,还是会出现这个问题,请问楼主能否帮忙分析一下这是什么原因?
// 执行事务
} 这个吗?
if(!fragmentManager.isDestroy()){
// 执行事务
commitAllowingStateLoss();
}
可能是Fragment在执行异步任务过程中,关闭页面,AC销毁了,然后异步任务回调后执行了Fragment事务;
解决思路可以在关闭页面时及时取消异步任务;或者做如下判断:
if(!fragmentManager.isDestroy()){
// 执行事务
}
还一种设置ViewPager.setOffscreenPageLimit(你的总栏目数量-1) 可以达到一次性加载所有Fragment,并且切换连视图都不销毁,但是非常不建议这种方式
为何要自己实例化…
另外跨fg 跨层级的情况 建议用eventbus
- 现象: 我的 drawerlayout 的抽屉有两个选项A,B,默认选中A。content 是一个 Fragment,里面又嵌套了一个 ViewPager。当我切换到B时,会replace 最外层的 Fragment。但是当我重新切换回 A 的时候,ViewPager 就不能够滑动了,里面的子Fragment 也是白屏
- 我尝试把 viewpager.setOffscreenPageLimit 设置为2,上面的那种现象就不存在了。我想请问下这个问题到底是什么原因?是缓存造成的么?
我要请教的问题是:两者之间有什么异同点?性能啊,开发维护成本啊,交互效果啊等等
在activity初始化视图后调用getsupportfragment.getfragments()返回null;
第二种情况 mainActivity中的mainfragment使用viewpager包含三个同级fragment 同样在mainfragment初始化视图后调用getchildfragment.getfragments()返回不为空有3个fragment
栈视图层级,建议多打印栈看看~
我这边的思路是:
在销毁重建的代码块中(saveinstancestate !=null)通过调用getfragments()来find之前的三个fragment并赋值给FragmentPagerAdapter来恢复viewpager;但是此getfragments()会返回为空,google得知viewpager中fragmentmanager不会把fragment加入进来;这里可以通过另外一种findFragmentByTag("android:switcher:" + R.id.view_pager + ":position"))来获取,但这种方式始终感觉不好,因为是依靠androidsdk源码加载framgent命名的方式去hack的;
我看博主示例代码中是没有对viewpager中恢复fragment进行相关处理的 每次新建或重建 你的fragmentpagerAdapter代码里都会新创建fragment;
博主这里没有处理的原因是基于什么?是这里本来不需要额外处理还是没有考虑到要去处理?如果需要处理那最好的方法是什么?还有我发现用你的fragmentation库在fragment中使用viewpager调用getchildfragmentmanager.getfragments()不会返回null
ViewPager+FragmentAdapter时,不需要处理子Fragment的销毁重启的情况,原因在FragmentAdapter已经帮助处理了,可以看其源码~
“getchildfragmentmanager.getfragments()不会返回null”说明起有子Fragment啊 ~
replace内部有remove,remove对于没有被加入回退栈的Fragment是直接销毁的,所以AB被销毁为null,而对于加入回退栈的仅仅是销毁视图,即走onDestroyView,Fragment实例还在。
replace内部是remove+add, 而remove对于没有被加入回退栈的Fragment是直接销毁的,而对于加入回退栈的是销毁视图,实例还在。
fragmentManager.popBackStack();
} else {
fragmentManager.popBackStackImmediate();
}
请问怎么确定在什么情况下使用popBackStack();在什么情况下使用popBackStackImmediate();
if (savedInstanceState != null) { // “内存重启”时调用msgFragment = getSupportFragmentManager().findFragmentByTag(msgFragment.getClass().getName);}
对了 可以看下我最新的一篇简书,9行代码解决重叠问题,希望能对你有帮助
我修改下,感谢提醒!!
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.youshe.yangyi/com.youshe.yangyi.MainActivity}: java.lang.NullPointerException: Attempt to write to field 'int android.support.v4.app.Fragment.mNextAnim' on a null object reference
的错误,然后我如果show和hide之前进行非空判断,不报错但还是会出现Fragment重叠的情况,请问您有遇到过这种问题么?该如何解决?
因为不知道你的详细使用场景,建议你先检查下动画相关,重叠问题再看下文章中的2种方案;
同时推荐可以看看Fragmentation库,现在最新版0.7已经比较完整了,各种嵌套、同级Fragment都可以简单解决,所有的重叠问题库中都帮你解决好了
Observable.just("字符串")
.compose(this.<String>bindUntilEvent(ActivityEvent.DESTROY))
.observeOn(AndroidSchedulers.mainThread())
.map(new Func1<String, String>() {
@Override
public String call(String str) {
//操作1,在主线程上面执行
return str;
}
})
.subscribe(new Action1<String>() {
@Override
public void call(String string) {
//操作2,在主线程上面操作
}
});
我想在操作1和操作2之间停顿一段时间,例如3秒,应该怎么写(用rxjava),之前试过delay和delaySubscription都打不到想要的效果,请问该怎么做?
用flatMap吧:.flatMap(s -> Observable.just(s).delay(3, SECONDS)).subscribe();
还一种方案,subscribe里postDelay... 还是用delay吧~
commitAllowingStateLoss()是可以避免这个问题,但不是最佳方法,毕竟可能会StateLoss;
如果是因为onResume方法中的切换导致的话,你可以试试放到主线程Handler.post里执行,或者避免这种实现 :)
【•如果你在用24.0.0+的版本,需要特殊处理,官方已经修复该BUG;】
这句话是不是有误,应该是 不需要特殊处理吧
你再检查下代码,打印栈内Fragment的log,调试下看看~
如果你已经这么做了的话,还出现全部show状态的话(即你说的重叠现象),那就是内存重启后,你没有对栈内Fragment做处理,可以参考我的重叠现象的2种解决方案。主要思路是,获取栈内全部的Fragment,除了你需要show的Fragment之外,其他全部手动hide
(其实还有另外一种情况会全部show,不过那种情况比较特殊少见,就不做介绍了)
不过要注意,第一次Fragmrnt被加载进来的时候(add),是不会回调onHiddenChanged的,所以在创建Fragment时,需要你在onCreateView或onActivityCreated生命周期里刷新网络数据 :)
在FragmentManger.class里:
f.onAttach(mHost.getContext()); // 这里调用onAttach(Context context)方法
在Fragment.class里:
public void onAttach(Context context) { // 这里其实是判断宿主是否是Activity
mCalled = true;
final Activity hostActivity = mHost == null ? null : mHost.getActivity();
if (hostActivity != null) {
mCalled = false;
onAttach(hostActivity);
}
}
@deprecated
public void onAttach(Activity activity) {
mCalled = true; // 如果该方法被调用的话,onAttach(Context context)一定是被调用了
}
所以看源码的,如果是你说的这个异常的话,那就是mHost.getContext()这个所获取的不是Activity,这就奇怪了,看FragmentActivity新建Host对象的代码,getContext()和getActivity()应该是同一个Activity。
比较忙,没太深入看,有空我再看看哈,保持对这个异常的关注....
不过看到源码 我们就知道可以放心的使用过时的onAttach(Activity activity)
现在情况下是不是最好全都用 android.support.v4.app.fragment class?
会有什么缺点么?
而app.Fragment库相比较V4最大的问题是,不同版本的安卓系统的的Fragment相关类都可能是不同的,而v4里的Fragment就避免了这个问题,所有作为一般的APP,我们都是用v4的
FATAL EXCEPTION: main
java.lang.NullPointerException
at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:591)
at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1416)
at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:420)
at android.os.Handler.handleCallback(Handler.java:615)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4745)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
at dalvik.system.NativeStart.main(Native Method)
1、在当前这种情况下,动画临时取消掉;
2、postDelay动画的时间再进行事务操作 :)
2、播放完执行pop()方法
3、再打开程序,Fragment并没有出栈,而是从头开始播放
求解
解决这个问题的话:应该避免这种设计 或者 在返回时onResume时再pop
不过我想官方的FragmentTabHost应该已经为我们处理了一些坑点,所有可以考虑使用这个。(PS:回头我也会研究下这个控件)
我一直不明白一个方法setRetainInstance,按我的理解,设为false,就应该不随着activity的重启而重启才对啊。但实际上不是这样。既然fragment依附于activity,那就让activity全部初始化不好么,非得通过FragmentManager把丢失的对象再找回来,重新new一个不是一样的么。
另外,内存重启,个人觉得还是重走app流程的好,避免引用一些static变量导致空指针。如果只是某个activity被杀了,通过onRestore找回还可以接受。但要是整个app都被强杀掉了,真没必要还去做额外的容错。
光想着内存重启这种情况了~
内存重启的情况,尽量还是通过onSaveInstanceState保存 再恢复比较合适;很多中低端安卓手机内存重启发生的频率非常高,如果重走app,体验很不好;通过合理的恢复,把需要保存的变量保存起来,避免一些空指针的问题
内存重启必然意味着app进程被强杀的,重新打开app时,Application类首先重走onCreate,然后恢复被强杀前的那个Activity。并没有单个Activity被杀,而app没被杀的情况的 :)