Android LayoutInflater加載布局詳解及實(shí)例代碼
Android LayoutInflater加載布局詳解
對(duì)于有一定Android開(kāi)發(fā)經(jīng)驗(yàn)的同學(xué)來(lái)說(shuō),一定使用過(guò)LayoutInflater.inflater()來(lái)加載布局文件,但并不一定去深究過(guò)它的原理,比如
1.LayoutInflater為什么可以加載layout文件?
2.加載layout文件之后,又是怎么變成供我們使用的View的?
3.我們定義View的時(shí)候,如果需要在布局中使用,則必須實(shí)現(xiàn)帶AttributeSet參數(shù)的構(gòu)造方法,這又是為什么呢?
既然在這篇文章提出來(lái),那說(shuō)明這三個(gè)問(wèn)題都是跟LayoutInflater脫不了干系的。在我們的分析過(guò)程中,會(huì)對(duì)這些問(wèn)題一一進(jìn)行解答。
我們一步一步來(lái),首先當(dāng)我們需要從layout中加載View的時(shí)候,會(huì)調(diào)用這個(gè)方法
LayoutInflater.from(context).inflater(R.layout.main_activity,null);
1.如何創(chuàng)建LayoutInflater?
這有什么值得說(shuō)的?如果你打開(kāi)了LayoutInflater.Java你自然就明白了,LayoutInflater是一個(gè)抽象類(lèi),而抽象類(lèi)是不能直接被實(shí)例化的,也就是說(shuō)我們創(chuàng)建的對(duì)象肯定是LayoutInflater的某一個(gè)實(shí)現(xiàn)類(lèi)。
我們進(jìn)入LayoutInflater.from方法中可以看到
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;
}
好吧,是獲取的系統(tǒng)服務(wù)!是從context中獲取,沒(méi)吃過(guò)豬肉還沒(méi)見(jiàn)過(guò)豬跑么,一說(shuō)到context對(duì)象十有八九是說(shuō)得ContextImpl對(duì)象,于是我們直接去到ContextImpl.java中,找到getSystemService方法
@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;
}
由代碼可知,我們的Service是從SYSTEM_SERVICE_FETCHERS這個(gè)HashMap中獲得的,而稍微看一下代碼就會(huì)發(fā)現(xiàn),這個(gè)HashMap是在static模塊中賦值的,這里注冊(cè)了很多的系統(tǒng)服務(wù),什么ActivityService,什么AlarmService等等都是在這個(gè)HashMap中。從LayoutInflater.from方法中可以知道,我們找到是Context.LAYOUT_INFLATER_SERVICE對(duì)應(yīng)的Service
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
好啦,主角終于登場(chǎng)了——PhoneLayoutInflater,我們獲取的LayoutInflater就是這個(gè)類(lèi)的對(duì)象。
那么,這一部分的成果就是我們找到了PhoneLayoutInflater,具體有什么作用,后面再說(shuō)。
2.inflater方法分析
這個(gè)才是最重要的方法,因?yàn)榫褪沁@個(gè)方法把我們的layout轉(zhuǎn)換成了View對(duì)象。這個(gè)方法直接就在LayoutInflater抽象類(lèi)中定義
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
傳入的參數(shù)一個(gè)是layout的id,一個(gè)是是否指定ParentView,而真正的實(shí)現(xiàn)我們還得往下看
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();
}
}
我們先從context中獲取了Resources對(duì)象,然后通過(guò)res.getLayout(resource)方法獲取一個(gè)xml文件解析器XmlResourceParser(關(guān)于在Android中的xml文件解析器這里就不詳細(xì)講了,免得扯得太遠(yuǎn),不了解的同學(xué)可以在網(wǎng)上查找相關(guān)資料閱讀),而這其實(shí)是把我們定義layout的xml文件給加載進(jìn)來(lái)了。
然后,繼續(xù)調(diào)用了另一個(gè)inflate方法
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
final Context inflaterContext = mContext;
//快看,View的構(gòu)造函數(shù)中的attrs就是這個(gè)?。?!
final AttributeSet attrs = Xml.asAttributeSet(parser);
//這個(gè)數(shù)組很重要,從名字就可以看出來(lái),這是構(gòu)造函數(shù)要用到的參數(shù)
mConstructorArgs[0] = inflaterContext;
View result = root;
try {
// 找到根節(jié)點(diǎn),找到第一個(gè)START_TAG就跳出while循環(huán),
// 比如<TextView>是START_TAG,而</TextView>是END_TAG
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
//獲取根節(jié)點(diǎn)的名稱(chēng)
final String name = parser.getName();
//判斷是否用了merge標(biāo)簽
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 {
// 這里需要調(diào)用到PhoneLayoutInflater中的方法,獲取到根節(jié)點(diǎn)對(duì)應(yīng)的View
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
//如果指定了parentView(root),則生成layoutParams,
//并且在后面會(huì)將temp添加到root中
if (root != null) {
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
// 上面解析了根節(jié)點(diǎn),這里解析根節(jié)點(diǎn)下面的子節(jié)點(diǎn)
rInflateChildren(parser, temp, attrs, true);
if (root != null && attachToRoot) {
root.addView(temp, params);
}
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;
}
}
這個(gè)就稍微有點(diǎn)長(zhǎng)了,我稍微去除了一些跟邏輯無(wú)關(guān)的代碼,并且添加了注釋?zhuān)绻心托目吹脑拺?yīng)該是能看懂了。這里主要講兩個(gè)部分,首先是rInflateChildren這個(gè)方法,其實(shí)就是一層一層的把所有節(jié)點(diǎn)取出來(lái),然后通過(guò)createViewFromTag方法將其轉(zhuǎn)換成View對(duì)象。所以重點(diǎn)是在如何轉(zhuǎn)換成View對(duì)象的。
3.createViewFromTag
我們一層層跟進(jìn)代碼,最后會(huì)到這里
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
......
......
try {
......
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
//不含“.” 說(shuō)明是系統(tǒng)自帶的控件
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (InflateException e) {
throw e;
......
}
}
為了方便理解,將無(wú)關(guān)的代碼去掉了,我們看到其實(shí)就是調(diào)用的createView方法來(lái)從xml節(jié)點(diǎn)轉(zhuǎn)換成View的。如果name中不包含'.' 就調(diào)用onCreateView方法,否則直接調(diào)用createView方法。
在上面的PhoneLayoutInflater中就復(fù)寫(xiě)了onCreateView方法,而且不管是否重寫(xiě),該方法最后都會(huì)調(diào)用createView。唯一的區(qū)別應(yīng)該是系統(tǒng)的View的完整類(lèi)名由onCreateView來(lái)提供,而如果是自定義控件在布局文件中本來(lái)就是用的完整類(lèi)名。
4. createView方法
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
//1.通過(guò)傳入的類(lèi)名,獲取該類(lèi)的構(gòu)造器
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);
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);
}
}
}
//2.通過(guò)獲得的構(gòu)造器,創(chuàng)建View實(shí)例
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]));
}
return view;
} catch (NoSuchMethodException e) {
......
}
}
這段代碼主要做了兩件事情
第一,根據(jù)ClassName將類(lèi)加載到內(nèi)存,然后獲取指定的構(gòu)造器constructor。構(gòu)造器是通過(guò)傳入?yún)?shù)類(lèi)型和數(shù)量來(lái)指定,這里傳入的是mConstructorSignature
static final Class<?>[] mConstructorSignature = new Class[] {
Context.class, AttributeSet.class};
即傳入?yún)?shù)是Context和AttributeSet,是不是猛然醒悟了?。。?strong>這就是為什么我們?cè)谧远xView的時(shí)候,必須要重寫(xiě)View(Context context, AttributeSet attrs)則個(gè)構(gòu)造方法,才能在layout中使用我們的View。
第二,使用獲得的構(gòu)造器constructor來(lái)創(chuàng)建一個(gè)View實(shí)例。
5.回答問(wèn)題
還記得上面我們提到的三個(gè)問(wèn)題嗎?現(xiàn)在我們來(lái)一一解答:
1.LayoutInflater為什么可以加載layout文件?
因?yàn)長(zhǎng)ayoutInflater其實(shí)是通過(guò)xml解析器來(lái)加載xml文件,而layout文件的格式就是xml,所以可以讀取。
2.加載layout文件之后,又是怎么變成供我們使用的View的?
LayoutInflater加載到xml文件中內(nèi)容之后,通過(guò)反射將每一個(gè)標(biāo)簽的名字取出來(lái),并生成對(duì)應(yīng)的類(lèi)名,然后通過(guò)反射獲得該類(lèi)的構(gòu)造器函數(shù),參數(shù)為Context和AttributeSet。然后通過(guò)構(gòu)造器創(chuàng)建View對(duì)象。
3.我們定義View的時(shí)候,如果需要在布局中使用,則必須實(shí)現(xiàn)帶AttributeSet參數(shù)的構(gòu)造方法,這又是為什么呢?
因?yàn)長(zhǎng)ayoutInflater在解析xml文件的時(shí)候,會(huì)將xml中的內(nèi)容轉(zhuǎn)換成一個(gè)AttributeSet對(duì)象,該對(duì)象中包含了在xml文件設(shè)定的屬性值。需要在構(gòu)造函數(shù)中將這些屬性值取出來(lái),賦給該實(shí)例的屬性。
感謝閱讀,希望能幫助到大家,謝謝大家對(duì)本站的支持!
相關(guān)文章
Android UI設(shè)計(jì)系列之自定義DrawView組件實(shí)現(xiàn)數(shù)字簽名效果(5)
這篇文章主要介紹了Android UI設(shè)計(jì)系列之自定義DrawView組件實(shí)現(xiàn)數(shù)字簽名效果,具有一定的實(shí)用性和參考價(jià)值,感興趣的小伙伴們可以參考一下2016-06-06
說(shuō)說(shuō)在Android如何使用服務(wù)(Service)的方法
這篇文章主要介紹了說(shuō)說(shuō)在Android如何使用服務(wù)(Service)的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-06-06
Android 用SQLite實(shí)現(xiàn)事務(wù)的方法
本篇文章小編為大家介紹,Android用SQLite實(shí)現(xiàn)事務(wù)的方法。需要的朋友參考下2013-04-04
Android 顯示和隱藏輸入法實(shí)現(xiàn)代碼
本文所要介紹的這個(gè)方法可以轉(zhuǎn)換軟件輸入法在窗體中的顯示狀態(tài),具體實(shí)現(xiàn)代碼如下,感興趣的你可以參考下哈,希望可以幫助到你2013-03-03
Android開(kāi)發(fā)之拖動(dòng)條和評(píng)分組件用法分析
這篇文章主要介紹了Android開(kāi)發(fā)之拖動(dòng)條和評(píng)分組件用法,結(jié)合實(shí)例形式較為詳細(xì)的分析了Android拖動(dòng)條及評(píng)分組件的布局與功能實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-07-07
Android實(shí)現(xiàn)分享微信好友及出現(xiàn)閃退的解決辦法
這篇文章主要介紹了Android實(shí)現(xiàn)分享微信好友及出現(xiàn)閃退的解決辦法的相關(guān)資料,需要的朋友可以參考下2016-03-03

