Android?Xml轉(zhuǎn)換為View過程詳解
Android布局文件Xml
通過setContentView(@LayoutResint layoutResID)或者LayoutInflater.from(context).inflate(int ResID)轉(zhuǎn)換為Java對象,開發(fā)工具Android Studio 提供的預覽功能,開發(fā)過程中界面和業(yè)務可以并行開發(fā),提高了開發(fā)效率。以下分析過程是基于 Android API 25 Platform 源碼,并以setContentView()方法為入口。
Xml 轉(zhuǎn)成 Java 對象方式
1、Activity中setContentView(@LayoutResint layoutResID)方法;
該方法都會被每個繼承 android.app.Activity 的子類重載;
2、LayoutInflater.from(Context context).inflate(@LayoutResint resource, ...)。
一般使用 Activity
- 1). android.support.v7.app.AppCompatActivity
- 2). android.support.v4.app.FragmentActivity
- 3). android.app.Activity
- 4). 其他 Activity
從Activity中setContentView()方法開始
public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); }
跟蹤getWindow()源碼
public Window getWindow() { return mWindow; }
mWindow在Activity.java中attach()方法里初始化
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) { ... mWindow = new PhoneWindow(this, window); ... }
所以Window.java的實現(xiàn)類是PhoneWindow.java類,@hide
代表 PhoneWindow
的源碼在 sdk 里面是隱藏的,查看 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(); } mContentParentExplicitlySet = true; }
從上面代碼可以發(fā)現(xiàn)如果沒有轉(zhuǎn)場動畫時,執(zhí)行的是
mLayoutInflater.inflate(layoutResID, mContentParent);
在PhoneWindow構(gòu)造函數(shù)里發(fā)現(xiàn)mLayoutInflater對象賦值代碼如下:
public PhoneWindow(Context context) { super(context); mLayoutInflater = LayoutInflater.from(context); }
所以可以得出一個結(jié)論 Activity.setContentView(resId) 最終還是使用LayoutInflater.from(context).inflate(resId, ……)。
在看下其他activity android.support.v7.app.AppCompatActivity
和android.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); }
getDelegate()源代碼最終會調(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(); }
因此xml 轉(zhuǎn)成 Java 對象是通過LayoutInflater
的inflate()
方法來完成的
LayoutInflater 對象獲取方式
關(guān)鍵字abstract
,LayoutInflater
是一個抽象類,不能實例化,LayoutInflater
對象獲取的方式有:
1). 在 Activity 中通過 getLayoutInflater() 獲取
2). LayoutInflater里靜態(tài)方法from(context) 獲取
3). context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) 獲取
如 Activity 的 getLayoutInflater()
/** * Convenience for calling * {@link android.view.Window#getLayoutInflater}. */ @NonNull public LayoutInflater getLayoutInflater() { return getWindow().getLayoutInflater(); }
可以看出 Activity 通過 getLayoutInflater() 獲取的是 PhoneWindow 的 mLayoutInflater。
LayoutInflater.from(context)
/** * 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對象都是通過服務獲取 LayoutInflater 實例對象
跟蹤下源碼context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);Context
的實現(xiàn)類是ContextImpl.java
,如:
@Override public Object getSystemService(String name) { return SystemServiceRegistry.getSystemService(this, name); }
跟蹤 SystemServiceRegistry.java
/** * Gets a system service from a given context. */ public static Object getSystemService(ContextImpl ctx, String name) { ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name); return fetcher != null ? fetcher.getService(ctx) : null; } /** * Statically registers a system service with the context. * This method must be called during static initialization only. */ 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); }
在 SystemServiceRegistry 類,這里只注冊各種系統(tǒng)服務的處,通過 Context.LAYOUT_INFLATER_SERVICE找到注冊代碼地方,如下:
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class, new CachedServiceFetcher<LayoutInflater>() { @Override public LayoutInflater createService(ContextImpl ctx) { return new PhoneLayoutInflater(ctx.getOuterContext()); }});
通過以上代碼發(fā)現(xiàn) LayoutInflater
的實現(xiàn)類是 PhoneLayoutInflater
LayoutInflater 讀取 Xml 文件并創(chuàng)建 View 對象,繼續(xù)跟蹤LayoutInflater.inflate()方法
1).View inflate(@LayoutRes int resource, @Nullable ViewGroup root)
2).View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
重點看第二個方法,代碼如下:
/** * Inflate a new view hierarchy from the specified xml resource. Throws * {@link InflateException} if there is an error. * * @param resource ID for an XML layout resource to load (e.g., * <code>R.layout.main_page</code>) * @param root Optional view to be the parent of the generated hierarchy (if * <em>attachToRoot</em> is true), or else simply an object that * provides a set of LayoutParams values for root of the returned * hierarchy (if <em>attachToRoot</em> is false.) * @param attachToRoot Whether the inflated hierarchy should be attached to * the root parameter? If false, root is only used to create the * correct subclass of LayoutParams for the root view in the XML. * @return The root View of the inflated hierarchy. If root was supplied and * attachToRoot is true, this is root; otherwise it is the root of * the inflated XML file. */ 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(); } }
根據(jù)以上代碼邏輯,首先通過 resource
對象把 resId 指向的 xml 文件轉(zhuǎn)換為XmlResourceParser
,然后執(zhí)行inflate(parser, root, attachToRoot)
方法,核心代碼如下:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate"); final Context inflaterContext = mContext; final AttributeSet attrs = Xml.asAttributeSet(parser); Context lastContext = (Context) mConstructorArgs[0]; mConstructorArgs[0] = inflaterContext; View result = root; try { // Look for the root node. ... final String name = parser.getName(); //分析1 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"); } //分析2 rInflate(parser, root, inflaterContext, attrs, false); } else { //分析3 // 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); } } //分析4 // 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; } }
分析
分析1:
如果 Xml 根標簽是 TAG_MERGE(即merge)
,則 root 不能為空, attachToRoot 為 true,在執(zhí)行rInflate(parser, root, inflaterContext, attrs, false)
分析2 rInflate()方法
void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException { final int depth = parser.getDepth(); int type; 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)) { parseRequestFocus(parser, parent); } 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 (finishInflate) { parent.onFinishInflate(); } }
rInflate(parser, root, inflaterContext, attrs, false) 總結(jié)如下
1). while遍歷該節(jié)點的子節(jié)點
2). 子節(jié)點有 "requestFocus"、"tag"、""、"include"
3). 子節(jié)點不能是 "merge"
4). 子節(jié)點的其他情況,則是各種 View 的標簽
5). View 標簽和 "include" 標簽會創(chuàng)建 View 對象
6). 遍歷結(jié)束以后執(zhí)行 parent.onFinishInflate()
如果子節(jié)點是 include,則執(zhí)行 parseInclude() ,parseInclude() 的源碼和 inflate(parser, root, attachToRoot) 類似,都是讀取xml對應的文件,轉(zhuǎn)換成 XmlResourceParser 然后遍歷里的標簽。
createViewFromTag(parent, name, context, attrs)
負責創(chuàng)建 View 對象
分析3、4
1). root 不為 null,才會讀取 xml 跟布局的 params 屬性;
2). attachToRoot 為 True ,返回的是 root 對象。否則返回的是 xml 創(chuàng)建的根標簽指定的 View
3). 調(diào)用了 createViewFromTag(root, name, inflaterContext, attrs) 方法創(chuàng)建 View
4). rInflateChildren()->rInflate();和 分析2一樣的
綜上所述,LayoutInflater.createViewFromTag()
創(chuàng)建 View 對象,源碼如下:
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) { if (name.equals("view")) { name = attrs.getAttributeValue(null, "class"); } // Apply a theme wrapper, if allowed and one is specified. 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(); } if (name.equals(TAG_1995)) { // Let's party like it's 1995! return new BlinkLayout(context, attrs); } 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); } 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; //異常處理 ... }
mFactory2
、mFactory
、mPrivateFactory
三個對象,似乎都是可以創(chuàng)建 View , 對于android.app.Activity,這三個對象為 null 或者空實現(xiàn),創(chuàng)建 View 對象直接看如下代碼:
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); if (constructor != null && !verifyClassLoader(constructor)) { constructor = null; sConstructorMap.remove(name); } Class<? extends View> clazz = null; try { Trace.traceBegin(Trace.TRACE_TAG_VIEW, name); if (constructor == null) { // Class not found in the cache, see if it's real, and try to add it 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); sConstructorMap.put(name, constructor); } else { // If we have a filter, apply it to cached constructor if (mFilter != null) { // Have we seen this name before? Boolean allowedState = mFilterMap.get(name); if (allowedState == null) { // New class -- remember whether it is allowed clazz = mContext.getClassLoader().loadClass( prefix != null ? (prefix + name) : name).asSubclass(View.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; 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; //異常處理 ...... }
以上代碼可以看出constructor.newInstance(args)
,通過反射創(chuàng)建 View 對象
對于 Android 內(nèi)置的各種 View 在 LayoutInflater 的實現(xiàn)類PhoneLayoutInflater
中重載了onCreateView()
方法
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。
以上就是Android Xml轉(zhuǎn)換為View過程詳解的詳細內(nèi)容,更多關(guān)于Android Xml轉(zhuǎn)換View的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android點擊WebView實現(xiàn)圖片縮放及滑動瀏覽效果
這篇文章主要為大家詳細介紹了Android點擊WebView實現(xiàn)圖片縮放及滑動瀏覽效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-12-12Android編程出現(xiàn)Button點擊事件無效的解決方法示例
這篇文章主要介紹了Android編程出現(xiàn)Button點擊事件無效的解決方法,結(jié)合實例形式分析了Android編程中出現(xiàn)Button點擊事件無效的原因及相關(guān)的解決方法,需要的朋友可以參考下2018-02-02android dialog邊框去除白色邊框?qū)崿F(xiàn)思路及代碼
android dialog邊框含有白色真是美中不足啊,本文將介紹如何去除白色邊框,有思路及代碼,感興趣的朋友可以了解下2013-01-01Android應用中實現(xiàn)手勢控制圖片縮放的完全攻略
這篇文章主要介紹了Android應用中實現(xiàn)手勢控制圖片縮放的完全攻略,采用了Matrix矩陣的方法,實例講解了包括觸摸點設置與各種沖突的處理等方面,相當全面,需要的朋友可以參考下2016-04-04Android自定義控件ScrollView實現(xiàn)上下滑動功能
這篇文章主要為大家詳細介紹了Android自定義控件ScrollView實現(xiàn)上下滑動功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-07-07Android UI設計與開發(fā)之ViewPager仿微信引導界面以及動畫效果
這篇文章主要為大家詳細介紹了Android UI設計與開發(fā)之ViewPager仿微信引導界面以及動畫效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-08-08Android 通過ViewHolder優(yōu)化適配器的實現(xiàn)方法(必看)
下面小編就為大家?guī)硪黄狝ndroid 通過ViewHolder優(yōu)化適配器的實現(xiàn)方法(必看)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-04-04