View 的創(chuàng)建 - LayoutInflater 基礎(chǔ)流程分析

LayoutInflater 將布局文件(XML)實(shí)例化為一個(gè) View 對(duì)象竹挡。

通常我們會(huì)通過(guò) Activity#getLayoutInflater() 或者是 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) 來(lái)獲取一個(gè)標(biāo)準(zhǔn)的與當(dāng)前運(yùn)行的 Context 相關(guān)聯(lián)的 LayoutInflater 實(shí)例。

我們以 Activity#setContentView(@LayoutRes int layoutResID) 為例來(lái)看一下 LayoutInflater 的工作流程痴颊。

源碼:Android-29尉姨、AndroidX

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

1 AppCompatActivity#setContentView 流程

AppCompatActivity 是 AndroidX 兼容包下的 Activity 的實(shí)現(xiàn)基類

//#AppCompatActivity
@Override
public void setContentView(@LayoutRes int layoutResID) {
    getDelegate().setContentView(layoutResID);
}

可以看到內(nèi)部通過(guò)代理來(lái)進(jìn)行設(shè)置,下面來(lái)看代理的實(shí)現(xiàn) getDelegate()

////#AppCompatActivity
@NonNull
public AppCompatDelegate getDelegate() {
    if (mDelegate == null) {
        mDelegate = AppCompatDelegate.create(this, this);
    }
    return mDelegate;
}

@NonNull
public static AppCompatDelegate create(@NonNull Activity activity,
        @Nullable AppCompatCallback callback) {
    return new AppCompatDelegateImpl(activity, callback);
}

通過(guò) getDelegate() 方法創(chuàng)建了代理的實(shí)現(xiàn)類 AppCompatDelegateImpl,下面我們來(lái)看這個(gè)代理實(shí)現(xiàn)類中的 setContentView 方法:

@Override
public void setContentView(int resId) {
    ensureSubDecor();
    ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
    contentParent.removeAllViews();
    LayoutInflater.from(mContext).inflate(resId, contentParent);
    mAppCompatWindowCallback.getWrapped().onContentChanged();
}

可以看見(jiàn)伤为,AppCompatActivity#setContentView 內(nèi)部是通過(guò) LayoutInflater.from(mContext).inflate(resId, contentParent) 來(lái)加載布局的

2 LayoutInflater.from(mContext) 流程

在來(lái)看 from(mContext) 方法之前,我們先明確 mContext 的類型据途。

2.1 mContext 的類型

往上翻可以看到我們?cè)趧?chuàng)建代理對(duì)象的同時(shí)绞愚,將 Activity 作為參數(shù)傳入,也就是說(shuō) mContext 的類型是 AppCompatActivity 也就是 ContextThemeWrapper 類型颖医。

這里我們貼上一張 Context 相關(guān)的背景知識(shí):


Context -android-29-

2.2 LayoutInflater.from(mContext)

/**
 * Obtains the LayoutInflater from the given context.
 */
public static LayoutInflater from(Context context) {
    LayoutInflater LayoutInflater =
            (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    if (LayoutInflater == null) {
        throw new AssertionError("LayoutInflater not found.");
    }
    return LayoutInflater;
}

可以發(fā)現(xiàn)我們是通過(guò) context.getSystemService 來(lái)獲取 LayoutInflater “服務(wù)”位衩。
之前我們已經(jīng)明確了 mContext 的類型是 ContextThemeWrapper 類型,我們來(lái)看看它的 from 方法:

 @Override
 public Object getSystemService(String name) {
     if (LAYOUT_INFLATER_SERVICE.equals(name)) {
         if (mInflater == null) {
             mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
         }
         return mInflater;
     }
     return getBaseContext().getSystemService(name);
 }

這個(gè)的 getBaseContext 獲取到的是 mBase 這個(gè)屬性,它的類型是 ContextImpl熔萧,mBase 的賦值源于 Activity 在創(chuàng)建后的調(diào)用的 attach 方法糖驴,這里就不再展開了。

然后讓我們來(lái)看看 ContextImplgetSystemService 方法:

@Override
public Object getSystemService(String name) {
    return SystemServiceRegistry.getSystemService(this, name);
}

到這里看上去是真正要獲取服務(wù)的地方了佛致,我們根據(jù) ContextImpl 對(duì)象本身和服務(wù)名稱SystemServiceRegistry 中獲取服務(wù)贮缕,我們可以把它理解為注冊(cè)表:

public static Object getSystemService(ContextImpl ctx, String name) {
    ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
    return fetcher != null ? fetcher.getService(ctx) : null;
}

跟進(jìn)到它的 getSystemService 方法中可以看到:

  1. 從 SYSTEM_SERVICE_FETCHERS 中獲取 ServiceFetcher
  2. 通過(guò) ServiceFetcher 獲取服務(wù)

服務(wù)在什么時(shí)候注冊(cè)?
SystemServiceRegistry 的靜態(tài)代碼塊中俺榆,對(duì)服務(wù)進(jìn)行了注冊(cè):

registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
        new CachedServiceFetcher<LayoutInflater>() {
    @Override
    public LayoutInflater createService(ContextImpl ctx) {
        return new PhoneLayoutInflater(ctx.getOuterContext());
    }});

