咱們知道,在Activity#setContentView()中會調用PhoneWindow#setContentView()。而在PhoneWindow#setContentView()中有這麼一句mLayoutInflater.inflate(layoutResID, mContentParent)
。這行代碼的做用是將咱們的activity_main.xml填充到mContentParent中去。詳見:setContentView源碼解析。在寫adapter的時候,也常常寫mInflater.inflate(layoutResID, parent,false)
。那麼,這行代碼怎麼就將xml文件轉換成了View或者ViewGroup了呢?java
獲取LayoutInflater對象無非如下兩種方式:android
LayoutInflater.from(Context context);
LayoutInflater LayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
其實這倆是同一種方式,首先看下LayoutInflater#from()緩存
源碼位置:frameworks/base/core/Java/Android/view/LayoutInflater.javaapp
LayoutInflater#from()ide
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對象的方式,不過就是對第二種方式的一個簡單封裝。實際上仍是一回事。Context的實現類是ContextImpl,跟進。佈局
源碼位置:frameworks/base/core/java/android/app/ContextImpl.javafetch
ContextImpl#getSystemService()this
@Override public Object getSystemService(String name) { return SystemServiceRegistry.getSystemService(this, name); }
跟進。.net
源碼位置:frameworks/base/core/java/android/app/SystemServiceRegistry.javacode
SystemServiceRegistry#getSystemService()
public static Object getSystemService(ContextImpl ctx, String name) { ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name); return fetcher != null ? fetcher.getService(ctx) : null; }
直接從全局變量SYSTEM_SERVICE_FETCHERS中依據名字就get到了fetcher,以後依據fetcher直接get到了LayoutInflater對象。大寫的懵B~原來啊,在SystemServiceRegistry中有個靜態代碼塊,先看下這部分。
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); } static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> { private final int mCacheIndex; public CachedServiceFetcher() { mCacheIndex = sServiceCacheSize++; } @Override @SuppressWarnings("unchecked") public final T getService(ContextImpl ctx) { final Object[] cache = ctx.mServiceCache; synchronized (cache) { // Fetch or create the service. Object service = cache[mCacheIndex]; if (service == null) { service = createService(ctx); cache[mCacheIndex] = service; } return (T)service; } } public abstract T createService(ContextImpl ctx); }
這裏連續貼了兩個方法和一個抽象內部類CachedServiceFetcher。因爲在抽象方法CachedServiceFetcher#createService()的具體實現中返回的是PhoneLayoutInflater,因此後文中使用的一直是PhoneLayoutInflater的對象。獲取LayoutInflater對象(實際上是其子類PhoneLayoutInflater對象)以後,調用LayoutInflater#inflate()。跟進。
源碼位置:frameworks/base/core/java/android/view/LayoutInflater.java
LayoutInflater#inflate()
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) { return inflate(resource, root, root != null); }
這裏以setContentView中的mLayoutInflater.inflate(layoutResID, mContentParent)
爲例,順帶也會講解adapter中mInflater.inflate(layoutResID,null)
這種狀況。也就是root
參數爲null
和不爲null
兩種狀況。root==null
,則第三個參數爲false
.root!=null
,則第三個參數爲true
。跟進。
LayoutInflater#inflate()
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(); } }
跟進。
LayoutInflater#inflate()
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { ... View result = root; try { ... // 獲取根節點的字符串,例如LinearLayout final String name = parser.getName(); // 根節點merge開頭 if (TAG_MERGE.equals(name)) { ... } else { // 建立根視圖View final View temp = createViewFromTag(root, name, inflaterContext, attrs); ViewGroup.LayoutParams params = null; if (root != null) { // 獲取LayoutParams params = root.generateLayoutParams(attrs); if (!attachToRoot) { // 應用LayoutParams到根節點View temp.setLayoutParams(params); } } // 遍歷解析子View,並添加到根節點temp中 rInflateChildren(parser, temp, attrs, true); // root不爲空,直接將根節點View添加到root中 if (root != null && attachToRoot) { root.addView(temp, params); } // root等於null,直接返回根節點temp if (root == null || !attachToRoot) { result = temp; } } }catch (Exception e) { ... } return result; } }
上面每一步都有註釋,下面重點看下生成根節點View的createViewFromTag()和遍歷生成子View的rInflateChildren()方法。
LayoutInflater#createViewFromTag()
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) { return createViewFromTag(parent, name, context, attrs, false); } View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) { ... if (-1 == name.indexOf('.')) { view = onCreateView(parent, name, attrs); } else { view = createView(name, null, attrs); } ... return view; }
跟進。
LayoutInflater#createView()
public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException { Constructor<? extends View> constructor = sConstructorMap.get(name); Class<? extends View> clazz = null; try { if (constructor == null) { clazz = mContext.getClassLoader().loadClass( prefix != null ? (prefix + name) : name).asSubclass(View.class); ... constructor = clazz.getConstructor(mConstructorSignature); constructor.setAccessible(true); sConstructorMap.put(name, constructor); } else { ... } final View view = constructor.newInstance(args); return view; } catch (Exception e) { ... } }
sConstructorMap是個HashMap<String, Constructor<? extends View>>
對象。首先依據根節點的名字,例如LinearLayout
去查找緩存的構造器,若是是第一次執行,確定返回null
。若是返回爲null
,則經過反射出構造方法,並強制設置可訪問,以後存進sConstructorMap中。若是緩存中有構造器,那麼直接取出。最後調用newInstance
反射出根節點View實例。獲得根節點View實例以後,接着設置屬性,最後調用rInflateChildren()遍歷建立子View。跟進。
LayoutInflater#rInflateChildren()
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException { rInflate(parser, parent, parent.getContext(), attrs, finishInflate); }
parent
參數是根節點View。這裏只是簡單轉發給rInflate()方法處理。跟進。
LayoutInflater#rInflateChildren()
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)) { ... } ... } 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(); } }
遍歷體如今While
循環上,name爲子節點View的名稱,例如:TextView,RelativeLayout等。幾個以tag、include等開頭的子節點走最上面幾個if的邏輯,咱們的重點在於尋常View走的else邏輯。能夠看到:首先,和建立根節點View調用同一個方法createViewFromTag()建立子View,緊接着設置子View的參數,而後調用遞歸調用rInflateChildren()方法再去測量子節點的全部View,最後纔將子節點添加到父佈局,這個父佈局多是根節點,也多是某個子節點。遍歷結束以後,全部子View也添加到佈局當中並設置好相應的佈局參數。
至此,LayoutInflater.from().inflate()源碼解析結束~