Android 源碼分析一 View 建立

最近看了些 View 相關的源碼,相比以前,有一些新的認知。爭取經過一次整理,能系統瞭解 Android View 加載和顯示的相關過程,記錄下來,共勉。接下來的全部源碼基於 Android API 27 Platformjava

對於 View 建立,通俗說其實就兩種方式,一種是直接經過 new 關鍵詞直接建立對象,另外就是經過 xml 填充一個 View。第一種方式寫起來最簡易,可是,也有一些代價,好比說全部屬性都要一個個設置,通用 style 也沒辦法使用。第二種方式最傳統,也是接下來重點關注的方式。android

構造方法參數

寫過自定義 View 都知道,咱們通常須要實現三個構造方法,固然,若是你使用 Kotlin 以後,這種狀況能夠有一些改善,相似這樣:程序員

class TestView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = -1)canvas

第一個參數上下文,這個沒啥問題,第二個參數,AttributeSet 屬性集合,第三個參數,defStyleAttr 應該是默認 style 的 id。緩存

反正至少得有這三個參數,並且,通常來講,咱們第三個參數也沒怎麼使用,默認使用的 -1 來佔位,第二個參數通常咱們也是使用 null 來默認佔位。它們到底有什麼用呢?能夠不寫對應的構造方法嗎?app

若是咱們自定義 View,只有上下文那個構造方法時,經過 xml 方式填充時就會出錯:async

Caused by: android.view.InflateException: Binary XML file line #12: Error inflating class com.lovejjfg.circle.TestView
 Caused by: java.lang.NoSuchMethodException: <init> [class android.content.Context, interface android.util.AttributeSet]
複製代碼

簡單說就是找不到兩個參數的那個構造方法,那麼這個構造方法到底在哪裏被調用呢?ide

###LayoutInflater 使用 xml 填充佈局,就必須得使用 LayoutInflater ,等等,Activity 設置佈局是經過 setContentView() 更新的,看看它的代碼呢。佈局

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

LayoutInflator 建立