可以看到最終我們獲取到的 LayoutInflater 類型是 PhoneLayoutInflater 類型感昼。

在 ContextImpl 類中有一個(gè) mServiceCache 屬性,在聲明的時(shí)候已經(jīng)初始化:

//ContextImpl
// The system service cache for the system services that are cached per-ContextImpl.
    @UnsupportedAppUsage
    final Object[] mServiceCache = SystemServiceRegistry.createServiceCache();

由此可見(jiàn)罐脊,在 ContextImpl 創(chuàng)建后定嗓,服務(wù)就會(huì)進(jìn)行注冊(cè)蜕琴,根據(jù)注釋可知,每一個(gè) ContextImpl 對(duì)象都有自己的服務(wù)緩存宵溅。

ServiceFetcher如何獲取服務(wù)凌简?
我們?cè)诨剡^(guò)頭來(lái)看服務(wù)獲取的具體邏輯(我刪除了部分代碼):

public final T getService(ContextImpl ctx) {
    final Object[] cache = ctx.mServiceCache;
    final int[] gates = ctx.mServiceInitializationStateArray;
    for (;;) {
        boolean doInitialize = false;
        synchronized (cache) {
            // Return it if we already have a cached instance.
            //①
            T service = (T) cache[mCacheIndex];
            if (service != null || gates[mCacheIndex] == ContextImpl.STATE_NOT_FOUND) {
                return service;
            }
         if (doInitialize) {
            // Only the first thread gets here.
            T service = null;
            try {
            //②
                service = createService(ctx);
                newState = ContextImpl.STATE_READY;
            } catch (ServiceNotFoundException e) {
                onServiceNotFound(e);
            } finally {
                synchronized (cache) {
                    ③
                    cache[mCacheIndex] = service;
                    gates[mCacheIndex] = newState;
                    cache.notifyAll();
                }
            }
            return service;
        }
    }
}
  1. 查看 ContextImpl 的緩存,緩存命中則直接返回服務(wù)
  2. 緩存不存在的話則通過(guò) createService 方法創(chuàng)建服務(wù)
  3. 最后將服務(wù)緩存在 ContextImpl 中

2.3 PhoneLayoutInflater#cloneInContext

在上面 ContextThemeWrapper 獲取服務(wù)的代碼中 mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this); 我們?cè)讷@取 PhoneLayoutInflater 之后恃逻,還調(diào)用了 cloneInContext 方法雏搂,名字聽上去是克隆一個(gè)對(duì)象。

public LayoutInflater cloneInContext(Context newContext) {
    return new PhoneLayoutInflater(this, newContext);
}

protected PhoneLayoutInflater(LayoutInflater original, Context newContext) {
    super(original, newContext);
}

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

由此可見(jiàn)我們使用了一個(gè)新的 Context 替換了原先 LayoutInflater 中的 mContext 屬性辛块,根據(jù)上面的代碼可知畔派,我們?cè)谑褂?ContextImpl 創(chuàng)建了 PhoneLayoutInflater 之后,將其中的 mContext 替換為 ContextThemeWrapper润绵。

3 LayoutInflater#inflate

