Fragment 使用及与 Activity 的关系

Fragment 到底是什么

Fragment 的发明是为了灵活的布局以及更好的复用布局,尤其是在较大显示器上,比如 A/B 两个 Fragment,A 表示新闻列表,B 表示新闻详情,在 Pad 上,可以一个 Activity 左边是 A,右边是 B,而在手机上,可以搞两个 Activity,一个放 A,另一个放 B,这样 A、B 都是复用的,通过动态改变 Activity 中的 Fragment ,高效的实现了在大小屏幕上的不同布局设计。【注:如果是想在横竖平时自动切换布局,可以将Activity 的布局文件放置在 res/layout-land 和 res/layout 中来完成】

Fragment 被定义为 Activity(FragmentActivity)的一部分,一个 Activity 可以包含多个Fragment 从而构建多窗格界面;虽然 Fragment 没有继承 View,但是完全可以把它视为“视图”,之所以说可以视为视图,是因为他可以被引用在 xml 中、直接被添加到 Activity 的某个 ViewGroup 中等,他的一个重要方法是onCreateView,用来返回整个 Fragment 的根 View,系统会在合适的时机调用该方法,并将返回的 View 插入到 Activity 的布局中。

当然,它不是继承自 View,也就与 View 有所不同,首先是 Fragment 具有完整的生命周期,其生命周期直接受宿主 Activity 生命周期影响。二是Fragment 可以参与到“返回栈”中,用户有时会感觉像返回到上一个页面;

一句话总结:Fragment 是具有类似于 Activity 生命周期和返回栈的 ViewGroup

将 Fragment 添加到 Activity

有两种方式可以实现将 Fragment 添加到 Activity 中

一是直接在 Activity 的布局中声明:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:orientation="horizontal"

    android:layout_width="match_parent"

    android:layout_height="match_parent">

    <fragment android:name="com.example.news.ArticleListFragment"

            android:id="@+id/list"

            android:layout_weight="1"

            android:layout_width="0dp"

            android:layout_height="match_parent" />

    <fragment android:name="com.example.news.ArticleReaderFragment"

            android:id="@+id/viewer"

            android:layout_weight="2"

            android:layout_width="0dp"

            android:layout_height="match_parent" />

</LinearLayout>

创建此 Activity 布局时,系统会将布局中指定的每个片段实例化,并为每个片段调用 onCreateView() 方法,以检索每个片段的布局。系统会直接插入片段返回的 View,从而代替 <fragment> 元素。

二是通过编程方式将 Fragment 添加到某个现有 ViewGroup

在 Activity 运行期间,可以随时将片段添加到 Activity 布局中。只需指定要将片段放入哪个 ViewGroup。要执行此操作,必须使用 FragmentTransaction 中的 API,可以从 FragmentActivity 获取一个 FragmentTransaction 实例,然后使用 add() 方法添加一个片段,指定要添加的片段以及将其插入哪个视图。

    val fragmentManager = supportFragmentManager

    val fragmentTransaction = fragmentManager.beginTransaction()

    val fragment = ExampleFragment()

    fragmentTransaction.add(R.id.fragment_container, fragment)

    fragmentTransaction.commit()

传递到 add() 的第一个参数是 ViewGroup,即应放置片段的位置,由资源 ID 指定,第二个参数是要添加的 Fragment。一旦您通过 FragmentTransaction 做出了更改,就必须调用 commit() 以使更改生效。

显然,第二种方法相较于第一种具有更好的灵活性。

管理 Fragment

    Fragment 比普通 ViewGroup 具有更好的灵活性,可以很方便的动态的从 Activity 添加和移除,为了方便管理,如你所见,系统提供了 FragmentManager 来管理 Fragment,可以从 Activity 中(只有 FragmentActivity 才有该方法,所以要使用 Fragment,则宿主 Activity 必须是FragmentActivity)通过 getSupportFragmentManager 获得;

    在 Activity 中使用片段的一大优点是,您可以通过片段执行添加、移除、替换以及其他操作,从而响应用户交互。提交给 Activity 的每组更改均称为事务,并且您可使用 FragmentTransaction 中的 API 来执行一项事务。您也可将每个事务保存到由 Activity 管理的返回栈内,从而让用户能够回退片段更改(类似于回退 Activity)。可以提交一组事务,然后通过 commit 应用到 Activity 中。

