美文网首页android应用框架android知识点Android那些事
Fragment全解析系列(二):正确的使用姿势

Fragment全解析系列(二):正确的使用姿势

作者: YoKey | 来源:发表于2016-02-28 16:16 被阅读53873次

    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()时,重叠问题很简单解决的:

    <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界面。

    相关文章

      网友评论

      • Aleyn_:悬浮球里边一直有 这个 SupportRequestManagerFragment 是怎么回事啊
        d49fa4ba68ce:@阿琳Ke 那你一定用的glide加载图片
      • 高级组装搬运工吴哥:博主很棒棒哦
      • 可可源:为什么用你这个框架会出现空白,Viewpager有三个Fragment,A ,B ,C ,然后点击C,再点击A,A被销毁了。
      • trycatchx:show(),hide() 不会回调任何的生命周期,那么如何得知 我的旧 fragment 又重新显示了?需要自己记录 吗?
      • codeT:写的很清楚,谢谢。最近没有继续写吗
      • Clendy:写的非常好!收藏了~~
      • 410fd86a74cd:项目中多处用的FragmentTabHost组织结构,遇到了Can not perform this action after onSaveInstanceState的问题。我知道这个是因为FragmentTabHost内部是commit而不是commitAllowingStateLoss造成的。所以重写了onSaveInstanceState,注释了//super。但是从后台bugly里面看到还有这种崩溃的记录。请大神指教
      • Man不经心:好文 fragment嵌套我用getsupprtfragmentmanager();结果切换账号重新加载子fragment的时候视图出不来,换成getChildFragmentManager()完美解决
      • 454b39345d5f:你好,请教一下fragment hide show 之后发现懒加载失效了,请问怎么解决这个问题?
      • 菜小东:您好楼主,上文提到了Activity像Fragment传递数据建议使用setArguments,我现在也是一直使用着这种方法,但最近遇到了一个android.os.TransactionTooLargeException on Nougat的问题,是因为我在Bundle传递了Parcels对象数据,数据大小是接口返回的,我无法控制,7.0以上好像对这种数据的大小有严格的限制,超过限制大小就报错了,我为了测试也搞了好几个Fragment传递Parcels对象数据,发现到达一定量7.0就崩溃了。7.0以下暂时没发现,所以感觉这种方式不太靠谱,要很注意数据量。我看网上还有另一种数据传递就是获取Activity对象然后强转调用它的方法获取Activity里面的数据,不知道这个靠谱不,我经常遇到Fragment里面的数据无缘无故没了。。。。。。
        YoKey:@菜小东 之所以建议setArguments(),是因为其会在强杀前后保存和恢复数据

        "获取Activity对象然后强转调用它的方法获取Activity里面的数据" 这种方式在 页面被强杀重启时,有可能造成数据丢失情况, 解决方式是:对于数据,在onSaveInstanceState()里保存,在 onCreate -> saveInstanceState !=null 时恢复, 即手动做保存和恢复
      • 技术宅的心:大神请问一下,如果要在原生fragment下不是在v4包下,那状态的保存要怎么实现,谢谢
      • 73580517689c:Fragment全解析系列(二):正确的使用姿势- ,写的不错不错,收藏了。

        推荐下,源码圈 300 胖友的书单整理:http://c7.gg/rSTv


        高级组装搬运工吴哥:这是什么东东?
        fe1421e713cc:写的蛮用心的,希望多多坚持那
      • 谢耀眼: android.support.v4.app.FragmentManager supportFragmentManager = mainActivity.getSupportFragmentManager();
        pagerAdapter = new SimpleFragmentPagerAdapter(supportFragmentManager, mainActivity, list);
        class SimpleFragmentPagerAdapter extends FragmentPagerAdapter {}
        如果换成您说的getChildFragmentManager()就会报错,因为FragmentPagerAdapter是v4包的,而getChildFragmentManager()得到的FM不是v4包下的,请问是怎么回事?
        谢耀眼:接49楼内容
      • 谢耀眼:作者大佬好
        原文:
        在给ViewPager绑定FragmentPagerAdapter时,
        new FragmentPagerAdapter(fragmentManager)的FragmentManager,一定要保证正确,如果ViewPager是Activity内的控件,则传递getSupportFragmentManager(),如果是Fragment的控件中,则应该传递getChildFragmentManager()。只要记住ViewPager内的Fragments是当前组件的子Fragment这个原则即可。
        -----------------------------------------------------------------------------------------------------------------
        继承FragmentPagerAdapter这个类是v4包下,设置参数要求是v4包的FragmentManager,您说的如果ViewPager是Fragment的View,则传递getChildFragmentManager(),但是得到的FragmentManager并不是v4的,程序会报错啊!!!
      • 47fe3088500c:碎片大神 给跪了:pray:
      • 敲代码的鸡:hide以后死活show不出来
      • RabbitL:还有就是我们要不要再fragment和viewPager配合使用的时候在onDestroyView()中去手动的清空当前fragment中的全局变量。。。
        YoKey:要看实际需求了~
      • RabbitL:还有就是fragment和viewPager结合使用的时候,使用fragmentPagerAdapter,销毁的fragment会走onDestroyView()方法,但是它的全局变量会不会被销毁。我怎么感觉有的时候并没有销毁呢
        YoKey:@RabbitL fragmentStatePagerAdapter会销毁实例
        RabbitL:@RabbitL 哦哦是这样的啊~那fragmentStatePagerAdapter呢
        YoKey:fragmentPagerAdapter 只销毁视图 不销毁实例; 全局变量不会被销毁
      • RabbitL:楼主不知道你遇没遇到过这种情况,也是通过hide()方法和show()方法来控制Fragment,但是没有出现fragment重叠,而是只显示一个tab中的fragment内容,无论点击哪个tab
        YoKey:这个倒没遇到过~
      • aedeec4a0b6f:楼主你好。如果第一个fragment没有添加到活动栈,第二个fragment添加到了活动栈,这种情况下再使用singleTask模式启动第一个fragment时就会出问题。因为findStackFragment是能找到第一个活动的,但是popToFix(toFragmentTag, 0, fragmentManager)就会出问题,无法通过fragmentTag回到第一个fragment所在活动栈。
        YoKey:@1307290814 第一个没有加入回退栈吗? 没有加入回退栈的话,出栈需要使用remove,而不是pop系列方法; 官方的popBackStack系列方法其实也是操作回退栈的Fragment的

        可以考虑换种方式来实现,比如建议加入回退栈~
      • dddc45e2e061:楼主你好,在使用fragment+viewpager的时候遇到以下崩溃:
        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已经做了,还是会出现这个问题,请问楼主能否帮忙分析一下这是什么原因?
        Blz:这个是在 commitAllowingStateLoss的时候 先去判断 if(!fragmentManager.isDestroy()){
        // 执行事务
        } 这个吗?
        if(!fragmentManager.isDestroy()){
        // 执行事务
        commitAllowingStateLoss();
        }
        dddc45e2e061:@YoKey 感谢楼主,楼主的文章写的很棒,对我帮助很大
        YoKey:@bugkeeper AC销毁后,执行了Fragment事务

        可能是Fragment在执行异步任务过程中,关闭页面,AC销毁了,然后异步任务回调后执行了Fragment事务;

        解决思路可以在关闭页面时及时取消异步任务;或者做如下判断:
        if(!fragmentManager.isDestroy()){
        // 执行事务
        }
      • SoloHo:能说一下attach和detach的用法吗?
        YoKey: @SoloHo 就是fragment实例 绑定宿主activity 和 解绑宿主activity
      • 龙龙有药:又遇到了fragment 闪屏问题。。。标题栏 第一次切换
      • e50e3ec2fb07:ViewPager 和Fragment连用的时候 有很多个栏目,如网易新闻,怎么缓存Fragemnt ,不销毁,避免每次都加载
        YoKey:@e50e3ec2fb07 FragmentPagerAdapter 这个Adapter只会销毁 Fragment视图 而不销毁Fragment实例,所有使用全局变量保存数据,可以实现你的需求

        还一种设置ViewPager.setOffscreenPageLimit(你的总栏目数量-1) 可以达到一次性加载所有Fragment,并且切换连视图都不销毁,但是非常不建议这种方式
      • 相互交流:楼主如果说,,一个界面有两个FrameLayout ,,,一个是左边的,,一个是右边的,他们的任务栈是怎么分配的,,是在一个栈里面,,还是分开的栈??
        YoKey: @相互交流 在ac里是同一个 fg里取决于你调用哪个方法

        为何要自己实例化…
        相互交流:@YoKey ,在同一个activity 里面,获取到的肯定是同一个,,?FragmentManager这个东西可以自己实例化吗?
        YoKey:@相互交流 取决于 是否是同一个FragmentManager加载的,是同一个FM则在同一层级的栈内,否则不同栈
      • 340170993cda:为什么onFragmentResult方法只执行一次,在打开关闭,就不会执行了啊,大神能帮我解决下吗
        340170993cda:还是要用eventbus,好,谢谢了
        YoKey: @Droidrzy 可以参考下demo DetailFragment
        另外跨fg 跨层级的情况 建议用eventbus
      • 240b50cbfcf3:楼主你好,问个问题,我的app中有一个view的点击事件是打开一个ViewPager+Fragment的Activity,第一次打开还好,退出这个Activity然后再次打开,这样操作两三次app的运行内存就迅速上升到200M了,在低配置的手机很容易出现OOM。这是什么原因导致的啊。
        YoKey:@T摩天倫 检测下哪个地方出现了内存泄漏, 从图片、是否异步任务持有该AC/FG的引用等等来检查~
      • 真的是叫时光啊:楼主,说真的,你给我开启了一扇大门。我以前用Fragment的方式太low了
        真的是叫时光啊:@YoKey 好好好,遇到坑咱们就交流一番 :stuck_out_tongue_winking_eye:
        YoKey:@真的是叫时光啊 大门后面很多坑~ :joy:
      • zubin:- 你好,我在自己在使用 Fragment 的时候出现了些问题想请教下。

        - 现象: 我的 drawerlayout 的抽屉有两个选项A,B,默认选中A。content 是一个 Fragment,里面又嵌套了一个 ViewPager。当我切换到B时,会replace 最外层的 Fragment。但是当我重新切换回 A 的时候,ViewPager 就不能够滑动了,里面的子Fragment 也是白屏

        - 我尝试把 viewpager.setOffscreenPageLimit 设置为2,上面的那种现象就不存在了。我想请问下这个问题到底是什么原因?是缓存造成的么?
        zubin:@YoKey 关于replace 的问题,我看了您那个框架都是始终有一个根Fragment在栈中,后面的fragment 基本都是通过 show/hide 显示的。能问下为什么会采取这种形式呢?是因为更好维护还是什么其他原因?
        zubin:@YoKey 其实是因为 fragment 里面有大量的图片,如果通过add 和 hide 来处理的话内存就会占用比较多。我去看下源码找找解决方法好了,谢谢!
        YoKey:@zubin 原因可能需要你从源码上来找了, replace我用的不是特别多,用的话也不会涉及到replace嵌套的Fragment, 我的认知是应该尽量避免replace嵌套Fragment~
      • 曾经的你呀:如你github 上的demo(类知乎),使用Show,hide 的方式处理Fragment,也可以用viewPager+FragmentPagerAdapter+懒加载实现几乎一致的效果。
        我要请教的问题是:两者之间有什么异同点?性能啊,开发维护成本啊,交互效果啊等等
        YoKey:@iSnowFlake 这个要看个人习惯和产品需求吧,开发上ViewPager比较简单,性能和维护成本感觉无明显区别
      • 9f12c744910c:嗯 看了源码 在instantiateItem这个方法中做了恢复处理;getchildfragmentmanager.getfragments()不会返回空 但是在activity中使用getsupportfragmentmanager.getfragments()会返回空 why?
        9f12c744910c:@YoKey 第一种情况 mainActivity中直接使用viewpager包含三个同级fragment,
        在activity初始化视图后调用getsupportfragment.getfragments()返回null;
        第二种情况 mainActivity中的mainfragment使用viewpager包含三个同级fragment 同样在mainfragment初始化视图后调用getchildfragment.getfragments()返回不为空有3个fragment
        YoKey:@15623601037 不一定的,看实际情况~
        栈视图层级,建议多打印栈看看~
      • 9f12c744910c:你好博主 我在activity中使用viewpager加载三个同级fragment activity销毁重建是否需要我自己来恢复viewpager?
        我这边的思路是:
        在销毁重建的代码块中(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
        YoKey:@15623601037
        ViewPager+FragmentAdapter时,不需要处理子Fragment的销毁重启的情况,原因在FragmentAdapter已经帮助处理了,可以看其源码~
        “getchildfragmentmanager.getfragments()不会返回null”说明起有子Fragment啊 ~
      • woniu0936:我有个问题,activity中现在有两个fragment,A和B(当前显示的是B),这时候我点击一个按钮,做了个操作replace(id, C)【C也是这里新创建的一个fragment】,这时候我调用getSupportFragmentManager().getFragments();拿到所有的fragment,log了一下,发现有三个值,一个是C两个是null。这是为什么?你文章中说使用replace会释放掉资源,为什么还会出现这种情况?
        YoKey:@woniu0936 就是没在回退栈里啊, 你没有调用所以不在回退栈里。
        replace内部有remove,remove对于没有被加入回退栈的Fragment是直接销毁的,所以AB被销毁为null,而对于加入回退栈的仅仅是销毁视图,即走onDestroyView,Fragment实例还在。
        woniu0936:可是A,B我都是直接add的,没有调用addToBackStack(toName);方法,为什么他还会被加入回退栈?
        YoKey:@woniu0936 如果AB没有加入回退栈,replace C后,AB会被销毁实例
        replace内部是remove+add, 而remove对于没有被加入回退栈的Fragment是直接销毁的,而对于加入回退栈的是销毁视图,实例还在。
      • woniu0936:我在你的源码里看到这段代码,if (fromStartWithPop) {
        fragmentManager.popBackStack();
        } else {
        fragmentManager.popBackStackImmediate();
        }
        请问怎么确定在什么情况下使用popBackStack();在什么情况下使用popBackStackImmediate();
      • 5f521029833a:有没有demo可以看看。我在想,会不会是我Fragment里面差了什么东西。
        YoKey: @5f521029833a 你再看下文章的示例代码和说明 应该描述的还算清楚的 :)
      • 5f521029833a:楼主, 我仿着写下面这部分的时候,报空指针异常啊。。
        if (savedInstanceState != null) { // “内存重启”时调用msgFragment = getSupportFragmentManager().findFragmentByTag(msgFragment.getClass().getName);}
      • Ideaqjj:当使用hide方法时,fragment被隐藏后是否还占用内存
        YoKey:@Ideaqjj 占,但是用replace也几乎是一样的,只是replace会释放View相关的内存;2种加载方式Fragment对象都会被FragmentManager保存在内存中的;实际上内存占用差别很小,Fragment本身所占内存也是非常低的
      • 旋转小子V3:楼主您好,我用的第二种方法当内存重启,savedInstanceState !=null的时候msgFragment也为空了,所以在msgFragment.getClass().getName()的时候报错,这里是不是应该保存fragment的tag在savedInstanceState里面而不是直接用msgFragment.getClass().getName()
        旋转小子V3:应该是MsgFragment.class.getName() :smile:
        YoKey:@旋转小子V3 已经修正,再次感谢提醒!!之前竟然一直没发现 =。=
        对了 可以看下我最新的一篇简书,9行代码解决重叠问题,希望能对你有帮助 :blush:
        YoKey:@旋转小子V3 天呐,笔误了!!! 感谢提醒,应该是MsgFragment.class().getName()
        我修改下,感谢提醒!! :pray:
      • yangyirunning:博主您好,看您的博文受益匪浅, 已经应用于实战,但是在实战中,内存重启(旋转屏幕)的情况下,会报出
        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重叠的情况,请问您有遇到过这种问题么?该如何解决?
        YoKey:@东仙轶Primo mNextAnim的这个异常,一般情况下是由于设置了转场动画,而在转场动画还没结束时进行了其他事务操作,然后在页面重启时就可能导致该异常。
        因为不知道你的详细使用场景,建议你先检查下动画相关,重叠问题再看下文章中的2种方案;
        同时推荐可以看看Fragmentation库,现在最新版0.7已经比较完整了,各种嵌套、同级Fragment都可以简单解决,所有的重叠问题库中都帮你解决好了 :smile:
      • woniu0936:求教一个关于rxjava的问题:
        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都打不到想要的效果,请问该怎么做?
        YoKey:@woniu0936 :joy: 嗯对,delay是延迟时间 发射,确实会delay->1->2
        用flatMap吧:.flatMap(s -> Observable.just(s).delay(3, SECONDS)).subscribe();
        woniu0936:@YoKey 使用delay的效果是:delay -> 操作1 -> 操作2,想要的效果是:操作1 -> delay -> 操作2:joy::joy::joy::joy::joy:
        YoKey:@woniu0936 delay不行吗? delay默认指定的computation线程,应该是可以的

        还一种方案,subscribe里postDelay... :joy: 还是用delay吧~
      • woniu0936:楼主你好。我的使用场景是这样的:两个activity(a和b),a中有三个fragment切换,我使用了你的hide()和show()的第二种方案,b是一个一般的activity,我在a的onResume()方法中写了个fragment的切换,现在的问题是我从a跳到b,这时候调用了a的onSaveInstanceState(Bundle outState)方法,然后我按返回键,从b返回a页面,调用a的onResume()方法,切换fragment的时候报错java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState ,解决方法是在切换fragment时,将commit()改为commitAllowingStateLoss(),请问这是什么原因?麻烦楼主有时间了赐教赐教,谢谢谢谢 :pray: :pray:
        woniu0936:@YoKey 非常感谢,暂时没想到避免的方式,我试试放到主线程里面执行吧,再次感谢 :+1:
        YoKey:@woniu0936 回调onSaveInstanceState时,会保存当前Fragment的一些数据和状态,在这之后再去执行pop或Fragment事务操作时,系统已经不能再帮你保存了,就抛出了这个异常;
        commitAllowingStateLoss()是可以避免这个问题,但不是最佳方法,毕竟可能会StateLoss;
        如果是因为onResume方法中的切换导致的话,你可以试试放到主线程Handler.post里执行,或者避免这种实现 :)
      • wjehovah:请问下像单Activity 多fragment 这种,fragment有很多20多个,又需要加入返回,内存开销会很大,这个怎么处理好。谢谢楼主
        defb1f03cad3:作者你好,
        【•如果你在用24.0.0+的版本,需要特殊处理,官方已经修复该BUG;】
        这句话是不是有误,应该是 不需要特殊处理吧
        YoKey:@wjehovah 栈内单例 可以看下我的Fragmentation库,你也可以从那个库的Demo检测 开大量循环Fragment的内存占用情况 相比Activity是很少的 :smile:
        wjehovah:@wjehovah 补充下,能实现Activity singleTask 栈内单例么
      • 勇敢的少年啊:楼主你好,当我使用show hide控制fragment切换时,每次出栈后(pop),栈里剩下的fragments的状态无论是hide还是show都会全部show出来,也会发生fragment重叠的现象,不知楼主有什么好的解决方案吗? :pray:
        YoKey:@勇敢的少年啊 诶 不对=。= 我遇到的特殊情况是全部Fragment显示的是hide状态,而且也很容易解决 :joy:

        你再检查下代码,打印栈内Fragment的log,调试下看看~


        勇敢的少年啊:@YoKey 我认为我可能碰到你说的那种特殊情况了😂
        YoKey:@勇敢的少年啊 首先add新的Fragment的时候要把上一个Fragment隐藏(即hide);

        如果你已经这么做了的话,还出现全部show状态的话(即你说的重叠现象),那就是内存重启后,你没有对栈内Fragment做处理,可以参考我的重叠现象的2种解决方案。主要思路是,获取栈内全部的Fragment,除了你需要show的Fragment之外,其他全部手动hide
        (其实还有另外一种情况会全部show,不过那种情况比较特殊少见,就不做介绍了)
        :smile:
      • Ideaqjj:有个问题咨询一下:因为有些Fragment我要实时刷新网络数据,但是采用hide和show方法切换Fragment的时候不经历生命周期,这时候我要怎么做到实时刷新,也就是切换Fragment时候玩刷新数据?
        Ideaqjj:@YoKey 原来是这样用,学习了,谢谢你!
        YoKey:@Ideaqjj hide/show 回调onHiddenChanged(boolean hidden)方法, 在这里处理就可以了,当hidden为false时,即显示时(比如从下个Fragment返回时),刷新网络数据

        不过要注意,第一次Fragmrnt被加载进来的时候(add),是不会回调onHiddenChanged的,所以在创建Fragment时,需要你在onCreateView或onActivityCreated生命周期里刷新网络数据 :)
      • 空指针tc:我是周末和你聊过回退栈的,我发现我不封装装填view,还是回退有问题。明显调用了pop的fragment的ondestroyview,但是ondestroy没有触发。。。
        YoKey: @3f80311e6ec3 OK 我下午pull下你的项目 争取找到这个奇怪问题的原因 :)
      • __Berial___:话说楼主遇到过 过时的 attach 的 activity 参数不为空,而新的 attach 的 context 的参数为空的情况么?
        c9fae7eb91f6:楼主有QQ或者微信或者其他任何联系方式么,我对fragmention这个框架的使用有些想咨询您的
        YoKey:@__Berial___ 上次记的好像有人也问过这个问题,首先我没遇到,其次看源码:
        在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。

        比较忙,没太深入看,有空我再看看哈,保持对这个异常的关注.... :smile:
        不过看到源码 我们就知道可以放心的使用过时的onAttach(Activity activity)
      • 60503258164d:很不理解为什么android.app.fragment class 去掉了getFragments()方法。。。
        现在情况下是不是最好全都用 android.support.v4.app.fragment class?
        会有什么缺点么?
        苌蓊芪:v4包里的getFragments也不推荐使用了:sweat:
        60503258164d:@YoKey 明白了。还是用V4好,之前认为app.Fragment库相比较V4更官方,所以倾向于用它。但是实际使用发现有很多问题。比如跟其他SDK 不兼容等
        YoKey:@watchforever V4包里的Fragment是作为support库持续维护的,能在各个版本的系统上一样的运行;
        而app.Fragment库相比较V4最大的问题是,不同版本的安卓系统的的Fragment相关类都可能是不同的,而v4里的Fragment就避免了这个问题,所有作为一般的APP,我们都是用v4的 :smile:
      • Tailyou:报如下错误:
        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)
      • d9cb7a76133e:楼主您好,看过这个关于fragment的文章,但是使用时候还是出现了我无法解决的问题,望楼主指点一二:我的MainActivity中报这个错误java.lang.NullPointerException: Attempt to write to field 'int android.support.v4.app.Fragment.mNextAnim' on a null object reference.....因为内存重启崩溃的
        YoKey:@d9cb7a76133e 嗯 动画问题引起的,原因主要是因为在动画还没结束就进行了其他的FragmentManager事务的操作;大致解决思路有2种:
        1、在当前这种情况下,动画临时取消掉;
        2、postDelay动画的时间再进行事务操作 :)
      • Tailyou:1、按Home键,程序在后台运行,如播放音频;
        2、播放完执行pop()方法
        3、再打开程序,Fragment并没有出栈,而是从头开始播放
        求解
        YoKey:@Tailyou 在后台的情况 执行Fragment事务或者pop 是会抛出 IllegalStateException: Can not perform this action after onSaveInstanceState 这个异常的,原因在于你按home键回到桌面时,系统已经调用了onSaveInstanceState帮你保存当前页面的一些数据和状态,在后台再去执行pop/事务操作时,系统已经不能再帮你保存了,就抛出了这个异常;
        解决这个问题的话:应该避免这种设计 或者 在返回时onResume时再pop :joy:
        Tailyou:@YoKey 对,总结的很到位,就是在后台执行pop无效, :smile:
        YoKey:@Tailyou 场景描述有点模糊 ~ 是在后台执行pop后无效是吗? 每次都是这个情况吗?
      • 7d420af06ff4:楼主 你在用setArguments 的时候 遇到 Fragment already active 是怎么处理的
        YoKey:@伽利略听京剧 这个异常是因为你的Fragment已经通过FragmentManager加载到栈内后你再次去调用setArguments()导致的,setArguments()应该在FragmentManger的事务之前去调用传递参数,一旦被加载到Fragment后,再想传递参数,你可以使用EventBus或者给Fragment添加相应的set方法 :)
      • 爵小友:可能我使用的fragment太简单了 :joy: 发现很多并不需要用到,但是学习了!
        56e3460f3c03:@爵小友 。。。。我也是 ,没搞过这么深,我是直接replace的
      • sugaryaruan:深度好文,赞!结合以前的使用感受,收获良多。现面临一个选择,app主页通过fm自己手动add,hide管理切换,还是选择通过现成的控件FragmentTabhost来组织app主页,不知道对此,你有什么看法和建议?
        e6da7f5b5d47:官方的FragmentTabHost是add/detach操作,我不会告诉你都被我改为show/hide了 :stuck_out_tongue_closed_eyes:
        sugaryaruan:@YoKey 赞,谢谢回复,近期也在了解FragmentTabHost。
        YoKey:@风一般得 我一直是使用自己简单封装的类似FragmentTabHost的组件,因为用习惯啦 :stuck_out_tongue_closed_eyes:
        不过我想官方的FragmentTabHost应该已经为我们处理了一些坑点,所有可以考虑使用这个。(PS:回头我也会研究下这个控件)
      • 轻微:而新的Fragment在创建时是不会毁掉onHiddenChanged() .毁掉--回调
        YoKey:@轻微 :cold_sweat: ....原来是说文章中的错别字~~ :joy: ~ 感谢提醒
        轻微:@YoKeyword 嗯. 错别字快去改.
        YoKey:@轻微 是的 :smile: 除非你hide()会回调, 而在创建事务时主动show()仍然不会回调
      • 浮华染流年:APP在后台回收后,就成空进程了,当APP重启,FM里面什么都没有吧....
        YoKey:@cb245848f4e2 因资源不足被回收后,FM会帮你保存Fragment,你可以测试下,打印FragmetManager.getFragments(),里面保存有app被回收前的Fragment。 :)
      • stay4it:这么深入的文章居然没人评论。。。。
        我一直不明白一个方法setRetainInstance,按我的理解,设为false,就应该不随着activity的重启而重启才对啊。但实际上不是这样。既然fragment依附于activity,那就让activity全部初始化不好么,非得通过FragmentManager把丢失的对象再找回来,重新new一个不是一样的么。

        另外,内存重启,个人觉得还是重走app流程的好,避免引用一些static变量导致空指针。如果只是某个activity被杀了,通过onRestore找回还可以接受。但要是整个app都被强杀掉了,真没必要还去做额外的容错。
        defb1f03cad3:谜一样的男子
        YoKey:@stay4it 啊! Activity被重启而app不被杀是有的~ 在屏幕旋转,语言切换等情况~
        :joy: 光想着内存重启这种情况了~ :stuck_out_tongue_winking_eye:
        YoKey:@stay4it FragmentManager保存Fragment的意义,和安卓保存被强杀的Activity是出于同一个目的:恢复到被强杀的app状态;如果被强杀后,都是重走流程,体验很差,比如你接个电话、拍个照,返回后,app被强杀了,又从Splash页面走起,会抓狂的。
        内存重启的情况,尽量还是通过onSaveInstanceState保存 再恢复比较合适;很多中低端安卓手机内存重启发生的频率非常高,如果重走app,体验很不好;通过合理的恢复,把需要保存的变量保存起来,避免一些空指针的问题
        内存重启必然意味着app进程被强杀的,重新打开app时,Application类首先重走onCreate,然后恢复被强杀前的那个Activity。并没有单个Activity被杀,而app没被杀的情况的 :)

      本文标题:Fragment全解析系列(二):正确的使用姿势

      本文链接:https://www.haomeiwen.com/subject/vxjwkttx.html