LayoutInflater.from(mContext).inflate(resId, contentParent); 由上面分析可知线椰,在獲取到 PhoneLayoutInflater 對(duì)象后,接著調(diào)用它的 inflate 方法:

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
    return inflate(resource, root, root != null);
}

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    if (DEBUG) {
        Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
              + Integer.toHexString(resource) + ")");
    }
    View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
    if (view != null) {
        return view;
    }
    XmlResourceParser parser = res.getLayout(resource);
    try {
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}

最終調(diào)用的 inflate 方法接收三個(gè)參數(shù):

  1. @LayoutRes int resource 這個(gè)參數(shù)就是我們 setContentView 方法傳入的 XML 資源文件
  2. @Nullable ViewGroup root 這是一個(gè)可選的父布局容器
  3. boolean attachToRoot 尘盼,這個(gè)參數(shù)決定是否將從 XML 加載的 View 對(duì)象添加進(jìn) root 中

根據(jù) setContentView 的調(diào)用可知憨愉,我們傳入的布局文件最后會(huì)被添加進(jìn) id 為 Content 的容器中

在 inflate 方法中我們創(chuàng)建了當(dāng)前 XML 資源文件的解析器,并將它傳入重載的 inflate 房中:

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            View result = root;
            try {
                //① merge 標(biāo)簽判斷
                if (TAG_MERGE.equals(name)) {
                    if (root == null || !attachToRoot) {
                        ...
                    }
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // ②創(chuàng)建 XMl 文件中頂層標(biāo)簽中標(biāo)記的 View 對(duì)象
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                    // ③指定 LayoutParams
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            temp.setLayoutParams(params);
                        }
                    }

                    //④遞歸的將子視圖填充到 temp 中
                    context.rInflateChildren(parser, temp, attrs, true);

                    //⑤將根據(jù) XML 實(shí)例化的 View 添加到 root 中                    
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }

                    //直接返回 temp 對(duì)象
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

            } catch (XmlPullParserException e) {
                ....
            }

            return result;
        }
    }

這里的方法有點(diǎn)長(zhǎng)卿捎,我同樣省略一些代碼配紫,讓我們的視線集中在布局的解析上。

3.1 merge 標(biāo)簽

我們判斷 XMl 的根標(biāo)簽是不是 merge午阵,如果是 merge 標(biāo)記的話在直接調(diào)用遞歸填充方法 rInflater躺孝,這里我們先不看對(duì) merge 標(biāo)簽的處理。

3.2 LayoutInflater#createViewFromTag

如果不是 merge 標(biāo)簽的話底桂,我們將調(diào)用 createViewFromTag 方法來(lái)創(chuàng)建 View 對(duì)象:

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
        //...
        try {
            View view = tryCreateView(parent, name, context, attrs);

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

            return view;
        } catch (InflateException e) {
            //...
        }
    }

這里也是分為三步:

  1. 嘗試調(diào)用 tryCreateView 方法創(chuàng)建 View
  2. 如果不是自定義 View 則調(diào)用 onCreateView 方法
  3. 如果是自定義 View 則調(diào)用 createView

3.2.1 LayoutInflater#tryCreateView

public final View tryCreateView(@Nullable View parent, @NonNull String name,
    @NonNull Context context,
    @NonNull AttributeSet attrs) {
    if (name.equals(TAG_1995)) {
        // Let's party like it's 1995!
        return new BlinkLayout(context, attrs);
    }
    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);
    }
    return view;
}
  1. 如果 XML 的標(biāo)記是 blink(TAG_1995) 的話植袍,會(huì)創(chuàng)建一個(gè) BlinkLayout 類型的對(duì)象,它會(huì)每隔 500ms 重繪一次籽懦,形成閃爍的效果于个。
  2. 嘗試通過(guò) mFactory2 對(duì)象的 onCreateView 來(lái)創(chuàng)建 View 對(duì)象
  3. 嘗試通過(guò) mFactory 對(duì)象的 onCreateView 來(lái)創(chuàng)建 View 對(duì)象
  4. 嘗試通過(guò) mPrivateFactory 對(duì)象的 onCreateView 來(lái)創(chuàng)建 View 對(duì)象

