探究Android View 繪制流程,Xml 文件到 View 對(duì)象的轉(zhuǎn)換過程

基于 Android API 26 Platform 源碼

寫作背景

Android 開發(fā)框架中,使用 Xml 文件描述 Ui 頁面,通過setContentView(resId) 或者LayoutInflater.inflate(resId,……)的方式把 Xml 文件描述的頁面轉(zhuǎn)換成 Java 對(duì)象。Xml 文件加上 AndroidStudio 提供的預(yù)覽功能,使得 Android 開發(fā)過程中頁面和業(yè)務(wù)邏輯可以并行開發(fā),極大地提高了開發(fā)效率。

但是大部分 Android 工程師對(duì) xml 文件如何轉(zhuǎn)換成 Java 不是十分了解,本文將帶大家一起探究 View 從 xml 文件到 Java 對(duì)象的轉(zhuǎn)換過程

xml 轉(zhuǎn)成成 Java 對(duì)象有幾種方式?

我們先羅列一下 xml 轉(zhuǎn)換成 Java 對(duì)象的方式

1. 在 Activity中調(diào)用 setContentView(resId)
2. LayoutInflater.from(context).inflate(resId,……) 

跟蹤一下 Activity.setContentView(resId)

我們一般在項(xiàng)目使用的 Activity 可能是

1. android.support.v7.app.AppCompatActivity
2. android.support.v4.app.FragmentActivity
3. android.app.Activity
4. 其他 Activity

所有的 Activity 都是 android.app.Activity 的子類。

但是!每個(gè)繼承 android.app.Activity 的子類 setContentView(resId) 實(shí)現(xiàn)方式都被重載了。我們這里先看最基礎(chǔ)的 android.app.Activity

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

查看一下 getWindow()源碼

public Window getWindow() {
    return mWindow;
}

全局搜索 mWindow 對(duì)象賦值的地方找到以下代碼

mWindow = new PhoneWindow(this, window, activityConfigCallback);

這里 PhoneWindow 的源碼在 sdk 里面是隱藏的,我們?nèi)?androidxref ->PhoneWindow.java 查看 PhoneWindow.setContentView(layoutResID)

@Override
public void setContentView(int layoutResID) {
    // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
    // decor, when theme attributes and the like are crystalized. Do not check the feature
    // before this happens.
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
}

當(dāng)我們沒有設(shè)置轉(zhuǎn)場(chǎng)動(dòng)畫的時(shí)候會(huì)執(zhí)行

mLayoutInflater.inflate(layoutResID, mContentParent);

在 PhoneWindow 的構(gòu)造函數(shù)中我們找到了 mLayoutInflater 對(duì)象賦值語句

public PhoneWindow(Context context) {
    super(context);
    mLayoutInflater = LayoutInflater.from(context);
}

所以我們得出一個(gè)結(jié)論 Activity.setContentView(resId) 最終還是使用LayoutInflater.from(context).inflate(resId, ……)

再回頭看下 android.support.v7.app.AppCompatActivityandroid.support.v4.app.FragmentActivity 我們發(fā)現(xiàn) android.support.v4.app.FragmentActivity 沒有重載 android.app.Activity.setContentView(resId) 但是 android.support.v7.app.AppCompatActivity 重載了

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

再跟蹤一下源代碼我們發(fā)現(xiàn)最終會(huì)調(diào)用到 android.support.v7.app.AppCompatDelegateImplV9.setContentView(resId)

@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();
}

這里我們又發(fā)現(xiàn)了 LayoutInflater 的身影。

這里我們可以總結(jié)一下 xml 轉(zhuǎn)成成 Java 對(duì)象是通過 LayoutInflater 的 inflate() 方法來完成的

LayoutInflater 對(duì)象如何實(shí)例化

看下一下 LayoutInflater 的源碼第一行

public abstract class LayoutInflater {……}

LayoutInflater 是一個(gè)抽象類, 抽象類是不能實(shí)例化的

confuse.jpg

先想一下 LayoutInflater 對(duì)象獲取的方式

1. 在 Activity 中通過 getLayoutInflater() 獲取
2. 通過 LayoutInflater.from(context) 獲取
3.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) 獲取

看 Activity 的 getLayoutInflater()

public LayoutInflater getLayoutInflater() {
    return getWindow().getLayoutInflater();
}    

