Android开发经验谈Android技术知识Android开发

面试官:说说Android fragment 标签加载过程;我:

2020-08-22  本文已影响0人  字节走动_Android

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方式相信看过前面几篇文章的小伙伴肯定都很熟悉了:

到了这里我们知道通过 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 函数。

image

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

作者:貌似许亚军
链接:https://www.jianshu.com/p/06a563d7a02c

推荐阅读:
字节跳动8年老Android面试官谈;Context都没弄明白凭什么拿高薪?
做了六年Android,终于熬出头了,15K到31K全靠这份高级面试题+解析
字节、腾讯,阿里Android高级面试真题汇总,会一半随便进大厂

上一篇下一篇

猜你喜欢

热点阅读