mFactory2、mPrivateFactory 對(duì)象都是 Factory2 類型暮顺,而 mFactory 對(duì)象是 Factory 類型厅篓,F(xiàn)actory2 是對(duì) Factory 接口的升級(jí),F(xiàn)actory2 繼承了 Factory 接口捶码,并且多了一個(gè) View onCreateView(View parent, String name,Context context, @NonNull AttributeSet attrs); 相比較于 Factory 的 onCreateView 而言多了一個(gè) View 類型的 parent 參數(shù)羽氮。

稍后我們?cè)賮?lái)看這幾個(gè)屬性是在何時(shí)被設(shè)置的,讓我們回到 createViewFromTag 方法

3.2.2 LayoutInflater#createView

如果我們沒(méi)有設(shè)置 mFactory2 這些屬性惫恼,那么 tryOnCreateView 這個(gè)方法返回的 View 對(duì)象就是 null档押,之后我們會(huì)根據(jù) XML 標(biāo)記是否包含 "." 來(lái)決定調(diào)用不同的方法。

如果標(biāo)簽不包含 "." ,說(shuō)明這是 Android 系統(tǒng)提供的 View 例如:TextView汇荐,ImageView...,這時(shí)我們會(huì)調(diào)用 onCreateView 方法盆繁,這個(gè)方法會(huì)調(diào)用一些列的重載方法掀淘,最后會(huì)調(diào)用 createView(String name, String prefix, AttributeSet attrs) 方法:

protected View onCreateView(String name, AttributeSet attrs)
        throws ClassNotFoundException {
    return createView(name, "android.view.", attrs);
}

public final View createView(String name, String prefix, AttributeSet attrs)
        throws ClassNotFoundException, InflateException {
    Context context = (Context) mConstructorArgs[0];
    if (context == null) {
        context = mContext;
    }
    return createView(context, name, prefix, attrs);
}

在 onCreateView 方法當(dāng)中,我們會(huì)調(diào)用 createView 方法油昂,并且限定了 prefix 這個(gè)入?yún)?"android.view"革娄,緊接著我們會(huì)調(diào)用最后重載的 createView 方法:

public final View createView(@NonNull Context viewContext, @NonNull String name,
            @Nullable String prefix, @Nullable AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        if (constructor != null && !verifyClassLoader(constructor)) {
            constructor = null;
            sConstructorMap.remove(name);
        }
        Class<? extends View> clazz = null;

        try {
            if (constructor == null) {
                // Class not found in the cache, see if it's real, and try to add it
                clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                        mContext.getClassLoader()).asSubclass(View.class);
                constructor = clazz.getConstructor(mConstructorSignature);
                constructor.setAccessible(true);
                sConstructorMap.put(name, constructor);
            } else {
                // If we have a filter, apply it to cached constructor
                ...
            }
        
            try {
                final View view = constructor.newInstance(args);
                if (view instanceof ViewStub) {
                    // Use the same context when inflating ViewStub later.
                    final ViewStub viewStub = (ViewStub) view;
                    viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
                }
                return view;
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        } 
        ...
    }

我省略一些異常捕獲和日志代碼,可以看到我們會(huì)嘗試從緩存中獲取當(dāng)前 View 的構(gòu)造函數(shù)冕碟,如果命中緩存的話拦惋,則直接通過(guò)反射創(chuàng)建 View 對(duì)象并返回,否則我們會(huì)通過(guò)入?yún)?nameprefix 來(lái)拼接 View 的全路徑類名安寺,然后在通過(guò)反射獲取它的構(gòu)造函數(shù)厕妖,放入緩存之后在創(chuàng)建 View 返回。

到這里我們可以說(shuō) LayoutInflater 是通過(guò)反射的方式將 XML 實(shí)例化成一個(gè) View 對(duì)象挑庶,這么說(shuō)沒(méi)錯(cuò)言秸,但是之前我們有提到過(guò),在反射創(chuàng)建 View 對(duì)象之前迎捺,會(huì)經(jīng)過(guò) mFactory 等對(duì)象的處理举畸,接下來(lái)讓我們肯看這些“工廠“是什么時(shí)候設(shè)置的。

4 LayoutInflater#setFactory2