Fragment 与 Activity 通信

在 Fragment 获取 Activity:getActivity()

在 Activity 获取 Fragment:supportFragmentManager.findFragmentById 或 supportFragmentManager.findFragmentByTag

    Fragment 与该 Activity 内的其他 Fragment 通信有三种方式:1、使用 ViewModel;2、通过 Activity,即在 Fragment 中定义接口,Activity 实现该接口,收到消息后转发给另一个 Fragment;3、通过 EventBus 等第三方库;

从代码的角度查看 Fragment 与 Activity 的关系

Activity、FragmentManager依赖关系

因为管理 Fragment 使用 FragmentManager,就从 getSupportFragmentManager 方法看起,在 FragmentActivity 中 getSupportFragmentManager 方法调用了 mFragments.getSupportFragmentManager(),是通过 mFragments 调用,mFragments 很重要,能发现 FragmentActivity 中关于 Fragment 的调用几乎都是通过 mFragments 来完成的,所以要看一下 mFragments 究竟是什么。

mFragments 是 FragmentActivity 的成员变量,在对象创建时就已经初始化:

final FragmentController mFragments = FragmentController.createController(new HostCallbacks());

可见,mFragments 是 FragmentController 的对象,其中 FragmentController 的部分代码如下

public class FragmentController {

    private final FragmentHostCallback<?> mHost;

    public static FragmentController createController(FragmentHostCallback<?> callbacks) {

        return new FragmentController(callbacks);

    }

    private FragmentController(FragmentHostCallback<?> callbacks) {

        mHost = callbacks;

    }

    public FragmentManager getSupportFragmentManager() {

        return mHost.getFragmentManagerImpl();

    }

    可见,创建对象的关键一步是给成员变量 mHost 赋值,类似于上一步,在 FragmentController 中的很多操作,又都委托给 mHost 处理,所以还要搞清楚 mHost 是什么,mHost 显然就是 FragmentActivity 传进来的 HostCallbacks 对象,HostCallbacks 继承自 FragmentHostCallback,因此在 FragmentController 中 mHost 被定义为 FragmentHostCallback 对象。FragmentHostCallback 是一个抽象类,它的主要目的是统一 Fragment 宿主,如何理解?就是Fragment 可以被添加到很多宿主上,不仅仅是 FragmentActivity,也可以是普通 Activity,宿主内部需要实现 FragmentHostCallback(如上,HostCallbacks 就是 FragmentActivity 的内部类,实现了 FragmentHostCallback),来完成一系列方法,正式因为 FragmentActivity 默认干了这个,才造成了 Fragment 只能在 FragmentActivity 内使用,而不能在普通 Activity 使用的假象。内部有几个重要的成员变量和方法:

public abstract class FragmentHostCallback<E> extends FragmentContainer {

    @Nullable private final Activity mActivity;

    @NonNull private final Context mContext;

    @NonNull private final Handler mHandler;

    private final int mWindowAnimations;

    final FragmentManagerImpl mFragmentManager = new FragmentManagerImpl();

    1、先说一下前两个,在 Fragment 中调用 getActivity()、getContext 返回的正是这两个成员变量。这两个成员变量在 FragmentHostCallback 构造函数中被赋值,虽然 FragmentActivity 只调用了 HostCallbacks 的无参构造函数创建对象,但是该无参构造函数调用了 super(即 FragmentHostCallback)的有参构造函数,传入的activity、context 都是 FragmentActivity 本身。

