Android fragment 標籤加載過程分析

本文概述

在上一篇文章中咱們介紹了 AsyncLayoutInflater 使用的注意事項及改進方案。android

建議先回顧下以前五篇文章,這個系列的文章從前日後順序看最佳:bash

本篇文章咱們來學習下 layout 中 fragment 標籤的加載過程,本文基於 Android 8.1.0。微信

一、鋪墊

各位老司機確定對 Fragment 的使用都很是熟悉,咱們簡單回顧下:Fragment 的添加方式有兩種:靜態添加和動態添加。而靜態添加就是在佈局中寫上 Fragment 的相關引用,以下:app

<?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 的定義:ide

public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener {
}
複製代碼

能夠看到 Fragment 並非一個 View,那說明 fragment 標籤就不是經過正常的反射來建立的,進一步說就是 fragment 標籤的建立和普通的 view 不是一個流程。函數

二、思考

問題:既然 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 的途徑:ui

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

其中一、二、4方式相信看過前面幾篇文章的小夥伴確定都很熟悉了:this

  • 一、2兩種方式本質上同樣,能夠經過咱們本身設置的 Factory 來建立View;
  • 4這種方式是經過反射來建立 View對象;
  • 而方式3在以前的幾篇文章中則沒有說到過,不過別急,接下來咱們會介紹它;

到了這裏咱們知道經過 setContentView 這種方式建立出 View 的途徑有4種,其中第4種咱們直接排除掉了,也就只剩下了前三種方式。

三、探究

在咱們探索到底是這三種方式中的哪種以前,咱們先來熟悉下 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;

四、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。

五、總結

本文主要學習 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

歡迎關注
相關文章
相關標籤/搜索