Android 中LayoutInflater(佈局加載器)源碼篇之createViewFromTag方法

本文出自博客Vander丶CSDN博客,如需轉載請標明出處,尊重原創謝謝
博客地址:blog.csdn.net/l540675759/…

前言

若是讀者沒有閱讀過該系列博客,建議先閱讀下博文說明,這樣會對後續的閱讀博客思路上會有一個清晰的認識。java

Android 中LayoutInflater(佈局加載器)系列博文說明android


導航

Android 中LayoutInflater(佈局加載器)系列博文說明緩存

Android 中LayoutInflater(佈局加載器)系列之介紹篇安全

Android 中LayoutInflater(佈局加載器)系列之源碼篇bash

Android 中LayoutInflater(佈局加載器)源碼篇之createViewFromTag方法app

Android 中LayoutInflater(佈局加載器)源碼篇之rInflate方法ide

Android 中LayoutInflater(佈局加載器)系列之實戰篇佈局


概述

本篇博客,是屬於Android 中LayoutInflater(佈局加載器)源碼篇其中一個部分,專門介紹createViewFromTag方法的流程,具體有如下幾部分:ui

  1. 一些不常見的標籤的解析方法以及使用,例如:view、blinkthis

  2. LayoutInflater中Factory,Factory2是什麼,用途是什麼?

  3. CreateViewFromTag默認的View建立流程解析


CreateViewFromTag源碼解析

private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
        return createViewFromTag(parent, name, context, attrs, false);
    }複製代碼

createViewFromTag在LayoutInflater中存在重載,最終仍是會調用5個參數的createViewFromTag方法。

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {

        //解析view標籤
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }

        //若是須要該標籤與主題相關,須要對context進行包裝,將主題信息加入context包裝類ContextWrapper
        if (!ignoreThemeAttr) {
            final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
            final int themeResId = ta.getResourceId(0, 0);
            if (themeResId != 0) {
                context = new ContextThemeWrapper(context, themeResId);
            }
            ta.recycle();
        }

        //BlinkLayout是一種閃爍的FrameLayout,它包裹的內容會一直閃爍,相似QQ提示消息那種。
        if (name.equals(TAG_1995)) {
            return new BlinkLayout(context, attrs);
        }

        //設置Factory,來對View作額外的拓展,這塊屬於可定製的內容
        try {
            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);
            }

        //若是此時不存在Factory,無論Factory仍是Factory2,仍是mPrivateFactory都不存在,那麼會直接對name直接進行解析
            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                     //若是name中包含.即爲自定義View,不然爲原生的View控件
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

            return view;複製代碼

根據源碼能夠將createViewFromTag分爲三個流程:

  1. 對一些特殊標籤,作分別處理,例如:view,TAG_1995(blink)

  2. 進行對Factory、Factory2的設置判斷,若是設置那麼就會經過設置Factory、Factory2進行生成View

  3. 若是沒有設置Factory或Factory2,那麼就會使用LayoutInflater默認的生成方式,進行View的生成


createViewFromTag分析過程:

(1)處理view標籤

若是標籤的名稱是view,注意是小寫的view,這個標籤通常你們不太經常使用,具體的使用狀況以下:

<view
        class="RelativeLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></view>複製代碼

在使用時,至關於全部控件標籤的父類同樣,能夠設置class屬性,這個屬性會決定view這個節點會變成什麼控件。


(2)若是該節點與主題相關,則須要特殊處理

若是該節點與主題(Theme)相關,須要將context與theme信息包裝至ContextWrapper類。


(3)處理TAG_1995標籤

這就有意思了,TAG_1995指的是blink這個標籤,這個標籤感受使用的不多,以致於你們根本不知道。

這個標籤最後會被解析成BlinkLayout,BlinkLayout其實就是一個FrameLayout,這個控件最後會將包裹內容一直閃爍(就和電腦版QQ消息提示同樣),有空你們能夠自行嘗試下,很簡單,下面貼一下用法:

<blink
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="這個標籤會一直閃爍"/>

    </blink>複製代碼

(4)判斷其是否存在Factory或者Factory2

在這裏先對Factory進行判空,這裏無論Factory仍是Factory2(mPrivateFactory 就是Factory2),本質上都是一種擴展操做,提早解析name,而後直接將解析後的View返回。

Factory

public interface Factory {
        public View onCreateView(String name, Context context, AttributeSet attrs);
    }複製代碼

Factory2

public interface Factory2 extends Factory {

        public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
    }複製代碼

從這裏能夠看出,Factory2和Factory都是一個接口,須要本身實現,而Factory2和Factory的區別是Factory2繼承Factory,從而擴展出一個參數,就是增長了該節點的父View。

這裏我自定義了一個Factory,下面自定義解析View的過程:

@Override
    public View onCreateView(String name, Context context, AttributeSet attrs) {
        View view = null;
        try {
            if (-1 == name.lastIndexOf(".")) {
                if (name.equals("View") || name.equals("ViewGroup")) {
                    view = mInflater.createView(name, "android.view.", attrs);
                } else {
                    view = mInflater.createView(name, "android.widget.", attrs);
                }
            } else {
                if (name.contains(".")) {
                    String checkName = name.substring(name.lastIndexOf("."));
                    String prefix = name.substring(0, name.lastIndexOf("."));
                    view = mInflater.createView(checkName, prefix, attrs);
                }
            }

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        if(view != null){
            //在這裏能夠對View作一些額外的操做,而且可以得到View的屬性集,能夠作一些自定義操做。
            view.xxxxxx
        }

        return view;
    }複製代碼

從上面能夠看出,Factory和Factory2其實LayoutInflater解析View時的一種擴展實現,在這裏能夠額外的對View處理,設置Factory和Factory2須要經過setFactory()或者setFactory2()來實現。

setFactory()

public void setFactory(Factory factory) {
        //若是已經設置Factory,不能夠繼續設置Factory
        if (mFactorySet) {
            throw new IllegalStateException("A factory has already been set on this LayoutInflater");
        }
        if (factory == null) {
            throw new NullPointerException("Given factory can not be null");
        }
        //設置Factory會添加一個標記
        mFactorySet = true;
        if (mFactory == null) {
            mFactory = factory;
        } else {
            mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
        }
    }複製代碼

setFactory2()

public void setFactory2(Factory2 factory) {
        if (mFactorySet) {
            throw new IllegalStateException("A factory has already been set on this LayoutInflater");
        }
        if (factory == null) {
            throw new NullPointerException("Given factory can not be null");
        }
        //注意設置Factory和Factory2的標記是共用的
        mFactorySet = true;
        if (mFactory == null) {
            mFactory = mFactory2 = factory;
        } else {
            mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
        }
    }複製代碼

經過上面代碼能夠看出,Factory和Factory2只可以設置一次,而且Factory和Factory2兩者互斥,只能存在一個。

因此通常setFactory()或者setFactory2(),通常在cloneInContext()以後設置,這樣生成一個新的LayoutInflater,標記默認是false,纔可以設置。


(5)LayoutInflater內置的解析過程

若是Factory或者Factory2沒有設置,或者返回View爲null,纔會使用默認解析方式。

if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                        view = createView(name, null, attrs);
                    }複製代碼

這段就是對自定義View和原生的控件進行判斷,這裏給你們說明下原生控件和自定義View的name區別:

原生 :  RelativeLayout

自定義View : com.demo.guidepagedemo.customview.CustomImageView複製代碼

原生控件的解析方式 onCreateView :

protected View onCreateView(View parent, String name, AttributeSet attrs)
            throws ClassNotFoundException {
        return onCreateView(name, attrs);
    }複製代碼

而後調用的仍是2個參數的onCreateView()方法

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

能夠看到最終方法的指向仍是調用createView方法