這里我們就可以看出 Activity 通過 getLayoutInflater() 獲取的是 PhoneWindow 的 mLayoutInflater (如果忘記了可以往上翻一下,或者去參考資料的鏈接里找找源碼)

再看一下 LayoutInflater.from(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;
}

此時(shí),我們必須請(qǐng)出柯南君幫我們宣布


kenan_01.jpg

真相只有一個(gè)!最終都是通過服務(wù)獲取 LayoutInflater 實(shí)例對(duì)象

下一步,源碼追蹤context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)
這里先說明一個(gè)前提,context 的實(shí)現(xiàn)類是 ContextImpl

如果對(duì)該前提有疑問請(qǐng)移步 Android Context完全解析,你所不知道的Context的各種細(xì)節(jié)

所以我們直接查看 ContextImpl.getSystemService(Context.LAYOUT_INFLATER_SERVICE)

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

繼續(xù)跟蹤 SystemServiceRegistry

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

這時(shí)候我們?cè)?SystemServiceRegistry 類停留一下,發(fā)現(xiàn)這里似乎只注冊(cè)各種系統(tǒng)服務(wù)的地方。我們找到了 Context.LAYOUT_INFLATER_SERVICE 注冊(cè)代碼。

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

 private static <T> void registerService(String serviceName, Class<T> serviceClass,
         ServiceFetcher<T> serviceFetcher) {
     SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
     SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
 }

然后我們終于找到 LayoutInflater 的實(shí)現(xiàn)類是 PhoneLayoutInflater

此時(shí)我們可以休息一下,喝口水,上個(gè)衛(wèi)生間,進(jìn)入下個(gè)階段

LayoutInflater 讀取 xml 文件并創(chuàng)建 View 對(duì)象

LayoutInflater.inflate()

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root)
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)

再去源碼查看一下,發(fā)現(xiàn)兩個(gè)方法其實(shí)只有一個(gè)方法是核心,另一個(gè)只是做了一下封裝,讓我們少傳入一個(gè)參數(shù)。

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

所以我們重點(diǎn)看一下 inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) 的源碼

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) + ")");
    }

    final XmlResourceParser parser = res.getLayout(resource);
    try {
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}

我們看到首先通過 res 對(duì)象把 resId 指向的 xml 文件轉(zhuǎn)換為 XmlResourceParser 然后執(zhí)行 inflate(parser, root, attachToRoot) 方法,該方法比較長(zhǎng),這里只貼出核心步驟。

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
        
        final Context inflaterContext = mContext;
        final AttributeSet attrs = Xml.asAttributeSet(parser);
        Context lastContext = (Context) mConstructorArgs[0];
        mConstructorArgs[0] = inflaterContext;
        View result = root;

        try {
            ……

            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
                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);
                    }
                }
                ……

                // 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.
                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.
                if (root == null || !attachToRoot) {
                    result = temp;
                }
            }

        } …… 
          省略異常處理部分
          ……

        return result;
    }
}

以上步驟還是很長(zhǎng),我們將拆分幾部分分析。

第一部分
    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);
    }

如果 xml 根標(biāo)簽是 merge,則 root 不能為空, attachToRoot 必須是 true。

然后執(zhí)行 rInflate(parser, root, inflaterContext, attrs, false)

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) {

        if (type != XmlPullParser.START_TAG) {
            continue;
        }

        final String name = parser.getName();

        if (TAG_REQUEST_FOCUS.equals(name)) {
            pendingRequestFocus = true;
            consumeChildElements(parser);
        } else if (TAG_TAG.equals(name)) {
            parseViewTag(parser, parent, attrs);
        } else if (TAG_INCLUDE.equals(name)) {
            if (parser.getDepth() == 0) {
                throw new InflateException("<include /> cannot be the root element");
            }
            parseInclude(parser, context, parent, attrs);
        } else if (TAG_MERGE.equals(name)) {
            throw new InflateException("<merge /> must be the root element");
        } else {
            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();
    }

    if (finishInflate) {
        parent.onFinishInflate();
    }
}

上面這個(gè)方式我們需要重點(diǎn)記一下

1. 遍歷該節(jié)點(diǎn)的子節(jié)點(diǎn)
2. 子節(jié)點(diǎn)有 "requestFocus"、"tag"、""、"include" 
3. 子節(jié)點(diǎn)不能是 "merge"
4. 子節(jié)點(diǎn)的其他情況,則是各種 View 的標(biāo)簽
5. View 標(biāo)簽和  "include" 標(biāo)簽會(huì)創(chuàng)建 View 對(duì)象
6. 遍歷結(jié)束以后執(zhí)行 parent.onFinishInflate()

