Android fragment 标签加载过程分析

本文概述

在上一篇文章中我们介绍了 AsyncLayoutInflater 使用的注意事项及改进方案。

建议先回顾下之前五篇文章,这个系列的文章从前往后顺序看最佳:

  • 《Android setContentView 源码解析》;
  • 《Android LayoutInflater 源码解析》;
  • 《Android LayoutInflater Factory 源码解析》;
  • 《Android AsyncLayoutInflater 源码解析》;
  • 《AsyncLayoutInflater 使用的注意事项及改进方案》

本篇文章我们来学习下 layout 中 fragment 标签的加载过程,本文基于 Android 8.1.0。

1、铺垫

各位老司机肯定对 Fragment 的使用都非常熟悉,我们简单回顾下:Fragment 的添加方式有两种:静态添加和动态添加。而静态添加就是在布局中写上 Fragment 的相关引用,如下:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@+id/fragment"
        android:name="com.example.MainFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</FrameLayout>

这个 layout 文件是相对特殊的,因为这个 fragment 标签不是很常见,而且大家回忆下 LayoutInflater 的 inflate 流程,其中 inflate 方法的返回值是 View。而我们看下 Fragment 的定义:

public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener {
}

可以看到 Fragment 并不是一个 View,那说明 fragment 标签就不是通过正常的反射来创建的,进一步说就是 fragment 标签的创建和普通的 view 不是一个流程。

2、思考

问题:既然 fragment 标签的创建和普通的 view 不是一个流程,那 fragment 标签是怎么加载的呢?

首先我们想下前提条件:fragment 标签仍然是处于布局文件中的。就是说 fragment 标签节点也会被 LayoutInflater 解析,只是被解析之后的流程和别的 view 不一样了。一路跟踪流程,我们来到了 LayoutInflater 的 createViewFromTag 方法:

View view;
if (mFactory2 != null) {
    view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
    view = mFactory.onCreateView(name, context, attrs);
} else {
    view = null;
}

if (view == null && mPrivateFactory != null) {
    view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}

if (view == null) {
    final Object lastContext = mConstructorArgs[0];
    mConstructorArgs[0] = context;
    try {
        if (-1 == name.indexOf('.')) {
            view = onCreateView(parent, name, attrs);
        } else {
            view = createView(name, null, attrs);
        }
    } finally {
        mConstructorArgs[0] = lastContext;
    }
}

我贴出来这段代码是为了总结下通过 setContentView 这种方式创建出 View 的途径:

  1. Factory2.onCreateView;
  2. Factory.onCreateView;
  3. mPrivateFactory.onCreateView;
  4. createView;

其中1、2、4方式相信看过前面几篇文章的小伙伴肯定都很熟悉了:

  • 1、2两种方式本质上一样,可以通过我们自己设置的 Factory 来创建View;
  • 4这种方式是通过反射来创建 View对象;
  • 而方式3在之前的几篇文章中则没有说到过,不过别急,接下来我们会介绍它;

到了这里我们知道通过 setContentView 这种方式创建出 View 的途径有4种,其中第4种我们直接排除掉了,也就只剩下了前三种方式。

3、探究

在我们探索究竟是这三种方式中的哪一种之前,我们先来熟悉下 mPrivateFactory。我们看下它的定义及设值的地方:

    private Factory2 mPrivateFactory;// 定义可以看出 mPrivateFactory 也实现了 Factory2 

    protected LayoutInflater(LayoutInflater original, Context newContext) {
        mContext = newContext;
        mFactory = original.mFactory;
        mFactory2 = original.mFactory2;
        mPrivateFactory = original.mPrivateFactory;
        setFilter(original.mFilter);
    }

    /**
     * @hide for use by framework
     */
    public void setPrivateFactory(Factory2 factory) {
        if (mPrivateFactory == null) {
            mPrivateFactory = factory;
        } else {
            mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory);
        }
    }

我们就知道了 mPrivateFactory 实现了 Factory2 接口,设值方式有两种,一种是 framework 调用,还有一种是创建 LayoutInflater 的时候传入。

这三种方式有一个共同特点就是都和 Factory 相关。而使用 Factory 都会通过 LayoutInflater setFactory,既然我们没有做事情就完成了对 fragment 标签的解析,那有理由相信是系统处理了。使用 Fragment 的时候需要继承 FragmentActivity 或者是 AppCompatActivity,这里就以 FragmentActivity 为例来分析,来搜下哪里调用了 setFactory 函数。

