嵌套Fragment的使用及常见错误

嵌套Fragments (Nested Fragments), 是在Fragment内部又添加Fragment.

使用时, 主要要依靠宿主Fragment的 getChildFragmentManager() 来获取FragmentManger.

虽然看起来和在activity中添加fragment差不多, 但因为fragment生命周期及管理恢复模式不同, 其中有一些需要特别注意的地方.

本文内容还包括了从Fragment迁移到v4.Fragment代码中需要改动的一些地方.

嵌套Fragments

嵌套Fragments Nested Fragments 是Android 4.2 API 17 引入的.

目的: 进一步增强动态复用.

如果要在Android 4.2之前使用, 可以用support library v4的版本, 后面会有详细的迁移过程介绍.

嵌套Fragment的动态添加

在宿主fragment里调用getChildFragmentManager()

即可用它来向这个fragment内部添加fragments.

Fragment videoFragment = new VideoPlayerFragment();
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.add(R.id.video_fragment, videoFragment).commit();

同样, 对于内部的fragment来说, getParentFragment() 方法可以获取到fragment的宿主fragment.

getChildFragmentManager() 和 getFragmentManager()

getChildFragmentManager()是fragment中的方法, 返回的是管理当前fragment内部子fragments的manager.

getFragmentManager()在activity和fragment中都有.

在activity中, 如果用的是v4 support库, 方法应该用getSupportFragmentManager(), 返回的是管理activity中fragments的manager.

在fragment中, 还叫getFragmentManager(), 返回的是把自己加进来的那个manager.

也即, 如果fragment在activity中, fragment.getFragmentManager()得到的是activity中管理fragments的那个manager.

如果fragment是嵌套在另一个fragment中, fragment.getFragmentManager()得到的是它的parent的getChildFragmentManager().

总结就是: getFragmentManager()是本级别管理者, getChildFragmentManager()是下一级别管理者.

这实际上是一个树形管理结构.

使用Support library

为什么要使用support library? 有两种原因:

  1. 要在API level11之前使用fragment.
  2. 要在API Level 17之前使用getChildFragmentManager(), 即使用嵌套Fragment.

迁移到support library需要改动哪些地方?

把Fragment迁移到v4版本, 需要改动如下地方:

import android.app.Fragment; -> import android.support.v4.app.Fragment;
Activity -> FragmentActivity / AppCompatActivity
activity.getFragmentManager() -> getSupportFragmentManager() Loader, LoaderManager, LoaderCursor也需要改成v4包的.
activity.getLoaderManager() -> getSupportLoaderManager()

Fragment中onTrimMemory()方法不见了

以前是这个方法

    @Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
imageLoader.trimMemory(level);
}

v4版本需要改成这个

   @Override
public void onLowMemory() {
super.onLowMemory();
imageLoader.trimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE);
}

嵌套Fragment使用常见错误

错误情形1: 把嵌套Fragment放在布局里

把嵌套Fragment放在布局里 -> InflateException in Binary XML

看起来嵌套fragment的使用除了要用getChildFragmentManager()以外, 其他跟之前似乎没什么区别.

如果嵌套的fragment不需要太多控制, 固定地占据了一块地方, 你可能想当然地为了省事就把它放进了xml布局文件里, 写个标签.

运行一下初看起来似乎没什么错, run一下也能显示出来, 但是千万不要这样做, 多玩两下更复杂的你就知道了.

上面官网介绍时就有这么一句:

Note: You cannot inflate a layout into a fragment when that layout includes a <fragment>.
Nested fragments are only supported when added to a fragment dynamically.

人家这么说肯定是有原因的哇, 下面我来告诉你我知道的问题:

如果Fragment被嵌套写在了布局里, inflate到这个标签的时候就相当于将它加进了FragmentManager里.

如果嵌套的parent fragment因为需要重建View而重新走了onCreateView()方法, 再次inflate, 此时就会抛出异常: InflateException in Binary XML

之前为什么可以呢? 非嵌套的情况, fragment直接加在activity里, 如果需要重新inflate, 必定是在onCreate()里, activity是重新建的, 所以没有问题, 因为不存在fragmentManager中已经持有同一个fragment的问题.

举一个例子:

在嵌套的情况下, 如果FragmentE布局里有FragmentA, 这时候我们需要叠加一个FragmentD.

用了replace(), 并且addToBackStack().

当D显示的时候, E实际上View是被销毁的, 然后back回来, 重建View, 即FragementE需要重新从onCreateView

()开始走生命周期, 走到inflate的时候又看到了fragmentA的标签.

但是这时候A实际上还在FragmentManager里面, 所以就会抛出如下的异常:

android.view.InflateException: Binary XML file line # XX: Binary XML file line #XX: Error inflating class fragment

崩溃的位置就在parent fragment(FragmentE) inflate的时候.

打印具体的异常栈信息可以看到:

at com.example.ddmeng.helloactivityandfragment.fragment.FragmentE.onCreateView(FragmentE.java:35)
at android.app.Fragment.performCreateView(Fragment.java:2220)
at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:973)
at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1148)
at android.app.FragmentManagerImpl.popBackStackState(FragmentManager.java:1587)
at android.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:578)
at android.support.v4.app.BaseFragmentActivityEclair.onBackPressedNotHandled(BaseFragmentActivityEclair.java:27)
at android.support.v4.app.FragmentActivity.onBackPressed(FragmentActivity.java:189)
Caused by: java.lang.IllegalArgumentException: Binary XML file line #16: Duplicate id 0x7f0c0059, tag null, or parent id 0xffffffff with another fragment for com.example.ddmeng.helloactivityandfragment.fragment.FragmentA
at android.app.FragmentManagerImpl.onCreateView(FragmentManager.java:2205)

实验例子代码

Solution 1: 动态添加child fragment

解决上面的问题有各种方法, 最常规的做法是, 使用动态添加:

Fragment fragmentA = getChildFragmentManager().findFragmentByTag(NESTED_FRAGMENT_TAG);
if (fragmentA == null) {
Log.i(LOG_TAG, "add new FragmentA !!");
fragmentA = new FragmentA();
FragmentTransaction fragmentTransaction = getChildFragmentManager().beginTransaction();
fragmentTransaction.add(R.id.fragment_container, fragmentA, NESTED_FRAGMENT_TAG).commit();
} else {
Log.i(LOG_TAG, "found existing FragmentA, no need to add it again !!");
}

Solution 2: 在异常之前remove child fragment

如果你的子fragment非要加在布局里不可, 而你的程序确实会有重建父fragment view的情形.

为了避免上面的异常, 你也可以这样做(tricky and not recommended):

public void removeChildFragment(Fragment parentFragment) {
FragmentManager fragmentManager = parentFragment.getChildFragmentManager();
Fragment child = fragmentManager.findFragmentById(R.id.child);
if (child != null) {
fragmentManager.beginTransaction()
.remove(child)
.commitAllowingStateLoss();
}
}

在parentFragment的onCreateView()方法中inflate之前和onSaveInstanceState()方法中做save工作之前调用它.

这两个地方是发生异常的地方, 只要在其之前remove就好.

错误情形2: 把fragment放在一个动态布局里

把fragment放在一个动态布局里 -> java.lang.IllegalArgumentException: No view found for id

发现这个错误是因为项目中的一个子Fragment是添加在RecyclerView里面的一块的.

RecyclerView要等到Loader的数据取到了之后再populate每一块的布局.

还是上面的流程, 启动父fragment, load数据, 添加子fragment, 这都没有问题.

但是一旦如果是上面的replace()addToBackStack() , 并且再次返回, 就会出现异常.

因为当重建View的时候, fragmentManager其中是持有child fragment的, 但是找不到它的container, 于是就会抛出异常.

我也同样做了一个小实验, 在我的demo程序里:

HelloActivityAndFragment

Nested Fragment in Dynamic Container:

在Fragment F中, 先添加一个FrameLayout, 再把child fragment A加进去.

然后在Activity中, 用D replace F, 按back键返回, 就会有crash:

     java.lang.IllegalArgumentException: No view found for id 0x7f0c0062 (com.example.ddmeng.helloactivityandfragment:id/frame_container) for fragment FragmentA{b37763 #0 id=0x7f0c0062 FragmentA}
at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:965)
at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1148)
at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1130)
at android.app.FragmentManagerImpl.dispatchActivityCreated(FragmentManager.java:1953)
at android.app.Fragment.performActivityCreated(Fragment.java:2234)
at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:992)
at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1148)
at android.app.BackStackRecord.popFromBackStack(BackStackRecord.java:1670)
at android.app.FragmentManagerImpl.popBackStackState(FragmentManager.java:1587)
at android.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:578)
at android.app.Activity.onBackPressed(Activity.java:2503)

这是因为返回的时候FragmentManager找不到对应的container了.

所以应该避免这种做法, 尽量把fragment加进parent的根布局里, 而不是某个动态添加的布局.

其他

关于嵌套fragments的情况, 可能和ViewPager结合使用的情形比较多.

这个感觉说来话长了, 以为有很多系统帮忙做的事情, 改天有空再说吧.

这里有个大哥写了个工具类Fragmentation.

他也有几篇博文分析遇到的坑和原因(见上面repo的README给出的链接), 里面有一些back stack的问题, 还有动画什么的, 大家有兴趣可以看看.

参考资料

Guide: Nested Fragments

相关Demo

本文地址:

Android Fragment使用(二) 嵌套Fragments (Nested Fragments) 的使用及常见错误