如果子節(jié)點(diǎn)是 include 則執(zhí)行 parseInclude() ,parseInclude() 的源碼和 inflate(parser, root, attachToRoot) 類似,都是讀取xml對(duì)應(yīng)的文件,轉(zhuǎn)換成 XmlResourceParser 然后遍歷里的標(biāo)簽

經(jīng)過層層調(diào)用,我們可以找到最終創(chuàng)建 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);

第一部分代碼,我們的到的結(jié)論是, createViewFromTag(parent, name, context, attrs)負(fù)責(zé)創(chuàng)建 View 對(duì)象

第二部分
  // Temp is the root view that was found in the xml
   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);
       }
   }
   ……

   // 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.
   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.
   if (root == null || !attachToRoot) {
       result = temp;
   }

因?yàn)檫@里排除了merge標(biāo)簽,這里的根標(biāo)簽肯定是一個(gè) View,所以調(diào)用了 createViewFromTag(root, name, inflaterContext, attrs) 方法創(chuàng)建 View 。再次印證了第一部分得出的結(jié)論 createViewFromTag(parent, name, context, attrs)負(fù)責(zé)創(chuàng)建 View 對(duì)象

然后看下后面的代碼我們就明白 inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) 三個(gè)參數(shù)的關(guān)系了

1. root 不為 null 的時(shí)候,才會(huì)讀取 xml 跟布局的 params 屬性。
   (這里可以解釋為啥我們有時(shí)候用 LayoutInflater 加載的 xml 根標(biāo)簽的屬性總是無效 )
2. attachToRoot 為 True ,返回的是  root 對(duì)象。否則返回的是 xml 創(chuàng)建的根標(biāo)簽指定的 View

LayoutInflater.createViewFromTag()創(chuàng)建 View 對(duì)象

通過上面的判斷我們終于找到了最最核心的方法 createViewFromTag()

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

有包裹了一層,并且把 ignoreThemeAttr 設(shè)置為 false,表示這里會(huì)收到 Theme 的影響。我們?cè)?createViewFromTag(parent, name, context, attrs, false) 中找到了創(chuàng)建 View 的代碼

  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;
      }
  }

  return view;

這里又出現(xiàn)了 mFactory2mFactory、mPrivateFactory 三個(gè)對(duì)象,似乎都是可以創(chuàng)建 View 。 對(duì)于android.app.Activity來說,這三個(gè)對(duì)象為 null 或者空實(shí)現(xiàn)(下一節(jié)會(huì)講這個(gè)) 所以我們直接看

 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;
 }

這里需要說明一下,如果 name屬性里面含有 . 表示這是一個(gè)自定義 View,系統(tǒng)自帶 View 我們可以省略類的路徑,而自定義 View 則不能省略。

對(duì)于自定義 View 的創(chuàng)建,這里省略了大部分代碼

public final View createView(String name, String prefix, AttributeSet attrs)
        throws ClassNotFoundException, InflateException {
    Constructor<? extends View> constructor = sConstructorMap.get(name);
    ……
    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]));
        }
        mConstructorArgs[0] = lastContext;
        return view;

    } ……
}

僅僅看到 constructor.newInstance(args) 我們已經(jīng)明白這里使用了 反射創(chuàng)建 View 對(duì)象

而對(duì)于 Android 內(nèi)置的各種 View 我們?cè)?LayoutInflater 的實(shí)現(xiàn)類 PhoneLayoutInflater 中找到了重載

/**
 * @hide
 */
public class PhoneLayoutInflater extends LayoutInflater {
    private static final String[] sClassPrefixList = {
        "android.widget.",
        "android.webkit.",
        "android.app."
    };

    @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
        for (String prefix : sClassPrefixList) {
            try {
                View view = createView(name, prefix, attrs);
                if (view != null) {
                    return view;
                }
            } catch (ClassNotFoundException e) {
                // In this case we want to let the base class take a crack
                // at it.
            }
        }

        return super.onCreateView(name, attrs);
    }
}

再看下 LayoutInflater 中的代碼

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

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

我們可以看到, 對(duì)于系統(tǒng)內(nèi)置的 View,會(huì)依次在 View 的標(biāo)簽前面加上"android.widget."、"android.webkit.","android.app." 、"android.view." 然后通過反射的方法創(chuàng)建 View。