/**
 * 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;
}
複製代碼

LayoutInflater 也是一個系統提供的遠程服務。ui

inflate() 方法

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

這個方法接收三個參數,一路點進去,首先會先經過傳入的 layoutId 構建 XmlParser :

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    final XmlResourceParser parser = res.getLayout(resource);
    try {
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}
複製代碼

XML 解析不展開說,接下來開始真正的 inflate() 方法:

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
        final Context inflaterContext = mContext;
        //1.AttributeSet 在這裏建立出來
        final AttributeSet attrs = Xml.asAttributeSet(parser);
        Context lastContext = (Context) mConstructorArgs[0];
        mConstructorArgs[0] = inflaterContext;
        View result = root;

        try {
            final String name = parser.getName();
            //2.merge 標籤的注意事項
            if (TAG_MERGE.equals(name)) {
                if (root == null || !attachToRoot) {
                    throw new InflateException("<merge /> can be used only with a valid "
                            + "ViewGroup root and attachToRoot=true");
                }
                rInflate(parser, root, inflaterContext, attrs, false);
            } else {
                // Temp is the root view that was found in the xml
                //3.真正的建立方法
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                ViewGroup.LayoutParams params = null;
                if (root != null) {
                    // Create layout params that match root, if supplied
                    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);
                    }
                }
                //4.建立子View
                // Inflate all children under temp against its context.
                rInflateChildren(parser, temp, attrs, true);
                // We are supposed to attach all the views we found (int temp)
                // to root. Do that now.
                //5.attachToRoot 參數做用
                if (root != null && attachToRoot) {
                    root.addView(temp, params);
                }
                // Decide whether to return the root that was passed in or the
                // top view found in xml.
                //5.attachToRoot 參數做用
                if (root == null || !attachToRoot) {
                    result = temp;
                }
            }

        } catch (XmlPullParserException e) {
          ...
        } finally {
          ...
        }
        return result;
    }
}
複製代碼

有五個注意點,已經分別在代碼中加上對應註釋,第一,View 建立的第二個參數 AttributeSet ,在這個方法中被建立出來了。第二,merge 標籤在這裏首次現身,詳細放到下面「特殊標籤處理」展開講。第三, createViewFromTag() 該方法纔是真正建立 tempView 的方法。第四,rInflateChildren() 方法用於填充子 View 的方法。第五,attachToRoot 參數決定是否把 temp 直接加到 rootView 上,決定是返回 rootView 仍是填充出來的 tempView

接着看真正建立 tempViewcreateViewFromTag() 方法。

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
        boolean ignoreThemeAttr) {
    ...
    //1.彩蛋
    if (name.equals(TAG_1995)) {
        // Let's party like it's 1995!
        return new BlinkLayout(context, attrs);
    }
    try {
        View view;
        //2. 各類 factory
        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 {
                //3.自定義View的差別
                if (-1 == name.indexOf('.')) {
                    view = onCreateView(parent, name, attrs);
                } else {
                    view = createView(name, null, attrs);
                }
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        }

        return view;
    } catch (Exception e) {
       ...
    }
}
複製代碼

彩蛋分析

三個點,第一,竟然看到一個彩蛋, private static final String TAG_1995 = "blink" Google 人 一下注釋,你會看到這個提交地址 戳戳戳,若是解析到這個標籤的話,會直接建立出 BlinkLayout 返回,blink 就是閃爍的意思,看註釋 // Let's party like it's 1995! ,哈哈那種一閃一閃的感受。那麼這個效果到底怎麼實現的呢?直接看代碼:

public BlinkLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        mHandler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                if (msg.what == MESSAGE_BLINK) {
                    if (mBlink) {
                        mBlinkState = !mBlinkState;
                        makeBlink();
                    }
                    invalidate();
                    return true;
                }
                return false;
            }
        });
    }

    private void makeBlink() {
        Message message = mHandler.obtainMessage(MESSAGE_BLINK);
        mHandler.sendMessageDelayed(message, BLINK_DELAY);
    }
    @Override
    protected void dispatchDraw(Canvas canvas) {
        if (mBlinkState) {
            super.dispatchDraw(canvas);
        }
    }
複製代碼

其實很簡單,就是經過 Handler 來控制是否調用 dispatchDraw() 方法,不調用,就啥都不繪製,調用就會繪製出來,那這就是一閃一閃亮晶晶的效果咯,真是程序員的小巧思啊。

另外注意這裏 Handler 的建立方式,使用的是 Callback,並非建立一個匿名內部類,複寫 handleMessage() 方法。

LayoutInflater Factory

彩蛋說完,迴歸總體,第二,出現了 factory. onCreateView() 方法。並且吧,這個factory還不止一個。那這是什麼操做呢?仔細看下 public interface Factory2 extends Factory private static class FactoryMerger implements Factory2 它們是這麼定義,Factory中只有一個方法:

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

Factory2 其實重載了一個新的方法:

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

至於 FactoryMerger 其實就是用於咱們添加咱們指定的 Factory 去建立對應 View

那麼問題來了,爲何要整兩個 Factory 呢?

看看 Factory 的具體實現類,首先有兩個須要重點關注,一個是 Activity,一個是FragmentManager

Activity 中,看到有這兩個方法的實現:

/**
 * Standard implementation of
 * {@link android.view.LayoutInflater.Factory#onCreateView} used when
 * inflating with the LayoutInflater returned by {@link #getSystemService}.
 * This implementation does nothing and is for
 * pre-{@link android.os.Build.VERSION_CODES#HONEYCOMB} apps.  Newer apps
 * should use {@link #onCreateView(View, String, Context, AttributeSet)}.
 *
 * @see android.view.LayoutInflater#createView
 * @see android.view.Window#getLayoutInflater
 */
@Nullable
public View onCreateView(String name, Context context, AttributeSet attrs) {
    return null;
}

/**
 * Standard implementation of
 * {@link android.view.LayoutInflater.Factory2#onCreateView(View, String, Context, AttributeSet)}
 * used when inflating with the LayoutInflater returned by {@link #getSystemService}.
 * This implementation handles <fragment> tags to embed fragments inside
 * of the activity.
 *
 * @see android.view.LayoutInflater#createView
 * @see android.view.Window#getLayoutInflater
 */
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);
}
複製代碼

