Android fragment 标签加载过程分析

jojo
发布于 2020-8-29 16:55
浏览
0收藏

本篇文章我们来学习下 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 的途径:

Factory2.onCreateView;
Factory.onCreateView;
mPrivateFactory.onCreateView;
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 函数。


Android fragment 标签加载过程分析-鸿蒙开发者社区

但是在 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。

接下来总结下流程:

在 Activity 的 attach 方法中会为当前 Activity 的设置 mPrivateFactory;
在 LayoutInflater 的 createViewFromTag 方法中会先使用 Factory2 或 Factory 来创建view;
针对 fragment 的场景下默认获取到的 view 是null;
如果是 null 则通过 mPrivateFactory 创建 view,这里就会走到 Activity 的onCreateView 方法;
通过 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 标签的创建过程:

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


作者:貌似许亚军
来源:简书

分类
1
收藏
回复
举报
回复
    相关推荐