最后補(bǔ)充一點(diǎn),Activity 和 mFactory2、mFactorymPrivateFactory 的關(guān)系

我們前面說過 對(duì)于android.app.Activity來說,mFactory2、mFactory、mPrivateFactory這三個(gè)對(duì)象為 null或者空實(shí)現(xiàn)

我們回到 Activity 的源碼中

final void attach(……) {
    ……

    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);
    ……
}

這里省略了很多代碼,但是我們看到在創(chuàng)建完 PhoneWindow 以后,緊接著調(diào)用了mWindow.getLayoutInflater().setPrivateFactory(this)

這里看到 Activity 實(shí)現(xiàn)了 LayoutInflater.Factory2 接口,并且通過mWindow.getLayoutInflater().setPrivateFactory(this),把 Activity 設(shè)置為 LayoutInflater 的 mPrivateFactory 成員變量。

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);
}

@Nullable
public View onCreateView(String name, Context context, AttributeSet attrs) {
    return null;
}

這里可以看到 Activity 通過自己的實(shí)現(xiàn)的 LayoutInflater.Factory2 接口,增加了對(duì)fragment標(biāo)簽的處理。

順便說一下 AppCompat 組件的安裝

android.support.v7 包中提供了一系列 AppCompatXXX 替代Android自帶的 XXX 。例如 android.view.View 被替代為android.support.v7.widget

第一步 AppCompatActivity.onCreate(@Nullable Bundle savedInstanceState)

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

這里可以看到 delegate.installViewFactory(),該方法的實(shí)現(xiàn)類在 android.support.v7.app.AppCompatDelegateImplV9

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

這里看到 LayoutInflaterCompat.setFactory2(layoutInflater, this) ,跟蹤下去

public static void setFactory2(
        @NonNull LayoutInflater inflater, @NonNull LayoutInflater.Factory2 factory) {
    IMPL.setFactory2(inflater, factory);
}


public void setFactory2(LayoutInflater inflater, LayoutInflater.Factory2 factory) {
        inflater.setFactory2(factory);

        final LayoutInflater.Factory f = inflater.getFactory();
        if (f instanceof LayoutInflater.Factory2) {
            // The merged factory is now set to getFactory(), but not getFactory2() (pre-v21).
            // We will now try and force set the merged factory to mFactory2
            forceSetFactory2(inflater, (LayoutInflater.Factory2) f);
        } else {
            // Else, we will force set the original wrapped Factory2
            forceSetFactory2(inflater, factory);
        }
    }

這里看到 inflater.setFactory2(factory) ,表示已經(jīng)安裝 AppCompatDelegateImplV9LayoutInflater.mFactory2

然后看 AppCompatDelegateImplV9.(View parent, final String name, @NonNull Context context, @NonNull AttributeSet attrs)

@Override
public View createView(View parent, final String name, @NonNull Context context, @NonNull AttributeSet attrs) {
    if (mAppCompatViewInflater == null) {
        mAppCompatViewInflater = new AppCompatViewInflater();
    }

    boolean inheritContext = false;
    if (IS_PRE_LOLLIPOP) {
        inheritContext = (attrs instanceof XmlPullParser)
                // If we have a XmlPullParser, we can detect where we are in the layout
                ? ((XmlPullParser) attrs).getDepth() > 1
                // Otherwise we have to use the old heuristic
                : shouldInheritContext((ViewParent) parent);
    }

    return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
            IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */
            true, /* Read read app:theme as a fallback at all times for legacy reasons */
            VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
    );
}

最后到 AppCompatViewInflater.createView(View parent, final String name, @NonNull Context context, @NonNull AttributeSet attrs, boolean inheritContext, boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext)

public final View createView(……) {
    ……
    // We need to 'inject' our tint aware Views in place of the standard framework versions
    switch (name) {
        case "TextView":
            view = new AppCompatTextView(context, attrs);
            break;
        ……
    }

    if (view == null && originalContext != context) {
        ……
        view = createViewFromTag(context, name, attrs);
    }

    if (view != null) {
        // If we have created a view, check its android:onClick
        checkOnClickListener(view, attrs);
    }

    return view;
}

到此 ,Xml 文件到 View 對(duì)象的轉(zhuǎn)換過程全部結(jié)束

end.jpg

參考資料

PhoneWindow.java

Android Context完全解析,你所不知道的Context的各種細(xì)節(jié)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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