簡單理解就是,Factory 是用於低版本,高版本是 Factory2 ,而後,Factory2Activity中主要用於解析 fragment 標籤,其餘它不 care(到這裏,你可能有個疑問,Activity 實現了這個接口,可是是啥時候設置直接到 LayoutInflater 中的呢?這個問題也放下面單獨講)。

View 真正的建立

這麼說下來,若是不是 fragment 標籤 ,那就會到剛剛的第三點,額,戰線有點兒長了,若是都已經忘記第三點就往上面翻再看下。在第三點以前,還有一個 mPrivateFactory 攔路虎,它還能夠再浪一把,這個咱們也先跳過,假定到這裏都沒建立 View,開始第三點。

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

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

若是不包含 . ,就使用 onCreateView(),這個方法其實就是給它把對應路徑補全。使用系統控件時,咱們並無寫出全路徑,例如 TextView ,而咱們自定義 View 時都是寫的全路徑,因此就直接執行 createView(name, null, attrs) 方法。

public final View createView(String name, String prefix, AttributeSet attrs)
        throws ClassNotFoundException, InflateException {
    //1.緩存中取 Constructor        
    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
            //2. 加載對應的 Class
            clazz = mContext.getClassLoader().loadClass(
                    prefix != null ? (prefix + name) : name).asSubclass(View.class);
            ...
            constructor = clazz.getConstructor(mConstructorSignature);
            constructor.setAccessible(true);
            //3.加入緩存
            sConstructorMap.put(name, constructor);
        } 
        
        ...

        Object lastContext = mConstructorArgs[0];
        if (mConstructorArgs[0] == null) {
            // Fill in the context if not already within inflation.
            mConstructorArgs[0] = mContext;
        }
        //4.指定參數
        Object[] args = mConstructorArgs;
        args[1] = attrs;
        //5.反射建立
        final View view = constructor.newInstance(args);
        //6.ViewStub處理
        if (view instanceof ViewStub) {
            // Use the same context when inflating ViewStub later.
            final ViewStub viewStub = (ViewStub) view;
            viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
        }
        mConstructorArgs[0] = lastContext;
        return view;

    } catch (Exception e) {
        ...
    } 
}
複製代碼

看到 final 時,隱約就以爲應該找到真正建立的方法。總的來講就是經過 ClassLoader 拿到字節碼,而後獲得構造方法 Constructor 對象,由於反射是有額外成本消耗,因此這裏有作緩存。接下來就是真正的反射建立,注意,反射建立時,使用的是兩個參數的構建方法,第一個是 Context 上下文,第二個就是第一步就建立出來的 AttributeSet ,這個老將在這裏終於派上用場。這也解釋了開頭提出那個問題,若是不指定帶有 Context AttributeSet 兩個參數的構造方法,LayoutInflator 是沒法建立出對應的 View,反射建立會在這裏拋出上文提到那個異常。

到這裏,tempView 終於建立成功。能夠先簡單總結下:LayoutInflator 填充 View 的過程,第一步加載佈局資源,生 XmlParserAttributeSet,而後根據不版本和不一樣標籤,選擇是經過 Factory 的實現類去建立(fragment標籤就是讓Activity去建立)仍是本身建立。本身建立的話,就是經過反射,調用View 的兩個參數的構造方法建立。

子 View 建立

tempView 建立後,還要解析它的子 View,過程固然重複相似,咱們知道在 View 建立填充完畢後中,有一個 onFinishInflate() 回調,看看它啥時候被調用。回到 inflate() 方法中的第四點,rInflateChildren(parser, temp, attrs, true)

