`上一節:安卓 - 源碼分析 - LayoutInflater(一)
inflate方法的重載:
inflate方法有很多重載,而最終將會進入:
/*
* 3個參數分別為:
* parser : 已經包含xml布局的xml解析器
* root : 可選參數,生成的布局的根視圖,作用取決于attachToRoot參數
* attachToRoot : 當為true時,生成的View作為子控件添加到根視圖root;
當為false時,root僅為生成的View提供外部的LayoutParams;
*/
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)
當調用參數為xml資源id的重載方法時:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
則會使用getResources().getLayout(resource)將xml布局資源轉換為XmlResourceParser:
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
默認的attachToRoot由root參數是否null決定:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root){
return inflate(parser, root, root != null);
}
inflate的過程:
解析過程,只需要關注最終使用的inflate方法。去掉Debug和Trace,取出關鍵的源碼先大概了解:
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 {
int type;
//Look for the root node.
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
final String name = parser.getName();
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;
}
}
} catch (Exception e) {
...
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
}
return result;
}
}
應該已經很明顯了,然后我們一步步分析:
/*
* 這里主要做了一些關鍵對象的配置和臨時保存。
*/
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
/*
* 這里主要檢索xml是否能夠被解析。
* 查找第一個標簽,直到文件結束。
* 一個標簽都找不到時,拋出異常,結束解析。
*/
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
/*
* 這里主要是為xml布局文件創建一個根節點,
*/
if (TAG_MERGE.equals(name)) {
/*
* 當標簽是merge時,必須有父布局及需要添加到父布局,否則異常
*/
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
/*
* 當第一個標簽是<merge/>時,調用rInflate方法解析xml并填充到參數給出的root
* rInflate方法后面介紹
*/
rInflate(parser, root, inflaterContext, attrs, false);
} else {
/*
* 當第一個標簽不是merge時,使用這個標簽創建一個根節點temp,
* 調用createViewFromTag方法創建View,這個方法后面介紹
*/
final String name = parser.getName();
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
/* 這里的流程下面介紹 */
...
}
/*
* 根據root和attachToRoot,為根節點temp配置合適的LayoutParams。
*/
ViewGroup.LayoutParams params = null;
if (root != null) {
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
/*
* 通過rInflateChildren方法,將剩余的子節點解析并填充到根節點temp
* rInflateChildren方法后面介紹
*/
rInflateChildren(parser, temp, attrs, true);
/*
* 這時根節點temp已經解析完畢,并已正確添設置LayoutParams,
* 如果參數root非空,且參數attachToRoot為true,則將temp添加到root上
*/
if (root != null && attachToRoot) {
root.addView(temp, params);
}
/*
* 在開始的時候,result被設置為root,
* 當root為空,或者attachToRoot為false,即temp沒有被添加到root時
* 應該將temp作為結果返回,即把result設置為temp
*/
if (root == null || !attachToRoot) {
result = temp;
}
/* 清空context引用 */
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
/* 返回結果 */
return result;
inflate過程調用的幾個重要方法:
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();
}
}
進一步分析:
/*
* 這里獲取了xml的當前層,用于后面的層次校驗,
* 保證while循環于當前節點內,例如:
* 當前層為1,當循環到層1時,表示當前節點的子節點全部解析完成
*/
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;
}
/* 這里的流程下面介紹 */
...
}
/*
* 根據不同的標簽進行不同的處理,主要針對幾個特殊標簽:
* <requestFocus/>:使用在非容器View內,能使View獲取焦點
* <tag/> :能在xml進行Tag設置,相當于setTag(id, value)
* <include/> :include只能作為子節點使用
* <merge/> :merge只能作為根節點使用
* 具體的parse相對簡單,可以自行了解
* 而不是這幾個特殊標簽時,進入正常解析流程
*/
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 {
/*
* 正常解析流程,看得出這個流程使用了遞歸算法
* 調用createViewFromTag,生成當前標簽對應的View對象,
* 然后通過其父容器得到LayoutParams,
* 然后調用rInflateChildren對該節點下的子節點進行解析填充,
* 最后,將這個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);
}
rInflateChildren方法:
/*
* 不做過多解析,看得出,rInflateChildren只是對rInflate的簡單封裝
*/
final void rInflateChildren(XmlPullParser parser, View parent,
AttributeSet attrs,boolean finishInflate)
throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
createViewFromTag方法:
View createViewFromTag(View parent, String name, Context context,
AttributeSet attrs, boolean ignoreThemeAttr) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
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)) {
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;
} catch (Exception e) {
...
}
}
這里就涉及到分析一提及到的Factory了,繼續慢慢分析:
/*
* 對于<view/>標簽,取出其class屬性替代標簽名稱
*/
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
// 這里會產生一個問題,什么是view標簽,class屬性又是什么
// 其實view標簽是為內部類View使用的,例如:
// 在com.example.A.class里,有一個自定義View:ViewB.class,
// 在編譯后,ViewB類名則變成了com.example.A.class$ViewB,
// 而<com.example.A$ViewB/>標簽是不能定義的,所以就要使用到<view/>標簽
// 即<view class="com.example.A$ViewB"/>
// 當然你也可以對普通的View使用同樣的定義方法如<view class="Button"/>
// 注意,使用<view/>標簽必須定義class,否則會拋空指針異常
/*
* 處理主題相關
*/
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();
}
/*
* 這個TAG_1995其實就是<blink/>標簽
* 會為這個標簽創建一個BlinkLayout直接返回
* 這個BlinkLayout,會對內部的控件提供一種閃爍的功能
* 然而是沒什么卵用的,其實就是內部維護了一個handler,
* 每0.5秒進行一次更新,根據mBlinkState去選擇性繪制子控件
* 這個0.5秒是個final值,不能改變
*/
if (name.equals(TAG_1995)) {
return new BlinkLayout(context, attrs);
}
/*
* 這里就是View的創建位置,調用優先級為:
* 自定義Factory > 系統Factory > LayoutInflater默認
*/
try {
View view;
if (mFactory2 != null) {
/*
* 優先使用Factory2
*/
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
/*
* 然后再是Factory
*/
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
/* 這里的流程下面介紹 */
...
}
/*
* 沒有設置自定義Factory時,嘗試使用系統添加的Factory進行創建
*/
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
/*
* 沒有設置任何工廠時,使用默認的createView方法創建View
*/
if (view == null) {
/*
* 這里也是臨時記錄context
*/
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
/*
* 這里其實就是看你的view有沒有完整的類名
* 類似Button,那么就是不完整,使用onCreateView
* 類似com.example.ViewA這樣的,就可以直接使用createView
*/
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
/*
* 還原臨時記錄
*/
mConstructorArgs[0] = lastContext;
}
}
// 返回View
return view;
onCreateView方法:
/*
* 這個方法就是對系統提供的控件補完類名"android.view."
* 實際上工作的還是createView方法
*/
protected View onCreateView(String name, AttributeSet attrs)
throws ClassNotFoundException {
return createView(name, "android.view.", attrs);
}
createView方法:
還是先貼源碼,在這個createView里面,涉及到我們之前說的Filter了,直接解析流程
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) {
/*
* onCreateView方法的"android.view."在這里拼接
* prefix + name
*/
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
/*
* 這里就是Filter作出過濾的地方了
* 不通過過濾就會通過failNotAllowed排除異常
*/
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 {
/*
* 有構造器緩存的話,直接使用Filter做一次過濾
* 這里做了個優化,緩存了Filter的過濾結果
*/
if (mFilter != null) {
// 查找Filter緩存
Boolean allowedState = mFilterMap.get(name);
if (allowedState == null) {
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
// 插入Filter緩存
mFilterMap.put(name, allowed);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
} else if (allowedState.equals(Boolean.FALSE)) {
failNotAllowed(name, prefix, attrs);
}
}
}
/*
* 到了這里就知道mConstructorArgs對象里面存放的是什么
* 分別是context和attrs,老鐵,沒毛病
*/
Object[] args = mConstructorArgs;
args[1] = attrs;
/*
* newInstance了,反射,沒毛病
*/
final View view = constructor.newInstance(args);
/*
* 像是ViewStub這樣的情況
* 則將自己(LayoutInflater)復制一份給ViewStub
* 延遲加載
*/
if (view instanceof ViewStub) {
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
/*
* 到這里終于結束這個創建流程了
*/
return view;
} catch (NoSuchMethodException e) {
...
}
}
上面整個流程涉及到的幾個點需要說一下:
Filter和Factory:
因為Filter的調用是在LayoutInflater的默認創建過程才會調用的,如果攔截的View在Factory中被返回,則Filter是不會起效的,你也可以在Factory中自行調用Filter進行攔截判斷。
創建子節點和本節點:
所有的創建本節點,都是使用createViewFromTag方法,
某一個節點的子節點創建,都是使用了rInflateChildren方法。