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

基于 Android API 26 Platform 源碼

寫作背景

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

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

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

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

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

跟蹤一下 Activity.setContentView(resId)

我們一般在項目使用的 Activity 可能是

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

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

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

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

查看一下 getWindow()源碼

public Window getWindow() {
    return mWindow;
}

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

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

當我們沒有設置轉(zhuǎn)場動畫的時候會執(zhí)行

mLayoutInflater.inflate(layoutResID, mContentParent);

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

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

所以我們得出一個結論 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)最終會調(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 的身影。

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

LayoutInflater 對象如何實例化

看下一下 LayoutInflater 的源碼第一行

public abstract class LayoutInflater {……}

LayoutInflater 是一個抽象類, 抽象類是不能實例化的

confuse.jpg

先想一下 LayoutInflater 對象獲取的方式

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

此時,我們必須請出柯南君幫我們宣布


kenan_01.jpg

真相只有一個!最終都是通過服務獲取 LayoutInflater 實例對象

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

如果對該前提有疑問請移步 Android Context完全解析,你所不知道的Context的各種細節(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;
}

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

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 的實現(xiàn)類是 PhoneLayoutInflater

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

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

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)兩個方法其實只有一個方法是核心,另一個只是做了一下封裝,讓我們少傳入一個參數(shù)。

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

所以我們重點看一下 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 對象把 resId 指向的 xml 文件轉(zhuǎn)換為 XmlResourceParser 然后執(zhí)行 inflate(parser, root, attachToRoot) 方法,該方法比較長,這里只貼出核心步驟。

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

以上步驟還是很長,我們將拆分幾部分分析。

第一部分
    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 根標簽是 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();
    }
}

上面這個方式我們需要重點記一下

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

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

經(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);

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

第二部分
  // 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;
   }

因為這里排除了merge標簽,這里的根標簽肯定是一個 View,所以調(diào)用了 createViewFromTag(root, name, inflaterContext, attrs) 方法創(chuàng)建 View 。再次印證了第一部分得出的結論 createViewFromTag(parent, name, context, attrs)負責創(chuàng)建 View 對象

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

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

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

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

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

有包裹了一層,并且把 ignoreThemeAttr 設置為 false,表示這里會收到 Theme 的影響。我們在 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 三個對象,似乎都是可以創(chuàng)建 View 。 對于android.app.Activity來說,這三個對象為 null 或者空實現(xiàn)(下一節(jié)會講這個) 所以我們直接看

 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屬性里面含有 . 表示這是一個自定義 View,系統(tǒng)自帶 View 我們可以省略類的路徑,而自定義 View 則不能省略。

對于自定義 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 對象

而對于 Android 內(nèi)置的各種 View 我們在 LayoutInflater 的實現(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);
}

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

最后補充一點,Activity 和 mFactory2、mFactory、mPrivateFactory 的關系

我們前面說過 對于android.app.Activity來說,mFactory2、mFactorymPrivateFactory這三個對象為 null或者空實現(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 實現(xiàn)了 LayoutInflater.Factory2 接口,并且通過mWindow.getLayoutInflater().setPrivateFactory(this),把 Activity 設置為 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 通過自己的實現(xiàn)的 LayoutInflater.Factory2 接口,增加了對fragment標簽的處理。

順便說一下 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(),該方法的實現(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 對象的轉(zhuǎn)換過程全部結束

end.jpg

參考資料

PhoneWindow.java

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

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容