final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
        boolean finishInflate) throws XmlPullParserException, IOException {
    rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
複製代碼

這個方法最後調用 rInflate() ,接下來再看看這個方法的實現。

void rInflate(XmlPullParser parser, View parent, Context context,
        AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

    final int depth = parser.getDepth();
    int type;
    boolean pendingRequestFocus = false;

    while (((type = parser.next()) != XmlPullParser.END_TAG ||
            parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

        ...

        final String name = parser.getName();
        //1. focus 標籤
        if (TAG_REQUEST_FOCUS.equals(name)) {
            pendingRequestFocus = true;
            consumeChildElements(parser);
        //2. tag 標籤
        } else if (TAG_TAG.equals(name)) {
            parseViewTag(parser, parent, attrs);
        //3. include 標籤
        } else if (TAG_INCLUDE.equals(name)) {
            if (parser.getDepth() == 0) {
                throw new InflateException("<include /> cannot be the root element");
            }
            parseInclude(parser, context, parent, attrs);
        //4. merge 標籤
        } else if (TAG_MERGE.equals(name)) {
            throw new InflateException("<merge /> must be the root element");
        } else {
            //5. 建立 view 遞歸解析
            final View view = createViewFromTag(parent, name, context, attrs);
            final ViewGroup viewGroup = (ViewGroup) parent;
            final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
            rInflateChildren(parser, view, attrs, true);
            viewGroup.addView(view, params);
        }
    }

    if (pendingRequestFocus) {
        parent.restoreDefaultFocus();
    }
    // 6.回調 onFinishInflate
    if (finishInflate) {
        parent.onFinishInflate();
    }
}
複製代碼

特殊標籤

額,先忽略那些 if 條件,直接先看 else,以前的套路建立 View 後再遞歸調用 rInflateChildren() ,不過須要注意再從新調用 rInflateChildren() 時,parent 參數已是剛剛新建立的 view 啦。最後回調onFinishInflate() 方法。

tag requestFocus 標籤

接着,再說說前面的這些 if 語句,除了咱們熟悉的 include merge 標籤檢查,這裏竟然還有什麼 tag requestFocus 等冷門標籤, 我反正有點兒震驚,層度不低於那個彩蛋。

<tag
    android:id="@id/test1"
    android:value="testTagValue"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"/>
複製代碼

而後嘗試了下 tag 標籤,結果是 OK 的,我能夠直接在父佈局中使用 getTag(R.id.test1) 拿到我在 xml 中設置的 value 。 不過具體使用場景我着實沒有想到,requestFocus 也是如此。

merge 標籤

咱們知道,merge 標籤用於減小層級,必須是頂級標籤,從上面代碼就能夠看到對頂級標籤的檢測。減小層級的話,就又要回到 inflate() 方法中第二點。

//2.merge 標籤的注意事項
        if (TAG_MERGE.equals(name)) {
            if (root == null || !attachToRoot) {
                throw new InflateException("<merge /> can be used only with a valid "
                        + "ViewGroup root and attachToRoot=true");
            }
            rInflate(parser, root, inflaterContext, attrs, false);
        }
複製代碼

若是解析到 merge 標籤,會直接調用 rInflate() 方法填充下一層級,parent 參數也不會變,因此,merge 標籤下面的內容直接就加到了 rootView 中。因此,這種狀況,上一層確定不能爲空,傳入的 parent 確定不能爲空。

include 標籤

private void parseInclude(XmlPullParser parser, Context context, View parent,
        AttributeSet attrs) throws XmlPullParserException, IOException {
    int type;

    if (parent instanceof ViewGroup) {
        ...
        final XmlResourceParser childParser = context.getResources().getLayout(layout);

        try {
            final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
            ...

            final String childName = childParser.getName();
            //1. merge 標籤直接填充
            if (TAG_MERGE.equals(childName)) {
                // The <merge> tag doesn't support android:theme, so
                // nothing special to do here.
                rInflate(childParser, parent, context, childAttrs, false);
            } else {
                final View view = createViewFromTag(parent, childName,
                        context, childAttrs, hasThemeOverride);
                final ViewGroup group = (ViewGroup) parent;

                final TypedArray a = context.obtainStyledAttributes(
                        attrs, R.styleable.Include);
                //2.include 標籤上的 id
                final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
                //3.include 標籤上的 visibility
                final int visibility = a.getInt(R.styleable.Include_visibility, -1);
                a.recycle();
                ViewGroup.LayoutParams params = null;
                try {
                    params = group.generateLayoutParams(attrs);
                } catch (RuntimeException e) {
                    // Ignore, just fail over to child attrs.
                }
                if (params == null) {
                    params = group.generateLayoutParams(childAttrs);
                }
                view.setLayoutParams(params);

                // Inflate all children.
                rInflateChildren(childParser, view, childAttrs, true);
                //4.覆蓋 id
                if (id != View.NO_ID) {
                    view.setId(id);
                }
                //5.設置可見性
                switch (visibility) {
                    case 0:
                        view.setVisibility(View.VISIBLE);
                        break;
                    case 1:
                        view.setVisibility(View.INVISIBLE);
                        break;
                    case 2:
                        view.setVisibility(View.GONE);
                        break;
                }

                group.addView(view);
                
            } finally {
                childParser.close();
            }
        }
        } else {
            throw new InflateException("<include /> can only be used inside of a ViewGroup");
        }
    ...
}
複製代碼

parseInclude() 方法中,若是是 merge 標籤,直接再次解析,而後會取出 include 標籤上的 idvisibility 屬性,若是 include 標籤上面有 id,那麼會從新設置給 View,那麼以前設置的 id 就會失效,而後更新 visibility 屬性。

ViewStub 標籤

咱們知道,ViewStub 標籤是用來佔位,實現 View 懶加載。那麼到底實現的呢?先看代碼。

...
        //6.ViewStub處理
        if (view instanceof ViewStub) {
            // Use the same context when inflating ViewStub later.
            final ViewStub viewStub = (ViewStub) view;
            viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
        }
複製代碼

根據這個代碼,明顯看出 ViewStub 標籤和 include 或者 merge 不同,它是 View 的子類,是一個真實 View

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(0, 0);
}

@Override
public void draw(Canvas canvas) {
}

@Override
protected void dispatchDraw(Canvas canvas) {
}
複製代碼

ViewStub 默認寬高都是 0 ,draw() (注意是 draw() 而不是 onDraw() 方法)等方法都是空實現,真就是一個殼。接着看它的 inflate () 方法實現。

public View inflate() {
    final ViewParent viewParent = getParent();
    ...
        if (mLayoutResource != 0) {
            final ViewGroup parent = (ViewGroup) viewParent;
            //1.填充真實佈局
            final View view = inflateViewNoAdd(parent);
            //2.替換本身
            replaceSelfWithView(view, parent);
            //3.建立弱引用
            mInflatedViewRef = new WeakReference<>(view);
            ...
            return view;
        } 
    ...
}

private View inflateViewNoAdd(ViewGroup parent) {
    final LayoutInflater factory;
    ...
    //1.填充真實佈局
    final View view = factory.inflate(mLayoutResource, parent, false);
    if (mInflatedId != NO_ID) {
        view.setId(mInflatedId);
    }
    return view;
}

private void replaceSelfWithView(View view, ViewGroup parent) {
    final int index = parent.indexOfChild(this);
    //1.移除本身
    parent.removeViewInLayout(this);

    final ViewGroup.LayoutParams layoutParams = getLayoutParams();
    //2.添加真實佈局
    if (layoutParams != null) {
        parent.addView(view, index, layoutParams);
    } else {
        parent.addView(view, index);
    }
}
複製代碼

看完仍是那話,ViewStub 就是一個殼,先佔一個坑,在調用 inflate() 以後才加載真實佈局,而後替換掉本身,從而實現懶加載。說到這裏,還要看一下它的 setVisibility() 方法。

@Override
@android.view.RemotableViewMethod(asyncImpl = "setVisibilityAsync")
public void setVisibility(int visibility) {
    //1.調用 inflate() 以後 mInflatedViewRef 不爲空
    if (mInflatedViewRef != null) {
        View view = mInflatedViewRef.get();
        if (view != null) {
            view.setVisibility(visibility);
        } else {
            throw new IllegalStateException("setVisibility called on un-referenced view");
        }
    } else {
        super.setVisibility(visibility);
        //2.第一次設置可見時觸發 inflate()
        if (visibility == VISIBLE || visibility == INVISIBLE) {
            inflate();
        }
    }
}
複製代碼

第一次看到這個方法時,我在想,咱們能夠直接經過 ViewStubVISIBLE GONE 來控制顯示和消失啊,爲何還要拿到真實佈局來控制呢?後面嘗試以後才意識到一個問題,上面的 replaceSelfWithView() 方法已經將本身刪除,因此,當咱們調用 viewStub.setVisibilty(View.VISIBLE) 以後,viewStub 這個對象已經被置空,不能再次使用。這個想法無法實現,並且更尷尬的是,若是你直接調用viewStub.setVisibilty(View.INVISIBLE) 以後,viewStub 置空,可是你又沒有真實 view 引用,你就不能直接讓它再次展現出來了。是否是以爲這裏有個坑?其實這個時候你可使用findView查找了,因此這個坑不存在。不過這也解釋了 ViewStub 爲何要用弱引用來持有真實 View

Factory 拓展

來填一填上文 Factory 的坑,以前說到 Activity 實現了 Factory 接口,可是何時,怎麼把本身設置到 LayoutInflator 中的呢?咱們直接到 Activityattach() 方法中。

final void attach(Context context, ActivityThread aThread,
        Instrumentation instr, IBinder token, int ident,
        Application application, Intent intent, ActivityInfo info,
        CharSequence title, Activity parent, String id,
        NonConfigurationInstances lastNonConfigurationInstances,
        Configuration config, String referrer, IVoiceInteractor voiceInteractor,
        Window window, ActivityConfigCallback activityConfigCallback) {
    
    ...
    mWindow.getLayoutInflater().setPrivateFactory(this);
    ...
}
複製代碼

Activityattach() 方法中,會調用 setPrivateFactory(this) 方法把本身設置到 Layout Inflator 中。

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

看這個代碼,它是設置的 mPrivateFactory ,這個優先級是最低的,前面介紹時第二點各類 factory 中,首先是 mFactory2mFactory ,這兩個 factory 是提供方法讓咱們咱們設置更改的,不過須要注意只能設置一次,因此,先打印看看 Activity 中設置狀況。

println("factory2:${LayoutInflater.from(this).factory2}")
    println("factory:${LayoutInflater.from(this).factory}")

com.lovejjfg.circle I/System.out: factory2:null
com.lovejjfg.circle I/System.out: factory:null
複製代碼

Activity 中,默認都沒有設置,因此你徹底能夠調用 setFactory() 方法設置咱們指定的Factory 來解析對應 View注意:上面演示時使用的是 Activity,但咱們通常不會直接繼承 Activity ,由於新的 appcompat 包中的那些新控件例 Toolbar 等等,都須要使用 AppCompatActivity 搭配上 appcompat 主題。這種狀況下,再看看相關日誌輸出。

com.lovejjfg.circle I/System.out: factory2:android.support.v7.app.AppCompatDelegateImplN@5307686
com.lovejjfg.circle I/System.out: factory:android.support.v7.app.AppCompatDelegateImplN@5307686
複製代碼

已經都設置了,並且這個變量只能設置一次,設置時會有檢查,因此在這種狀況下,咱們基本上沒辦法再去設置新的 Factory 。既然它已經設置過,那麼就弄明白兩個問題,第一,哪裏設置,第二,有什麼特別的用途。也不賣關子,第一個問題,在 AppcompatActivityonCreate() 方法中。

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

再貼一個 LayoutInflaterCompat 代碼片斷,這裏強調有 framework bug 修復,已經經過 反射 強制更新 Factory

/**
 * For APIs < 21, there was a framework bug that prevented a LayoutInflater's
 * Factory2 from being merged properly if set after a cloneInContext from a LayoutInflater
 * that already had a Factory2 registered. We work around that bug here. If we can't we
 * log an error.
 */
static void forceSetFactory2(LayoutInflater inflater, LayoutInflater.Factory2 factory) {
    if (!sCheckedField) {
        try {
            sLayoutInflaterFactory2Field = LayoutInflater.class.getDeclaredField("mFactory2");
            sLayoutInflaterFactory2Field.setAccessible(true);
        } catch (NoSuchFieldException e) {
            Log.e(TAG, "forceSetFactory2 Could not find field 'mFactory2' on class "
                    + LayoutInflater.class.getName()
                    + "; inflation may have unexpected results.", e);
        }
        sCheckedField = true;
    }
    if (sLayoutInflaterFactory2Field != null) {
        try {
            sLayoutInflaterFactory2Field.set(inflater, factory);
        } catch (IllegalAccessException e) {
            Log.e(TAG, "forceSetFactory2 could not set the Factory2 on LayoutInflater "
                    + inflater + "; inflation may have unexpected results.", e);
        }
    }
}
複製代碼

第二點,有什麼用呢,前面提過,Activity 中,其實就判斷是不是 fragment 標籤,不是的話,就返回空,不操做。在 AppcompatActivity 中,createView() 會執行到 AppCompatViewInflater 中的 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;
        ...
        default:
            // The fallback that allows extending class to take over view inflation
            // for other tags. Note that we don't check that the result is not-null.
            // That allows the custom inflater path to fall back on the default one
            // later in this method.
            view = createView(context, name, attrs);
    }

    ...

    return view;
}