FragmentActivity继承关系

但是在 FragmentActivity 的继承链上的各个类我们并没有搜到 setFactory 或 setFactory2。这两个常规的设置没有找到,我们再来找第三种方式 setPrivateFactory,最终在 Activity 搜到了,attach 方法中:

    mWindow.getLayoutInflater().setPrivateFactory(this);

然后我们看下 Activity 的定义,实现了 LayoutInflater.Factory2 接口

    public class Activity extends ContextThemeWrapper
            implements LayoutInflater.Factory2,
            Window.Callback, KeyEvent.Callback,
            OnCreateContextMenuListener, ComponentCallbacks2,
            Window.OnWindowDismissedCallback, WindowControllerCallback,
            AutofillManager.AutofillClient {

                @Nullable
                public View onCreateView(String name, Context context, AttributeSet attrs) {
                    return null;
                }

                public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
                    if (!"fragment".equals(name)) {
                        return onCreateView(name, context, attrs);
                    }

                    return mFragments.onCreateView(parent, name, context, attrs);
                }

            }

可以看出来在 onCreateView 方法中会判断标签名字如果是 fragment 的话则会调用 mFragments.onCreateView 来创建 View。

接下来总结下流程:

  1. 在 Activity 的 attach 方法中会为当前 Activity 的设置 mPrivateFactory;
  2. 在 LayoutInflater 的 createViewFromTag 方法中会先使用 Factory2 或 Factory 来创建view;
  3. 针对 fragment 的场景下默认获取到的 view 是null;
  4. 如果是 null 则通过 mPrivateFactory 创建 view,这里就会走到 Activity 的onCreateView 方法;
  5. 通过 mFragments(也就是 FragmentController)的 onCreateView 方法来创建 View;

4、mFragments.onCreateView

mFragments 其实是 FragmentController,然后细跟代码会走到 FragmentManager 的 onCreateView 方法:

    @Override
    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        if (!"fragment".equals(name)) {
            return null;
        }

        moveToState(fragment, Fragment.CREATED, 0, 0, false);

        if (fragment.mView == null) {
            throw new IllegalStateException("Fragment " + fname
                    + " did not create a view.");
        }
        return fragment.mView;
    }

然后到了 moveToState 方法,注意传入的 newState 是 Fragment.CREATED。

    void moveToState(Fragment f, int newState, int transit, int transitionStyle,
            boolean keepActive) {

         case Fragment.CREATED:

         if (newState > Fragment.CREATED) {
             if (DEBUG) Log.v(TAG, "moveto ACTIVITY_CREATED: " + f);
             if (!f.mFromLayout) {
                 ViewGroup container = null;
                 if (f.mContainerId != 0) {
                     if (f.mContainerId == View.NO_ID) {
                         throwException(new IllegalArgumentException(
                                 "Cannot create fragment "
                                         + f
                                         + " for a container view with no id"));
                     }
                     container = mContainer.onFindViewById(f.mContainerId);
                     if (container == null && !f.mRestored) {
                         String resName;
                         try {
                             resName = f.getResources().getResourceName(f.mContainerId);
                         } catch (NotFoundException e) {
                             resName = "unknown";
                         }
                         throwException(new IllegalArgumentException(
                                 "No view found for id 0x"
                                 + Integer.toHexString(f.mContainerId) + " ("
                                 + resName
                                 + ") for fragment " + f));
                     }
                 }
                 f.mContainer = container;

                 // 重点:最关键的方法在这里
                 f.mView = f.performCreateView(f.performGetLayoutInflater(
                         f.mSavedFragmentState), container, f.mSavedFragmentState);

                 if (f.mView != null) {
                     f.mView.setSaveFromParentEnabled(false);
                     if (container != null) {
                         container.addView(f.mView);
                     }
                     if (f.mHidden) {
                         f.mView.setVisibility(View.GONE);
                     }
                     f.onViewCreated(f.mView, f.mSavedFragmentState);
                     dispatchOnFragmentViewCreated(f, f.mView, f.mSavedFragmentState,
                             false);
                     // Only animate the view if it is visible. This is done after
                     // dispatchOnFragmentViewCreated in case visibility is changed
                     f.mIsNewlyAdded = (f.mView.getVisibility() == View.VISIBLE)
                             && f.mContainer != null;
                 }
             }

             f.performActivityCreated(f.mSavedFragmentState);
             dispatchOnFragmentActivityCreated(f, f.mSavedFragmentState, false);
             if (f.mView != null) {
                 f.restoreViewState(f.mSavedFragmentState);
             }
             f.mSavedFragmentState = null;
         }
    }