之前我們分析的是 AppCompatActivity#setContentView 流程:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

在 setContentView 之前我們調(diào)用了父類的 onCreate 方法:

 @Override
 protected void onCreate(@Nullable Bundle savedInstanceState) {
     final AppCompatDelegate delegate = getDelegate();
     delegate.installViewFactory();
     delegate.onCreate(savedInstanceState);
     super.onCreate(savedInstanceState);
 }

這里可以看到我們調(diào)用了 delegate 對(duì)象的 installViewFactory 方法:

//AppCompatDelegateImpl
@Override
public void installViewFactory() {
    LayoutInflater layoutInflater = LayoutInflater.from(mContext);
    if (layoutInflater.getFactory() == null) {
        LayoutInflaterCompat.setFactory2(layoutInflater, this);
    } else {
        if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
            Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
                    + " so we can not install AppCompat's");
        }
    }
}

其中 LayoutInflaterCompat.setFactory2(layoutInflater, this);對(duì) mFactory 進(jìn)行了設(shè)置:

//LayoutInflaterCompat
class AppCompatDelegateImpl extends AppCompatDelegate
        implements MenuBuilder.Callback, LayoutInflater.Factory2{
        //......
}

public static void setFactory2(
        @NonNull LayoutInflater inflater, @NonNull LayoutInflater.Factory2 factory) {
    inflater.setFactory2(factory);
    //省略了兼容性代碼......
}

AppCompatDelegateImpl 實(shí)現(xiàn)了 Factory2 接口凳枝,我們通過(guò) setFactory2 方法將 this 賦值給 mFactory 和 mFactory2 對(duì)象抄沮,下面我們來(lái)看 Factory2 接口在 AppCompatDelegateImpl 中的實(shí)現(xiàn):

 @Override
 public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
     return createView(parent, name, context, attrs);
 }
 
 public View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs) {
        //省略部分代碼......
        mAppCompatViewInflater = new AppCompatViewInflater();
        return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
                IS_PRE_LOLLIPOP, true, VectorEnabledTintResources.shouldBeUsed()
        );
    }

可以看到在 createView 方法中,我們構(gòu)建了一個(gè) AppCompatViewInflater 對(duì)象岖瑰,將創(chuàng)建 View 的操作交給了它叛买,接著我們看這個(gè)對(duì)象的 createView 方法:

final View createView(View parent, final String name, @NonNull Context context,
        @NonNull AttributeSet attrs, boolean inheritContext,
        boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
    final Context originalContext = context;
    // We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
    // by using the parent's context
    if (inheritContext && parent != null) {
        context = parent.getContext();
    }
    if (readAndroidTheme || readAppTheme) {
        // We then apply the theme on the context, if specified
        context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
    }
    if (wrapContext) {
        context = TintContextWrapper.wrap(context);
    }
    View view = null;
    // We need to 'inject' our tint aware Views in place of the standard framework versions
    switch (name) {
        case "TextView":
            view = createTextView(context, attrs);
            verifyNotNull(view, name);
            break;
        case "ImageView":
            view = createImageView(context, attrs);
            verifyNotNull(view, name);
            break;
    // 省略了后續(xù)代碼...
}

protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
    return new AppCompatTextView(context, attrs);
}
  1. 可以看見(jiàn),為了能夠兼容锭环,使得 android 5.0 之前的版本能夠使用矢量圖功能聪全,我們會(huì)對(duì)originalContext 進(jìn)行包裝,通過(guò) TintContextWrapper 的 wrapper 方法將它包裝成 TintContextWrapper 類型
  2. 通過(guò)字符串匹配辅辩,直接使用 new 對(duì)象的方式难礼,創(chuàng)建相對(duì)應(yīng)的 View,節(jié)省了反射帶來(lái)的開銷

由此可見(jiàn)玫锋,我們雖然我們?cè)?XML 中聲明的是 TextView 類型蛾茉,但是為了向前兼容,系統(tǒng)實(shí)際創(chuàng)建的是 AppCompatTextView 類型撩鹿;另外通過(guò) View#getContext 方法獲取到的類型也不一定是 Activity 類型谦炬,也有可能是 TintContextWrapper 類型。