@NonNull
protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
    return new AppCompatTextView(context, attrs);
}
複製代碼

能夠看到,這裏把 TextView 標籤原本應該建立的 TextView 換成了 AppCompatTextView 類。

appcompat.png

直接貼個圖,簡單理解,Google 官方推出 AppCompat 組件以後,新增一些新特性。出於對咱們開發者關照(讓你一個個 xml 去替換估計你也不會幹),因此就想出經過 LayoutInflatorsetFactory() 這個方法直接添加本身的轉換工廠,這樣神不知鬼不覺的就讓你的舊控件就能使用新特性(咱們就能夠偷懶)。因此,在 AppCompatActivityAppCompaDialog 中,不用刻意去寫 AppCompatXxxView ,它會自動轉換。截圖中最後一句有強調,咱們只須要注意在自定義 View時才須要額外設置繼承 AppCompatXxxView ,到這裏,Android Studio 給你警告的緣由也大白。

Fragment View 建立

最後,再補全 FragmentView 的建立過程。 前文分析 Activity 中只解析 fragment 標籤。最後會調用到 FragmentManager 中的 onCreateView() 方法。

@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
    if (!"fragment".equals(name)) {
        return null;
    }
    ...
    //建立 Fragment
    if (fragment == null) {
        fragment = mContainer.instantiate(context, fname, null);
        ...
        fragment.onInflate(mHost.getContext(), attrs, fragment.mSavedFragmentState);
        addFragment(fragment, true);

    }
  ...
    if (mCurState < Fragment.CREATED && fragment.mFromLayout) {
        moveToState(fragment, Fragment.CREATED, 0, 0, false);
    } else {
        moveToState(fragment);
    }
   ...
}
複製代碼

Fragment 建立不展開說了,用了反射,之後篇章有空再細聊。下面調用 moveToState() 方法,state 設置的是 Fragment.CREATED

//FragmentManager
void moveToState(Fragment f, int newState, int transit, int transitionStyle,
        boolean keepActive) {
    ...
     f.mView = f.performCreateView(f.performGetLayoutInflater(
                                f.mSavedFragmentState), container, f.mSavedFragmentState);
    ....
  }

//Fragment
View performCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
        @Nullable Bundle savedInstanceState) {
    if (mChildFragmentManager != null) {
        mChildFragmentManager.noteStateNotSaved();
    }
    mPerformedCreateView = true;
    return onCreateView(inflater, container, savedInstanceState);
}
複製代碼

到這裏,就回調咱們熟悉的 onCreateView(inflater, container, savedInstanceState) 方法,完工。

相關文章
相關標籤/搜索