    再看下 mFragmentManager,它是 FragmentManagerImpl 的对象,显然,是 FragmentManager 的具体实现类,里面就包含了addFragment、moveFragment、findFragmentByTag 等等方法,具体实现就在这里!!

    2、重要的方法包括 onGetLayoutInflater、onStartActivityFromFragment、onFindViewById、onAttachFragment

因为 FragmentActivity 的内部类 HostCallbacks 实现了 FragmentHostCallback,所以这些方法基本都在 FragmentActivity 的这个内部类里面重写,简单摘取两个方法看下:

        public HostCallbacks() {

            super(FragmentActivity.this /*fragmentActivity*/);// 构造时调用父类构造函数,将 FragmentActivity 传入,所以 mHost.getActivity(也包括 getContext) 获取的便是该 FragmentActivity

        }

        @Override

        public LayoutInflater onGetLayoutInflater() {

            return FragmentActivity.this.getLayoutInflater().cloneInContext(FragmentActivity.this);

        }

        @Override

        public void onAttachFragment(Fragment fragment) {

            FragmentActivity.this.onAttachFragment(fragment);

        }

        @Nullable

        @Override

        public View onFindViewById(int id) {

            return FragmentActivity.this.findViewById(id);

        }

小结

先看引用关系:FragmentActivity -> FragmentController -> FragmentHostCallback -> FragmentManager

过程:FragmentActivity 的内部类实现了 FragmentHostCallback,FragmentActivity 创建对象时便初始化 FragmentController 类型的成员变量(传入了内部类 HostCallbacks 实例),FragmentController 类似于门面模式,对 FragmentActivity 提供统一的调用方式,屏蔽了内部实现细节,其实内部主要是通过 FragmentHostCallback 类型的成员变量(其实是 FragmentActivity 传进来的),该类中就包含了 Context、FragmentManager 等重要成员变量,

onCreate 过程

    FragmentActivity 的 onCreate 方法第一行便是 mFragments.attachHost(null /*parent*/);刚方法在 FragmentController 中的实现为 mHost.mFragmentManager.attachController( mHost, mHost /*container*/, parent);即调用了 FragmentManager 的attachController 方法,代码如下:

    public void attachController(FragmentHostCallback host,

            FragmentContainer container, Fragment parent) {

        if (mHost != null) throw new IllegalStateException("Already attached");

        mHost = host;

        mContainer = container;

        mParent = parent;

    }

    显然是在对 FragmentManager 中的重要变量赋值,注意,FragmentManager 里面也持有 mHost,跟上面讲的 mHost 是一个东西。在 FragmentActivity onCreate 最后一行,调用了 mFragments.dispatchCreate();传递到 FragmentManager 这层,主要就是调用 moveToState 相关方法,这个方法(有几个人重载)很重要,负责了 Fragment 状态(生命周期等)的管理,逻辑也相当的负责,只捡几个重要的说:

    一是在创建 Fragment 时对Fragment 重要的成员变量:mHost、mParentFragment、mFragmentManager 赋值,看,Fragment 中也持有 mHost、mFragmentManager,在 Fragment 中也持有该 mHost,Fragment 中很多方法就是通过 mHost 完成的,比如常用的 getActivity,就是调用的 mHost.getActivity,通过上一小节的分析可以知道,获取到的就是 FragmentActivity。

    Fragment 定义的状态主要有INITIALIZING、CREATED、RESUMED等等,FragmentManager 中就是根据不同的状态,来完成 Fragment 类的初始化、生命周期的执行等;这里,我们重点看一下 Create 阶段,在 switch case Fragment.CREATED: 分支里可以找到下面的代码逻辑:

                            // 下面的 f 就是 Fragment

                            ...

                            //container 就是 Activity 中设置的根 ViewGroup,至于 f.mContainerId 的赋值过程,后面再讲 container 的来历

                            container = (ViewGroup) mContainer.onFindViewById(f.mContainerId);

                            ...

                            f.mContainer = container;