5 LayoutInflater#setPrivateFactory

經(jīng)過(guò)上面的流程分析,我們已經(jīng)知道 mFactory 和 mFactory2 屬性是什么時(shí)候被賦值的键思,那么 mPrivateFactory 又是什么時(shí)候被賦值的呢础爬?

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

我們可以通過(guò) LayoutInflater#setPrivateFactory 方法設(shè)置 mPrivateFactory,注釋中說(shuō)這是個(gè)隱藏方法吼鳞,被 framework 層使用看蚜,這里給大家一個(gè)看源碼的網(wǎng)站,就是谷歌最近剛開源的 Code Serach赔桌,我們通過(guò)這個(gè)網(wǎng)站來(lái)查看一下
setPrivateFactory 方法的引用供炎。

-w1434

如圖,我們?cè)?Activity attach 方法調(diào)用的時(shí)候通過(guò) mWindow.getLayoutInflater().setPrivateFactory(this);對(duì) mPrivateFactory 進(jìn)行設(shè)置疾党,并且設(shè)置的值是 this音诫,說(shuō)明我們 Activity 也實(shí)現(xiàn)了 Factory2 接口。

下面我們來(lái)看 Factory2 接口在 Activity 中的實(shí)現(xiàn):

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

可以看到這里對(duì) XML 中的 fragment 標(biāo)記進(jìn)行了處理雪位,F(xiàn)ragmentController#onCreateView 方法會(huì)對(duì) Fragment 進(jìn)行實(shí)例化竭钝,并調(diào)用 Fragment 實(shí)現(xiàn)的 onCreateView 方法來(lái)返回 View 對(duì)象。

到此為止我們就知道了 XML 中的 fragment 標(biāo)簽是如何被處理的茧泪。

6. final

Android 系統(tǒng)通過(guò)給 LayoutInflater 設(shè)置工廠的方式蜓氨,自己決定 View 的實(shí)例化,以此來(lái)實(shí)現(xiàn)向前兼容队伟,利用 setFactory 方法我們還可以做到很多事情穴吹,比如全局字體的替換,給特定的 View 設(shè)置特定的背景...

同時(shí)我也只對(duì)最基礎(chǔ)的流程進(jìn)行分析嗜侮,里面對(duì) merge港令、include、viewStub 等標(biāo)簽的處理并沒(méi)有展開锈颗,其實(shí)這些標(biāo)簽也只是遞歸的進(jìn)行 View 的創(chuàng)建并添加進(jìn)容器而已顷霹。

參考鏈接:

  1. https://juejin.im/post/5dd499a6f265da0bf21126cc
  2. https://blog.csdn.net/mq2553299/article/details/99737681
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市击吱,隨后出現(xiàn)的幾起案子淋淀,更是在濱河造成了極大的恐慌,老刑警劉巖覆醇,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件朵纷,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡永脓,警方通過(guò)查閱死者的電腦和手機(jī)袍辞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)常摧,“玉大人搅吁,你說(shuō)我怎么就攤上這事威创。” “怎么了谎懦?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵肚豺,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我界拦,道長(zhǎng)详炬,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任寞奸,我火速辦了婚禮,結(jié)果婚禮上在跳,老公的妹妹穿的比我還像新娘枪萄。我一直安慰自己,他們只是感情好猫妙,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布瓷翻。 她就那樣靜靜地躺著,像睡著了一般割坠。 火紅的嫁衣襯著肌膚如雪齐帚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天彼哼,我揣著相機(jī)與錄音对妄,去河邊找鬼。 笑死敢朱,一個(gè)胖子當(dāng)著我的面吹牛剪菱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播拴签,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼孝常,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了蚓哩?” 一聲冷哼從身側(cè)響起构灸,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎岸梨,沒(méi)想到半個(gè)月后喜颁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡盛嘿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年洛巢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片次兆。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡稿茉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情漓库,我是刑警寧澤恃慧,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站渺蒿,受9級(jí)特大地震影響痢士,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜茂装,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一怠蹂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧少态,春花似錦城侧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至侨歉,卻和暖如春屋摇,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背幽邓。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工炮温, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人牵舵。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓茅特,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親棋枕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子白修,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容