Android fragment 标签加载过程分析

本文概述

在上一篇文章中我们介绍了 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;

广告时间

今日头条各Android客户端团队招人火爆进行中,各个级别和应届实习生都需要,业务增长快、日活高、挑战大、待遇给力,各位大佬走过路过千万不要错过!

本科以上学历、对技术有热情,欢迎加我的微信详聊:KOBE8242011

欢迎关注

猜你喜欢

转载自juejin.im/post/5b777bd5f265da432607265f
今日推荐