public final View createView(String name, String prefix, 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 {
        //若是構造器不存在,這個就至關於Class以前是否被加載過,sConstructorMap就是緩存這些Class的Map
            if (constructor == null) {
                //經過前綴+name的方式去加載
                clazz = mContext.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name).asSubclass(View.class);
                //經過過濾去設置一些不須要加載的對象
                if (mFilter != null && clazz != null) {
                    boolean allowed = mFilter.onLoadClass(clazz);
                    if (!allowed) {
                        failNotAllowed(name, prefix, attrs);
                    }
                }
                constructor = clazz.getConstructor(mConstructorSignature);
                constructor.setAccessible(true);
                //緩存Class
                sConstructorMap.put(name, constructor);
            } else {
            //若是Class存在,而且加載Class的ClassLoader合法
            //這裏先判斷該Class是否應該被過濾
                if (mFilter != null) {
                    //過濾器也有緩存以前的Class是否被容許加載,判斷這個Class的過濾狀態
                    Boolean allowedState = mFilterMap.get(name);
                    if (allowedState == null) {
                        //加載Class對象操做
                        clazz = mContext.getClassLoader().loadClass(
                                prefix != null ? (prefix + name) : name).asSubclass(View.class);
                        //判斷Class是否可被加載
                        boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                        mFilterMap.put(name, allowed);
                        if (!allowed) {
                            failNotAllowed(name, prefix, attrs);
                        }
                    } else if (allowedState.equals(Boolean.FALSE)) {
                        failNotAllowed(name, prefix, attrs);
                    }
                }
            }

            Object[] args = mConstructorArgs;
            args[1] = attrs;

            //若是過濾器不存在,直接實例化該View
            final View view = constructor.newInstance(args);
            //若是View屬於ViewStub那麼須要給ViewStub設置一個克隆過的LayoutInflater
            if (view instanceof ViewStub) {
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
            }
            return view;複製代碼

上面代碼有點長,就直接在代碼裏面加註釋了,這裏額外說一下這個方法:

判斷ClassLoader是否安全的verifyClassLoader :

private final boolean verifyClassLoader(Constructor<? extends View> constructor) {
        final ClassLoader constructorLoader = constructor.getDeclaringClass().getClassLoader();
        if (constructorLoader == BOOT_CLASS_LOADER) {
            //這裏注意BootClassLoader是至關於全部派生出來的ClassLoader的原始基類,全部的ClassLoader都是根據其衍生的。
            return true;
        }
        //這裏是一個遍歷操做,一直在遍歷加載mContext的ClassLoader的繼承樹,一直在往上尋找,若是
        //constructor的ClassLoader與繼承樹中某個ClassLoader相同就說明這個ClassLoader是安全的
        ClassLoader cl = mContext.getClassLoader();
        do {
            if (constructorLoader == cl) {
                return true;
            }
            cl = cl.getParent();
        } while (cl != null);
        return false;
    }複製代碼

這裏簡單說明下,幾種ClassLoader的做用:

(1)BootClassLoader 加載Android FrameWork層的一些字節碼文件

(2)PathClassLoader 加載已經安裝到系統上的應用App(apk)上的字節碼文件

(3)DexClassLoader 加載指定目錄中的Class字節碼文件

(4)BaseDexClassLoader 是PathClassloader和DexClassLoader的父類複製代碼

通常的App剛啓動的時候,就會有兩個ClassLoader被加載,分別是PathClassLoader、DexClassLoader而這兩個ClassLoader都是繼承BaseDexClassLoader.

而BaseDexClassLoader繼承的是ClassLoader,可是在ClassLoader中getParent()方法賦予其Parent爲BootClassLoader,這個若是你們感興趣,能夠自行查閱ClassLoader。


流程圖

由於圖有點大,建議你們查看先拷貝到本地,或者放大120%,就能夠清晰觀看了。

這裏寫圖片描述
這裏寫圖片描述
相關文章
相關標籤/搜索