LayoutInflater“移花接木”的上篇,介紹了LayoutInflater對象的獲取方式,更主要的是分析幾種方式的原理,發現最終都是通過獲取系統服務的方式。那麼,本篇算是“移花接木”的重頭,主要分析xml是如何轉換爲view的
獲取LayoutInflater對象後,就是使用Inflate方法,從此開始,揭開“移花接木”之神祕面紗...
LayoutInflater.java
兩個參數,一個xml對應的資源ID,一個是父級的view,也就是你的xml的view要綁定在誰身上
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
這裏除上面的兩個參數,多出一個,表示是否綁定在父view上,取決於父view是否爲null 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();
}
}
此處可以看出,涉及到Resources類、LayoutInflater類,執行以下兩個方法,分析會圍繞這兩個方法展開
步驟1:public XmlResourceParser getLayout(int resourceId)
步驟2:public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)
步驟1 getLayout分析:
Resources.java
加載xml資源解析器,這裏加載的是layout的,當然,還有anim等資源的解析器,參數type對應的就是“layout”,如果是動畫,對應的是“anim”
public XmlResourceParser getLayout(@LayoutRes int id) throws NotFoundException {
return loadXmlResourceParser(id, "layout");
}
XmlResourceParser loadXmlResourceParser(int id, String type)
throws NotFoundException {
synchronized (mAccessLock) {
TypedValue value = mTmpValue;
if (value == null) {
mTmpValue = value = new TypedValue();
}
getValue(id, value, true);
if (value.type == TypedValue.TYPE_STRING) {
return loadXmlResourceParser(value.string.toString(), id,
value.assetCookie, type);
}
throw new NotFoundException(
"Resource ID #0x" + Integer.toHexString(id) + " type #0x"
+ Integer.toHexString(value.type) + " is not valid");
}
}
這裏涉及getValue,loadXmlResource兩個方法,
先看getValue方法內部,是在資源中查找是否存在xml的id,如果有程序繼續執行,如果沒有,則拋出異常,程序中斷
public void getValue(@AnyRes int id, TypedValue outValue, boolean resolveRefs)
throws NotFoundException {
boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs);
if (found) {
return;
}
throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id));
}
關於異常部分,實際開發中的確遇到過,比如說資源的id寫錯,會拋出資源找不到或者id無效的異常,就是在此校驗
再看loadXmlResource方法,如下:
XmlResourceParser loadXmlResourceParser(String file, int id, int assetCookie, String type) throws NotFoundException {
if (id != 0) {
try {
// These may be compiled...
synchronized (mCachedXmlBlockIds) {
// First see if this block is in our cache.
final int num = mCachedXmlBlockIds.length;
for (int i=0; i<num; i++) {
if (mCachedXmlBlockIds[i] == id) {
//System.out.println("**** REUSING XML BLOCK! id="
// + id + ", index=" + i);
return mCachedXmlBlocks[i].newParser();
}
}
// Not in the cache, create a new block and put it at
// the next slot in the cache.
XmlBlock block = mAssets.openXmlBlockAsset(
assetCookie, file);
if (block != null) {
int pos = mLastCachedXmlBlockIndex+1;
if (pos >= num) pos = 0;
mLastCachedXmlBlockIndex = pos;
XmlBlock oldBlock = mCachedXmlBlocks[pos];
if (oldBlock != null) {
oldBlock.close();
}
mCachedXmlBlockIds[pos] = id;
mCachedXmlBlocks[pos] = block;
return block.newParser();
}
}
}
}
throw new NotFoundException(
"File " + file + " from xml type " + type + " resource ID #0x"
+ Integer.toHexString(id));
}
乍一看,很多代碼,有點蒙圈,仔細一看,return位置,newParser()方法是重點需要關注的,mCachedXmlBlocks是一個XmlBlock類型數組,如下:
private final XmlBlock[] mCachedXmlBlocks = new XmlBlock[4];
這樣,最終集中到XmlBlock類,如下:
XmlBlock.java
public XmlResourceParser newParser() {
synchronized (this) {
if (mNative != 0) {
return new Parser(nativeCreateParseState(mNative), this);
}
return null;
}
}
看到這,Parser是什麼,Parser是一個類,實現了XmlResourceParser接口,繼承了XmlPullParser接口,
XmlResourceParser雖然是接口,但基本沒聲明方法
也就是說,XmlBlock的內部類Parser,具體實現了XmlPullParser的方法,源碼如下:
final class Parser implements XmlResourceParser{
//...
public String getText() {
int id = nativeGetText(mParseState);
return id >= 0 ? mStrings.get(id).toString() : null;
}
public int getLineNumber() {
return nativeGetLineNumber(mParseState);
}
//...
public int next() throws XmlPullParserException,IOException {
if (!mStarted) {
mStarted = true;
return START_DOCUMENT;
}
if (mParseState == 0) {
return END_DOCUMENT;
}
int ev = nativeNext(mParseState);
if (mDecNextDepth) {
mDepth--;
mDecNextDepth = false;
}
switch (ev) {
case START_TAG:
mDepth++;
break;
case END_TAG:
mDecNextDepth = true;
break;
}
mEventType = ev;
if (ev == END_DOCUMENT) {
// Automatically close the parse when we reach the end of
// a document, since the standard XmlPullParser interface
// doesn't have such an API so most clients will leave us
// dangling.
close();
}
return ev;
} Parser的代碼比較繁雜,這裏摘錄了一部分,具體可參見XmlBlock.java源碼
在next()方法中,很清晰看到對於xml步步解析也是使用EventType的進行的,在XmlPullParser.java中TYPES數組定義了xml所有的EventType類型
可以看出,對xml的解析,用到很多native方法,也就是說,xml是靠c代碼解析的,這點避免了java類的編譯過程,尤其是xml解析這種對xml層層解析的過程,是比較耗時的操作,c代碼的執行效率遠高於java代碼
native方法部分具體實現暫時沒有研究,後續慢慢會補上
步驟2 Inflate分析開始,
LayoutInflater.java,以下源碼在該類中
第一個參數,步驟1 獲取到的資源解析器
第二個參數,父級的view
第三個參數,是否綁定到父view上
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;
// Look for the root node.
int type;
//...
final String name = parser.getName();
//...
// 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);
}
}
//...
// 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;
}
}
上面代碼大致過程:
首先,執行通過解析器,獲取到要inflate的view名稱,
然後,調用createViewFromTag,參數依次,父view,view名稱,上下文,view對應的AttributeSet,最終得到temp,也就是我們inflate xml佈局後的view,
值得注意的,這個temp是xml佈局中最頂的,比如,xml佈局中最頂級是Framlayout,那麼,temp對應的就是Framlayout,如果是LinearLayout,temp對應的就是LinearLayout,這也是爲什麼我們finViewById時,需要指定view.findViewById的原因,從頂級父view開始搜尋指定id的子view
接着,通過父view得到LayoutParams,設置view的LayoutParams,將view綁定到父view上
如果沒有父view,直接返回view
接下來,具體看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 (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();
}
//...
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;
}
}
看完這段代碼,是不是很爽?基本不大明白,別怕,這裏我們只關注需要的,那就是最終返回的view,
上面的關鍵執行過程:mFactory2,mFactory,mPrivateFactory的判斷邏輯,不爲null,分別回調各自方法賦值給view,全部爲null,調用類方法createView,onCreateView
涉及到對應接口mFactory2,mFactory,mPrivateFactory對應的方法,有類方法createView,onCreateView,也有回調方法onCreateView
先看接口mFactory2,mFactory,mPrivateFactory,是在LayoutInfalter的構造中初始化的,如下:
protected LayoutInflater(LayoutInflater original, Context newContext) {
mContext = newContext;
mFactory = original.mFactory;
mFactory2 = original.mFactory2;
mPrivateFactory = original.mPrivateFactory;
setFilter(original.mFilter);
}
關於上面LayoutInfalter的構造方法,沒有找到具體在哪被實例化,不過,這並不影響我們分析,這裏認定mFactory2,mFactory,mPrivateFactory全部爲null,那麼,按照上面
代碼邏輯,就會調用LayoutInflater的類方法createView,onCreateView,代碼如下:
protected View onCreateView(String name, AttributeSet attrs)
throws ClassNotFoundException {
return createView(name, "android.view.", attrs);
}
onCreateView內部執行的是createView方法,如下:
第一個參數,view的名稱,第二個參數view對應的類名前綴,第三個參數view對應的AttributeSet
public final View createView(String name, String prefix, AttributeSet attrs){
Constructor<? extends View> constructor = sConstructorMap.get(name);
Class<? extends View> clazz = null;
//...
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;
//...
}
代碼很長,一點點分析,首先是從sConstructorMap中取對應name的view構造,也就是從緩存中取構造,
如果存在構造,則執行else分支,如果沒有構造,則執行if,獲取字節碼對象,獲取構造,添加到sConstructorMap緩存中,方便再次使用
獲取view類的構造方法後,創建view的實例對象,返回實例對象
這個過程是反射的運用
以上是假定,mFactory2,mFactory,mPrivateFactory全部爲null情況
public PhoneLayoutInflater(Context context) {
super(context);
}
protected LayoutInflater(Context context) {
mContext = context;
}
這也說明,創建LayoutInfalter並未涉及到,protected LayoutInflater(LayoutInflater original, Context newContext),這種構造方法,那麼,mFactory2,mFactory,mPrivateFactory自然也就全部爲null
Inflate分析完成
步驟1 步驟2,整個Inflater.inflate方法執行過程分析到此結束
總結:xml佈局,資源文件的解析,底層是用c代碼編寫的,執行效率高
xml佈局轉化爲view最終是採用反射的方式,獲取實例對象的