一個(gè)應(yīng)用最基本的就是一個(gè)個(gè)的Activity金踪,在Activity入口方法onCreate中的第一步就是setContentView也就是加載布局文件柠傍。今天我們就來(lái)學(xué)習(xí)一下Android中加載布局文件的流程咏瑟。
通過(guò)本文你也許能明白下面幾個(gè)問(wèn)題:
- 1.setContentView(id)真正的機(jī)制與原理
- 2.marge為什么不增加視圖層數(shù)以及為什么不能用于根布局但必須用于xml文件的根標(biāo)簽
- 3.自定義view在寫到xml文件中時(shí)為什么會(huì)調(diào)用兩參數(shù)的構(gòu)造方法
- 4.inflate(resource, root, attachToRoot)三個(gè)參數(shù)之間的真正關(guān)系及一些常見(jiàn)問(wèn)題
- 5.ViewStub的幾個(gè)問(wèn)題:①.為什么可以提高性能 ②.是如何延遲加載布局的 ③.為什么inflate()只能調(diào)用一次而setVisibility(View.VISIBLE)可以多次調(diào)用
首先要清楚一點(diǎn),Activity的類型有很多四瘫,有些Activity的setContentView方法和最原始的android.app.Activity中的還是有一定區(qū)別的,這里我們以最原始的為依據(jù)開始分析歼秽,本文源碼基于Android 8.0,源碼位置
android\frameworks\base\core\java\android\app\Activity.java
setContentView有幾個(gè)重載方法情组,但是功能都一樣燥筷,只不過(guò)有的需要傳入資源名有的直接傳一個(gè)view,既然是分析加載xml文件的流程院崇,我們當(dāng)然看最常用的那個(gè)傳入資源名的方法:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
發(fā)現(xiàn)實(shí)際上是調(diào)用的getWindow()的setContentView肆氓,先看getWindow():
public Window getWindow() {
return mWindow;
}
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow是一個(gè)PhoneWindow對(duì)象,PhoneWindow源碼位置:
frameworks\base\core\java\com\android\internal\policy\PhoneWindow.java
我們先不管那個(gè)構(gòu)造函數(shù),直接看setContentView方法
@Override
public void setContentView(int layoutResID) {
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;
}
這里對(duì)有過(guò)場(chǎng)動(dòng)畫的情況下做了特殊處理底瓣,我們先看沒(méi)有過(guò)程動(dòng)畫的情況谢揪。到這里我們發(fā)現(xiàn)最后調(diào)用了inflate方法,和我們?cè)谧鰈istview等一些場(chǎng)景中加載一個(gè)布局文件的方法是一樣的捐凭。接下來(lái)就著重看看這里拨扶。
先看mLayoutInflater的實(shí)例化過(guò)程:
android\frameworks\base\core\java\android\view\LayoutInflater.java
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;
}
原來(lái)我們一直在用的LayoutInflater是一個(gè)系統(tǒng)服務(wù)。來(lái)看一下這個(gè)服務(wù)茁肠,但LayoutInflater是一個(gè)抽象類患民,肯定不是我們要找的,只能從Context 的getSystemService入手垦梆。但是匹颤,Context 只是一個(gè)抽象類,getSystemService必定是它的一個(gè)實(shí)現(xiàn)類中的托猩,就是ContextImpl(至于這里面的關(guān)系印蓖,詳見(jiàn)此處),ContextImpl源碼位置:
android\frameworks\base\core\java\android\app\ContextImpl.java
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
再看 SystemServiceRegistry.getSystemService京腥,SystemServiceRegistry也在app包下
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
new HashMap<String, ServiceFetcher<?>>();
發(fā)現(xiàn)SYSTEM_SERVICE_FETCHERS 只是一個(gè)HashMap赦肃,getSystemService所做的操作僅僅是按名字去內(nèi)容,那么我們就看看這個(gè)HashMap的put方法公浪,看有沒(méi)有什么收獲摆尝。經(jīng)過(guò)搜索,這個(gè)類中有個(gè)registerService方法用來(lái)put:
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);
}
這里要清楚因悲,有可能是外部某個(gè)類調(diào)用這個(gè)方法用來(lái)注冊(cè)服務(wù)堕汞,這時(shí)候我們只能全文搜索注冊(cè)LayoutInflater時(shí)用的key--Context.LAYOUT_INFLATER_SERVICE。幸運(yùn)的時(shí)晃琳,這個(gè)服務(wù)就是在SystemServiceRegistry的static代碼段中注冊(cè)的:
static {
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
}
跟蹤了一圈終于找到了讯检,LayoutInflater的具體實(shí)現(xiàn)類就是PhoneLayoutInflater。PhoneLayoutInflater代碼位置:
android\frameworks\base\core\java\com\android\internal\policy\PhoneLayoutInflater.java
這個(gè)類中卫旱,我們當(dāng)然要先看一下inflate的實(shí)現(xiàn),但是并沒(méi)有找到人灼。原來(lái)PhoneLayoutInflater并沒(méi)有重寫inflate方法,調(diào)用的還是抽象類LayoutInflater內(nèi)原本就實(shí)現(xiàn)的顾翼。投放。。既然如此适贸,還是看一下吧:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != 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();
}
}
上面這個(gè)過(guò)程主要工作就是構(gòu)造了一個(gè)XmlResourceParser 解析器灸芳,然后調(diào)用了inflate的另一個(gè)重載方法:
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 {
int type;
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 {
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
rInflateChildren(parser, temp, attrs, true);
if (root != null && attachToRoot) {
root.addView(temp, params);
}
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
final InflateException ie = new InflateException(e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(parser.getPositionDescription()
+ ": " + e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
return result;
}
}
這個(gè)方法還是比較長(zhǎng)的涝桅,我們一點(diǎn)一點(diǎn)看。從try代碼段開始烙样。第一個(gè)while循環(huán)是尋找開始標(biāo)簽或結(jié)束標(biāo)簽冯遂。如果直接先找到結(jié)束標(biāo)簽,則拋異常谒获。否則區(qū)除標(biāo)簽名蛤肌。
之后如果是marge標(biāo)簽,則root不能為空且attachToRoot不能為false批狱。這里也就驗(yàn)證了marge使用時(shí)不能作為根布局裸准。對(duì)于marge的處理是調(diào)用了rInflate方法:
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();
}
}
上面方法主要就是遍歷所有子節(jié)點(diǎn),首先是對(duì)"requestFocus"赔硫,"tag"炒俱,"include","merge"幾個(gè)特殊標(biāo)簽的處理卦停。其中如果有merge標(biāo)簽則拋異常向胡,若為include標(biāo)簽,執(zhí)行parseInclude方法惊完,由于include主要是引入另一個(gè)xml文件僵芹,所以parseInclude功能和inflate類似,這里先不介紹小槐。在if判斷的最后一塊則是重點(diǎn)拇派,這里是創(chuàng)建view的真正地方,調(diào)用了createViewFromTag方法凿跳,然后配置LayoutParams 件豌,最后添加到viewGroup中,這也就是marge為什么不添加嵌套等級(jí)的原因控嗜。上面兩個(gè)方法我們放到后面講茧彤。
到這里marge標(biāo)簽處理完了,我們?cè)诨氐絠nflate方法疆栏,看不是marge標(biāo)簽的情況曾掂。既然不是marge標(biāo)簽則就是一個(gè)view了,所以直接調(diào)用了:
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
這里我們就來(lái)看一下這個(gè)核心的方法:
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();
}
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;
}
.....
}
首先調(diào)用了另一個(gè)重載方法壁顶,并將參數(shù)ignoreThemeAttr改為false珠洗,意思是會(huì)收到主題的影響。接下來(lái)首先處理了一下如果標(biāo)簽是blink的情況若专,這種東西好像很少用许蓖,有興趣的可以去查找一下。我們看正常情況:
首先出現(xiàn)了mFactory2、mFactory 和mPrivateFactory三個(gè)變量膊爪。我們先探究一下這三個(gè)參數(shù)的來(lái)歷自阱,首先看一下他們的類:
public interface Factory {
public View onCreateView(String name, Context context, AttributeSet attrs);
}
public interface Factory2 extends Factory {
public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
}
全都是接口,再看他們初始化的地方蚁飒,首先是在構(gòu)造中:
protected LayoutInflater(LayoutInflater original, Context newContext) {
mContext = newContext;
mFactory = original.mFactory;
mFactory2 = original.mFactory2;
mPrivateFactory = original.mPrivateFactory;
setFilter(original.mFilter);
}
不過(guò)回顧LayoutInflater的創(chuàng)建過(guò)程:
LayoutInflater.from(context) ->
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) ->
new PhoneLayoutInflater(ctx.getOuterContext()); ->
LayoutInflater(context)
而這三個(gè)參數(shù)是在LayoutInflater的另外一個(gè)兩參數(shù)構(gòu)造中初始化的动壤,所以從Activity中過(guò)來(lái)時(shí)萝喘,這三個(gè)參數(shù)都為空淮逻。
但是他們還有set方法:
void setFactory(Factory factory)
void setFactory2(Factory2 factory)
void setPrivateFactory(Factory2 factory)
通過(guò)搜索在Activity中調(diào)用了setPrivateFactory:
final void attach(....) {
...
mWindow.getLayoutInflater().setPrivateFactory(this);
...
}
傳入的是this,是因?yàn)锳ctivity實(shí)現(xiàn)了Factory2 接口:
@Nullable
public View onCreateView(String name, Context context, AttributeSet attrs) {
return null;
}
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);
}
從代碼中看阁簸,主要是對(duì)標(biāo)簽為fragment時(shí)做了處理爬早,由于我們主要是看layout的加載,當(dāng)標(biāo)簽不為fragment時(shí)返回null启妹,所以我們直接從createViewFromTag中的view==null開始看筛严。
接下來(lái)又出現(xiàn)一個(gè)if判斷,主要是判斷標(biāo)簽中是否有“.”饶米,有則代表是自定義view桨啃,沒(méi)有則是系統(tǒng)內(nèi)置的。分別調(diào)用不同的方法檬输。對(duì)于自定義view:
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) {
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 (mFilter != null) {
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);
mFilterMap.put(name, allowed);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
} else if (allowedState.equals(Boolean.FALSE)) {
failNotAllowed(name, prefix, attrs);
}
}
}
Object lastContext = mConstructorArgs[0];
if (mConstructorArgs[0] == null) {
mConstructorArgs[0] = mContext;
}
Object[] args = mConstructorArgs;
args[1] = attrs;
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) {
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
mConstructorArgs[0] = lastContext;
return view;
}
....
}
關(guān)鍵代碼是這一句·:
constructor = clazz.getConstructor(mConstructorSignature);
static final Class<?>[] mConstructorSignature = new Class[] {Context.class, AttributeSet.class};
看見(jiàn)是利用了反射照瘾,并且調(diào)用了view眾多構(gòu)造中的兩參數(shù)那個(gè),這也就驗(yàn)證了為什么在xml文件中丧慈,自定義view會(huì)調(diào)用兩參數(shù)的構(gòu)造析命。獲得了構(gòu)造后他還會(huì)存起來(lái)供下次使用,節(jié)省性能逃默。之后便調(diào)用了newInstance方法鹃愤,并傳入了context和attrs。接下來(lái)又處理了view時(shí)是ViewStub的情況完域。如果是ViewStub則給他設(shè)置一個(gè)LayoutInflater软吐,這里的cloneInContext實(shí)現(xiàn)在PhoneLayoutInflater中:
public LayoutInflater cloneInContext(Context newContext) {
return new PhoneLayoutInflater(this, newContext);
}
可見(jiàn)就是給他一個(gè)LayoutInflater以便于在ViewStub調(diào)用inflate()時(shí)使用,ViewStub的inflate()方法實(shí)際上就是調(diào)用了LayoutInflater的inflate方法來(lái)加載view吟税,這樣就實(shí)現(xiàn)了界面加載效率的提高(具體分析見(jiàn)文章最后)凹耙。
看完自定義view的加載,我們?cè)倏磧?nèi)置view的加載乌妙,調(diào)用的時(shí)onCreateView方法:
protected View onCreateView(View parent, String name, AttributeSet attrs)
throws ClassNotFoundException {
return onCreateView(name, attrs);
}
這里的兩參數(shù)onCreateView方法在PhoneLayoutInflater中有重寫:
@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) {
}
}
return super.onCreateView(name, attrs);
}
sClassPrefixList值為下:
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
最后也調(diào)用了createView方法使兔,和自定義view的一樣,只不過(guò)自定義view中createView第二個(gè)參數(shù)為null藤韵,內(nèi)置view這里有值虐沥,主要是講TextView,ImageView這些簡(jiǎn)稱補(bǔ)全為全類名,方便反射欲险。
到這里一個(gè)view基本上是創(chuàng)建出來(lái)了镐依,我們?cè)倩厮莸絠nflate方法中(代碼跨度比較大天试,源碼分析就是這樣槐壳,但我們心中要有清晰的線路)务唐,在創(chuàng)建完view后,做了一個(gè)root != null的判斷带兜,這里是讀取了根布局的Params枫笛,只有attachToRoot為false時(shí)刚照,才會(huì)應(yīng)用父節(jié)點(diǎn)的布局參數(shù),這也就是我們?cè)谑褂肔istView或者RecycleView創(chuàng)建View時(shí)无畔,為什么有時(shí)候根標(biāo)簽的屬性會(huì)補(bǔ)齊作用的原因啊楚,到這里下面這個(gè)我們常用方法的三個(gè)參數(shù)的意義應(yīng)該都清楚了:
View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
在配置完參數(shù)后,又調(diào)用了rInflateChildren去解析標(biāo)簽內(nèi)的子view:
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
這個(gè)里面又調(diào)用了rInflate方法恭理,基本上就是深度優(yōu)先的遍歷解析xml文件闸昨,把遇到的所有標(biāo)簽換為對(duì)應(yīng)對(duì)象饵较,然后viewGroup.addView方法一層一層聯(lián)系起來(lái)。
在inflate方法最后循诉,通過(guò)兩個(gè)判斷,確定是返回根布局還是單個(gè)view狈蚤。我們從Activity中過(guò)來(lái)時(shí)划纽,root不為空勇劣,attachToRoot為true潭枣,根據(jù)流程時(shí)將解析出來(lái)的view添加到根布局并返回根布局幻捏。再介紹另外一個(gè)場(chǎng)景,就是在Listview中g(shù)etView時(shí)谐岁,我們參考一個(gè)權(quán)威的寫法:
view = inflater.inflate(resource, parent, false);
上面來(lái)自于ArrayAdapter榛臼。他的attachToRoot為false伊佃,所以返回的是我們要構(gòu)建的xml文件內(nèi)容讽坏,也就是一個(gè)一個(gè)item中要顯示的內(nèi)容例证。
最后的最后织咧,我們?cè)诨氐絇honeWindow的setContentView方法,這里只是簡(jiǎn)單的調(diào)用mLayoutInflater.inflate(layoutResID, mContentParent);抵屿,根據(jù)上一段的流程分析捅位,目的就是將xml文件解析出來(lái)的視圖樹添加到mContentParent(關(guān)于mContentParent的具體可以分析PhoneWindow的內(nèi)容,這里就不詳細(xì)說(shuō)明了尿扯,只用知道他是DecorView的一部分衷笋,是Activity的視圖區(qū)域即可)矩屁。
到這里我們的分析基本上就完成了,我們最后梳理一下泊脐,前一部分跟蹤了setContentView的調(diào)用烁峭,發(fā)現(xiàn)最后調(diào)用了mLayoutInflater.inflate(layoutResID, mContentParent);方法。inflate方法只是一個(gè)入口耘柱,具體一層一層解析xml文件視圖樹的任務(wù)則交給了rInflate方法调煎,根據(jù)標(biāo)簽創(chuàng)建view對(duì)象的任務(wù)交給了createViewFromTag方法,遍歷的方法是深度優(yōu)先遍歷的悲关。
最后我們?cè)谔幚硪粋€(gè)遺留問(wèn)題娄柳,就是關(guān)于ViewStub的赤拒,都說(shuō)使用這個(gè)會(huì)節(jié)省內(nèi)存提高性能,到底因?yàn)槭裁茨卣饩矗覀兘柽@個(gè)分析LayoutInflater.inflate的機(jī)會(huì)來(lái)看一下蕉朵。
第一個(gè)問(wèn)題:我們說(shuō)ViewStub中的內(nèi)容是不會(huì)立即創(chuàng)建出對(duì)象的,而其他view不管是不是GONE狀態(tài)都會(huì)創(chuàng)建冷蚂。這點(diǎn)很好證明蝙茶,在createView方法中蛉拙,我們并沒(méi)有看到任何地方會(huì)根據(jù)VIew的可見(jiàn)性來(lái)控制對(duì)象的創(chuàng)建,而單獨(dú)對(duì)ViewStub做了特殊處理吮廉,僅僅實(shí)例化了ViewStub對(duì)象畸肆,而對(duì)其內(nèi)部的layout沒(méi)有處理轴脐,所以可以說(shuō)明ViewStub確實(shí)有延遲加載的功能抡砂,可以在界面創(chuàng)建階段不過(guò)多的加載view節(jié)省內(nèi)存注益。
第二個(gè)問(wèn)題:我們都說(shuō)ViewStub被設(shè)置為可見(jiàn)的時(shí)候溯捆,或是調(diào)用了ViewStub.inflate()的時(shí)候提揍,布局才會(huì)被加載,到底是不是呢谎仲?我們看ViewStub的源碼:
android\frameworks\base\core\java\android\view\ViewStub.java
public void setVisibility(int visibility) {
if (mInflatedViewRef != null) {
View view = mInflatedViewRef.get();
if (view != null) {
view.setVisibility(visibility);
} else {
throw new IllegalStateException("setVisibility called on un-referenced view");
}
} else {
super.setVisibility(visibility);
if (visibility == VISIBLE || visibility == INVISIBLE) {
inflate();
}
}
}
public View inflate() {
final ViewParent viewParent = getParent();
if (viewParent != null && viewParent instanceof ViewGroup) {
if (mLayoutResource != 0) {
final ViewGroup parent = (ViewGroup) viewParent;
final View view = inflateViewNoAdd(parent);
replaceSelfWithView(view, parent);
mInflatedViewRef = new WeakReference<>(view);
if (mInflateListener != null) {
mInflateListener.onInflate(this, view);
}
return view;
} else {
throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
}
} else {
throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
}
}
private View inflateViewNoAdd(ViewGroup parent) {
final LayoutInflater factory;
if (mInflater != null) {
factory = mInflater;
} else {
factory = LayoutInflater.from(mContext);
}
final View view = factory.inflate(mLayoutResource, parent, false);
if (mInflatedId != NO_ID) {
view.setId(mInflatedId);
}
return view;
}
可見(jiàn)調(diào)用setVisibility設(shè)置為VISIBLE 或INVISIBLE是就會(huì)調(diào)用inflate()郑诺。而inflate()中的inflateViewNoAdd方法就是調(diào)用了LayoutInflater的inflate方法去解析布局文件(這里的LayoutInflater實(shí)例就是在LayoutInflater的createView中最后若一個(gè)view是ViewStub時(shí)調(diào)用setLayoutInflater傳入的)间景,最后在ViewStub的inflate()中調(diào)用replaceSelfWithView將布局中ViewStub自己替換為inflate出來(lái)的視圖樹艺智,replaceSelfWithView也很簡(jiǎn)單就是移除和添加:
private void replaceSelfWithView(View view, ViewGroup parent) {
final int index = parent.indexOfChild(this);
parent.removeViewInLayout(this);
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
if (layoutParams != null) {
parent.addView(view, index, layoutParams);
} else {
parent.addView(view, index);
}
}
第三個(gè)問(wèn)題十拣,為什么inflate()只能用一次而setVisibility(View.VISIBLE )可以多次調(diào)用志鹃?這個(gè)在上面方法中就能發(fā)現(xiàn)曹铃,它直接把ViewStub移除,新的布局被替換進(jìn)去秘血,這也就是為什么Inflate只能調(diào)用一次的原因评甜,因?yàn)樵趇nflate()中viewParent 為空忍坷,將拋異常熔脂。另外在inflate()中實(shí)例化了mInflatedViewRef 對(duì)象柑肴,下次調(diào)用setVisibility時(shí)晰骑,由于mInflatedViewRef 不為空,就調(diào)用不到inflate()了隶症,所以可以安全的多次調(diào)用setVisibility岗宣。