然后到了 Fragment 的 performCreateView 方法:

    View performCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        if (mChildFragmentManager != null) {
            mChildFragmentManager.noteStateNotSaved();
        }
        mPerformedCreateView = true;
        return onCreateView(inflater, container, savedInstanceState);
    }

在最后一行我们再次看到了 onCreateView 方法,这个 onCreateView 就是 Fragment 的一个方法,我们在开发中需要覆写的那个。Fragment 的 performCreateView() 方法的返回值是一个 View ,这个View 被返回给了 Activity 中的 onCreateView 方法;这样就实现了遇到 fragment 标签特殊处理并返回 view。

5、总结

本文主要学习 layout 中 fragment 标签的创建过程,并且将思考、分析的过程也写了下来,希望对大家阅读源码、思考问题有所帮助。

我们再来回顾下 fragment 标签的创建过程:

  1. FragmentActivity 实现了 Factory2接口,并在 attach 方法设置了mPrivateFactory;
  2. LayoutInflater 使用 factory 对 fragment 标签默认创建出来的 view 为null;
  3. 走到了 mPrivateFactory 的 onCreateView 方法;
  4. 调用 Activity 的 onCreateView 方法;
  5. 调用 FragmentController 的 onCreateView 方法;
  6. 调用 FragmentManager 的 onCreateView 方法;
  7. 调用 moveToState 方法,其中会调用 Fragment 的 performGetLayoutInflater 方法;
  8. 调用 Fragment 的 performCreateView 方法,就创建了 fragment 标签对应的 view;

原文发布于微信公众号 - 双十二技术哥(gh_b0e7544783e2)

原文发表时间:2018-08-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏分享达人秀

自定义BaseAdapter

在ListView的使用中,有时候还需要在里面加入按钮等控件,实现单独的操作。也就是说,这个ListView不再只是展示数据,也不仅仅是这一行要来处理用...

22680
来自专栏james大数据架构

实例演示Android异步加载图片

本文给大家演示异步加载图片的分析过程。让大家了解异步加载图片的好处,以及如何更新UI。 首先给出main.xml布局文件: 简单来说就是 LinearLayou...

22150
来自专栏知识分享

android客服端+eps8266+单片机+路由器之远程控制系统

用android客服端+eps8266+单片机+路由器做了一个远程控制的系统,因为自己是在实验室里,所以把实验室的门,灯做成了远程控制的。 控制距离有多远---...

75960
来自专栏一个会写诗的程序员的博客

第14章 使用Kotlin 进行 Android 开发(2)

我们使用 fastjson 来解析这个数据。在 app 下面的 build.gradle中添加依赖

10320
来自专栏7号代码

Android网络与数据存储——SharedPreferences(实现是否开启引导界面)

SharedPreferences保存的数据主要是简单类型的key-value对。

21380
来自专栏Vamei实验室

安卓第八夜 玛丽莲梦露

上一讲说明了数据库中存取数据的方法。这一讲将以条目的视图方式,来以相似的视图方式,显示多个数据对象。这种方式特别适合于显示从数据库中取出的多个结构相似的数据,比...

22090
来自专栏三流程序员的挣扎

Navigation 详解二

BottomNavigationView 以更简洁的方式来实现过去的 BottomNavigationBar 的样式。Android Studio 中创建一个 ...

25420
来自专栏分享达人秀

ListView优化和列表首尾使用

前面连续几期都在学习ListView的各种使用方法,如果细心的同学可能会发现其运行效率是有待提高的,那么本期就来一起学习有哪些方法技巧来优化ListVi...

24980
来自专栏Android Note

Android-实用的MVP

15330
来自专栏10km的专栏

jface databinding:使用CheckboxTableViewer实现表中(Set)对象与CheckTable中选中条目数据绑定

上一篇博文《jface databinding:可多选的widget List组件selection项目与java.util.List对象的双向数据绑定》讲述了...

426100

扫码关注云+社区

领取腾讯云代金券