Android Fragment使用(二) 嵌套Fragments (Nested Fragments) 的使用及常见错误的更多相关文章

  1. Android Fragment(二)

    废话:在上一篇的博客中我们给出了Fragment的简单介绍,这一片博客给大家介绍一下Fragment到底该怎样用.主要都用在哪方面等等. 需求:现有一个界面,要求,竖屏时界面的背景颜色为红色,横屏时界 ...

  2. Android - Fragment(二)加载Fragment

    Fragment加载方法 加载方法有两种,在xml文件中注册,或者是在Java代码中加载. xml中注册 例如在fragment_demo.xml中定义 <?xml version=" ...

  3. JAVA环境下利用solrj二次开发SOlR搜索的环境部署常见错误

    问题一:出现控制台坏的响应错误一Bad request 控制台出现错误如下: Bad Request request: http://hostIP:8983/solr/update?wt=javabi ...

  4. Android Fragment使用(四) Toolbar使用及Fragment中的Toolbar处理

    Toolbar作为ActionBar使用介绍 本文介绍了在Android中将Toolbar作为ActionBar使用的方法. 并且介绍了在Fragment和嵌套Fragment中使用Toolbar作为 ...

  5. 浅谈Android Fragment嵌套使用存在的一些BUG以及解决方法

    时间 2014-03-18 18:00:55 eoe博客 原文  http://my.eoe.cn/916054/archive/24053.html 主题 安卓开发 自从Android3.0引入了F ...

  6. Android系列之Fragment(二)----Fragment的生命周期和返回栈

    ​[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/ ...

  7. android开发 Fragment嵌套调用常见错误

    在activity中有时须要嵌套调用fragment,但嵌套调用往往带来视图的显示与预期的不一样或是fragment的切换有问题.在使用时要注意几点: 1.fragment中嵌套fragment,子f ...

  8. Android之Fragment(二)

    本文主要内容 如何管理Fragment回退栈 Fragment如何与Activity交互 Fragment与Activity交互的最佳实践 没有视图的Fragment的用处 使用Fragment创建对 ...

  9. Android Fragment使用(一) 基础篇 温故知新

    Fragment使用的基本知识点总结, 包括Fragment的添加, 参数传递和通信, 生命周期和各种操作. Fragment使用基础 Fragment添加 方法一: 布局里的标签 标识符: tag, ...

随机推荐

  1. 掌握 Cinder 的设计思想 - 每天5分钟玩转 OpenStack(46)

    上一节介绍了 Cinder 的架构,这节讨论 Cinder 个组件如何协同工作及其设计思想. 从 volume 创建流程看 cinder-* 子服务如何协同工作 对于 Cinder 学习来说,Volu ...

  2. 借助node实战WebSocket

    一.WebSocket概述 WebSocket协议,是建立在TCP协议上的,而非HTTP协议. 如下: ws://127.0.0.1或wss://127.0.0.1就是WebSocket请求. 注:w ...

  3. Ajax JQuery HTML 提交上传文件File HTML+ Ajax+ASP.NET+ WebService

    起因:公司最近有些项目用到了HTML+WebService的组合,发现访问速度等都快了许多,但是由于通过Ajax只能应付一些简单的文字类的传输,上传文件就捉襟见肘了,如果一直引用第三方的swf之类上传 ...

  4. Apache Sqoop - Overview——Sqoop 概述

    Apache Sqoop - Overview Apache Sqoop 概述 使用Hadoop来分析和处理数据需要将数据加载到集群中并且将它和企业生产数据库中的其他数据进行结合处理.从生产系统加载大 ...

  5. 用jekyll制作高大上的网站(二)——实际应用

    最近公司要制作个文档库,直接就可以将jekyll应用到实际中. 模版使用了Jekyll Clean,这么模版相对内部简单一点,学习成本不会很大,而复杂的Minimal Mistakes就当作参考. 模 ...

  6. hibernate笔记--组件映射方法

    假设我们需要保存学生student的信息,student中有一个address属性,我们知道像这种信息其值可能会有多个,就像一个人会有两个以上的手机号,这种情况在hibernate中应该这样配置: 新 ...

  7. 跟我学PHP第二篇- 配置Mysql以及PHP WampServer篇(1)

    大家好,昨天我给大家介绍了如何去安装ZEND STUDIO,下面昨天文章的链接: http://www.cnblogs.com/kmsfan/p/zendStudio.html 本节为配置的第一部分, ...

  8. Angularjs promise对象解析

    1.先来看一段Demo,看完这个demo你可以思考下如果使用$.ajax如何处理同样的逻辑,使用ng的promise有何优势? var ngApp=angular.module('ngApp',[]) ...

  9. 移动端IM系统的协议选型:UDP还是TCP?

    1.前言 对于有过网络编程经验的开发者来说,使用何种数据传输层协议来实现数据的通信,是个非常基础的问题,它涉及到你的第一行代码该如何编写. 从PC时代的IM开始,IM开发者就在为数据传输协议的选型争论 ...

  10. struct 大小计算

    结构体是一种复合数据类型,通常编译器会自动的进行其成员变量的对齐,已提高数据存取的效率.在默认情况下,编译器为结构体的成员按照自然对齐(natural alignment)条方式分配存储空间,各个成员 ...