                            // 调用 Fragment 的 performCreateView 方法,该方法中就调到了 onCreateView,并且赋值给 Fragment 的 mView。

                            // 这里的三个参数,正是 Fragment 的 onCreateView 需要的三个参数,其中 container 就是 Fragment 要被添加到的 ViewGroup

                            f.performCreateView(f.performGetLayoutInflater(f.mSavedFragmentState), container, f.mSavedFragmentState);

                            if (f.mView != null) {

                                f.mInnerView = f.mView;

                                f.mView.setSaveFromParentEnabled(false);

                                if (container != null) {

                                    container.addView(f.mView);//就在这里,将 Fragment 的布局 View 添加到 Activity 设置的某个 ViewGroup 中;

                                }

                                if (f.mHidden) {

                                    f.mView.setVisibility(View.GONE);

                                }

                                f.onViewCreated(f.mView, f.mSavedFragmentState);// 调用 Fragment onViewCreated 生命周期

                                dispatchOnFragmentViewCreated(f, f.mView, f.mSavedFragmentState, false);// 分发该生命周期,以执行该生命周期所需要做的事情

                            } else {

    代码已经注释的比较详细了,不再赘述,这里看下第一行提到的 contaner,这个看上去是承载 Fragment 的根 ViewGroup,那么它是在哪里被赋值的呢,这里可以从 Fragment 被添加到 Activity 的两种方式入手:

1、通过xml 的方式添加

xml 中的 fragment 标签稍显奇怪,因为 Fragment 并没有继承 View,具体初始化和添加过程还没仔细看,下面是我的猜测:FramentManagerImpl 实现了 LayoutInflater.Factory2 接口,在实现方法 onCreateView 中一上来便有这样一个判断

if (!"fragment".equals(name)) {

            return null;

        }

所以我认为,这是在判断标签名称,如果是 fragment 标签,才继续后面的操作,如果猜测成立,该方法中便能看到 Fragment 的根 View 相关的赋值操作;

2、通过 fragmentTransaction.add(R.id.fragment_container, fragment) 添加 Fragment,这个简单,很容易就能追踪到:fragment.mContainerId = containerViewId; 可见将 Activity 中的 ViewGroup 的 Id 保存在了 Fragment 中,所以在上段代码的第一行,就是根据该 Id,调用到 mContainer(其实就是 mHost)的 onFindViewById 方法,该方法其实调到了 FragmentActivity 的 findViewById 方法,自然就是获取了 Activity 中要添加的 Fragment 那个ViewGroup 了。

FragmentManager 对于 Fragment 的管理

FragmentManager 中含有好多的成员变量,重要的包括:

final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory2 {

    final ArrayList<Fragment> mAdded = new ArrayList<>();// 使用 List 保存已经添加进来的所有 Fragment

    SparseArray<Fragment> mActive;// 保存当前活跃的 Fragment

    ArrayList<BackStackRecord> mBackStack;

    int mCurState = Fragment.INITIALIZING;// 当前状态

    FragmentHostCallback mHost;// 上面有提到

    FragmentContainer mContainer;

    Fragment mParent;

上文也提到,FragmentManager 中维护着 Fragment 的状态,根据状态的推移触发生命周期及其他操作,逻辑比较复杂,不再详细说明。

小结

Fragment 的生命周期都是通过 FragmentActivity 进行转发的。还是像开头说的那样,Fragment 就可以看成是具有生命周期的 View,将自己的 View 添加到 Activity 的布局中,Fragment 只是 View 的承载者,方便对 View 进行控制、并能承载生命周期等。

实验

通过两个小实验,实际动手验证一下我们的结论

1、通过打印出 Activity 中所有的 View,验证 Fragment 的 View 是直接被添加到 Activity 的布局中;

结论:验证通过

2、当拿到 Activity 实例后,如何获取 Activity 上所有 Fragment 实例?

kotlin示例:fragmentActivity.supportFragmentManager.fragments

参考

官方文档:https://developer.android.com/guide/components/fragments

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260