前言:
Android布局文件Xml侣诺,通過setContentView(@LayoutResint layoutResID)或者LayoutInflater.from(context).inflate(int ResID)轉(zhuǎn)換為Java對象铲咨,開發(fā)工具Android Studio 提供的預(yù)覽功能众旗,開發(fā)過程中界面和業(yè)務(wù)可以并行開發(fā)姆蘸,提高了開發(fā)效率。以下分析過程是基于 Android API 25 Platform 源碼宾尚,并以setContentView()方法為入口孝情。
Xml 轉(zhuǎn)成 Java 對象方式
1、Activity中setContentView(@LayoutResint layoutResID)方法著恩;該方法都會被每個(gè)繼承 android.app.Activity 的子類重載院尔;
2、LayoutInflater.from(Context context).inflate(@LayoutResint resource, ...)喉誊。
一般使用的 Activity 可能是
1). android.support.v7.app.AppCompatActivity
2). android.support.v4.app.FragmentActivity
3). android.app.Activity
4). 其他 Activity
從Activity中setContentView()方法開始
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
跟蹤下getWindow()源碼:
public Window getWindow() {
return mWindow;
}
mWindow在activity中attach()方法里初始化
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window) {
...
mWindow = new PhoneWindow(this, window);
...
}
所以Window.java的實(shí)現(xiàn)類是PhoneWindow.java類邀摆,@hide
代表 PhoneWindow
的源碼在 sdk 里面是隱藏的,查看 PhoneWindow.setContentView(layoutResID)
如下:
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
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;
}
從上面代碼可以發(fā)現(xiàn)如果沒有轉(zhuǎn)場動畫時(shí)伍茄,執(zhí)行的是
mLayoutInflater.inflate(layoutResID, mContentParent);
在PhoneWindow構(gòu)造函數(shù)里發(fā)現(xiàn)mLayoutInflater對象賦值代碼如下:
public PhoneWindow(Context context) {
super(context);
mLayoutInflater = LayoutInflater.from(context);
}
所以可以得出一個(gè)結(jié)論 Activity.setContentView(resId) 最終還是使用LayoutInflater.from(context).inflate(resId, ……)栋盹。
在看下其他activity android.support.v7.app.AppCompatActivity
和android.support.v4.app.FragmentActivity
發(fā)現(xiàn) android.support.v4.app.FragmentActivity
沒有重載 android.app.Activity.setContentView(resId)
但是 android.support.v7.app.AppCompatActivity
重載了
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
getDelegate()源代碼最終會調(diào)用到 android.support.v7.app.AppCompatDelegateImplV9.setContentView(resId)
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}
因此xml 轉(zhuǎn)成 Java 對象是通過LayoutInflater
的inflate()
方法來完成的
關(guān)鍵字abstract
,LayoutInflater
是一個(gè)抽象類敷矫,不能實(shí)例化例获,LayoutInflater
對象獲取的方式有:
1). 在 Activity 中通過 getLayoutInflater() 獲取
2). LayoutInflater里靜態(tài)方法from(context) 獲取
3). context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) 獲取
如 Activity 的 getLayoutInflater()
/**
* Convenience for calling
* {@link android.view.Window#getLayoutInflater}.
*/
@NonNull
public LayoutInflater getLayoutInflater() {
return getWindow().getLayoutInflater();
}
可以看出 Activity 通過 getLayoutInflater() 獲取的是 PhoneWindow 的 mLayoutInflater音念。
LayoutInflater.from(context)
/**
* Obtains the LayoutInflater from the given 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;
}
所以LayoutInflater對象都是通過服務(wù)獲取 LayoutInflater 實(shí)例對象
跟蹤下源碼context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
Context
的實(shí)現(xiàn)類是ContextImpl.java
,如:
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
繼續(xù)跟蹤 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;
}
/**
* Statically registers a system service with the context.
* This method must be called during static initialization only.
*/
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);
}
在 SystemServiceRegistry 類躏敢,這里只注冊各種系統(tǒng)服務(wù)的處闷愤,通過 Context.LAYOUT_INFLATER_SERVICE找到注冊代碼地方,如下:
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
通過以上代碼發(fā)現(xiàn) LayoutInflater
的實(shí)現(xiàn)類是 PhoneLayoutInflater
LayoutInflater 讀取 Xml 文件并創(chuàng)建 View 對象件余,繼續(xù)跟蹤LayoutInflater.inflate()方法
1).View inflate(@LayoutRes int resource, @Nullable ViewGroup root)
2).View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
重點(diǎn)看第二個(gè)方法讥脐,代碼如下:
/**
* Inflate a new view hierarchy from the specified xml resource. Throws
* {@link InflateException} if there is an error.
*
* @param resource ID for an XML layout resource to load (e.g.,
* <code>R.layout.main_page</code>)
* @param root Optional view to be the parent of the generated hierarchy (if
* <em>attachToRoot</em> is true), or else simply an object that
* provides a set of LayoutParams values for root of the returned
* hierarchy (if <em>attachToRoot</em> is false.)
* @param attachToRoot Whether the inflated hierarchy should be attached to
* the root parameter? If false, root is only used to create the
* correct subclass of LayoutParams for the root view in the XML.
* @return The root View of the inflated hierarchy. If root was supplied and
* attachToRoot is true, this is root; otherwise it is the root of
* the inflated XML file.
*/
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();
}
}
根據(jù)以上代碼邏輯,首先通過 resource
對象把 resId 指向的 xml 文件轉(zhuǎn)換為XmlResourceParser
啼器,然后執(zhí)行inflate(parser, root, attachToRoot)
方法旬渠,核心代碼如下:
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 {
// Look for the root node.
...
final String name = parser.getName();
//分析1
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");
}
//分析2
rInflate(parser, root, inflaterContext, attrs, false);
} else {
//分析3
// 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);
}
}
//分析4
// 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;
}
}
//異常處理部分
return result;
}
}
分析1:
如果 Xml 根標(biāo)簽是 TAG_MERGE(即merge)
,則 root 不能為空端壳, attachToRoot 為 true告丢,在執(zhí)行rInflate(parser, root, inflaterContext, attrs, false)
分析2 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();
}
}
rInflate(parser, root, inflaterContext, attrs, false)
總結(jié)如下
1). while遍歷該節(jié)點(diǎn)的子節(jié)點(diǎn)
2). 子節(jié)點(diǎn)有 "requestFocus"、"tag"损谦、""岖免、"include"
3). 子節(jié)點(diǎn)不能是 "merge"
4). 子節(jié)點(diǎn)的其他情況,則是各種 View 的標(biāo)簽
5). View 標(biāo)簽和 "include" 標(biāo)簽會創(chuàng)建 View 對象
6). 遍歷結(jié)束以后執(zhí)行 parent.onFinishInflate()
如果子節(jié)點(diǎn)是 include照捡,則執(zhí)行 parseInclude() ,parseInclude() 的源碼和 inflate(parser, root, attachToRoot) 類似颅湘,都是讀取xml對應(yīng)的文件,轉(zhuǎn)換成 XmlResourceParser 然后遍歷里的標(biāo)簽栗精。
createViewFromTag(parent, name, context, attrs)
負(fù)責(zé)創(chuàng)建 View 對象
分析3闯参、4
1). root 不為 null,才會讀取 xml 跟布局的 params 屬性;
2). attachToRoot 為 True 悲立,返回的是 root 對象鹿寨。否則返回的是 xml 創(chuàng)建的根標(biāo)簽指定的 View
3). 調(diào)用了 createViewFromTag(root, name, inflaterContext, attrs) 方法創(chuàng)建 View
4). rInflateChildren()->rInflate();和 分析2一樣的
綜上所述,LayoutInflater.createViewFromTag()
創(chuàng)建 View 對象薪夕,源碼如下:
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;
//異常處理
...
}
mFactory2
脚草、mFactory
、mPrivateFactory
三個(gè)對象寥殖,似乎都是可以創(chuàng)建 View , 對于android.app.Activity玩讳,這三個(gè)對象為 null 或者空實(shí)現(xiàn),創(chuàng)建 View 對象直接看如下代碼:
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;
}
注:如果 name
屬性里面含有.
表示這是一個(gè)自定義 View嚼贡,系統(tǒng)自帶 View 我們可以省略類的路徑熏纯,而自定義 View 則不能省略
自定義View創(chuàng)建,核心代碼如下:
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) {
// 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;
//異常處理
......
}
以上代碼可以看出constructor.newInstance(args)
粤策,通過反射創(chuàng)建 View 對象
對于 Android 內(nèi)置的各種 View 在 LayoutInflater 的實(shí)現(xiàn)類PhoneLayoutInflater
中重載了onCreateView()
方法
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
@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) {
// In this case we want to let the base class take a crack
// at it.
}
}
return super.onCreateView(name, attrs);
}
LayoutInflater 中的代碼如下:
protected View onCreateView(View parent, String name, AttributeSet attrs)
throws ClassNotFoundException {
return onCreateView(name, attrs);
}
protected View onCreateView(String name, AttributeSet attrs)
throws ClassNotFoundException {
return createView(name, "android.view.", attrs);
}
對于系統(tǒng)內(nèi)置的 View樟澜,會依次在 View 的標(biāo)簽前面加上android.widget.
,android.webkit.
,android.app.
秩贰,android.view.
然后通過反射的方法創(chuàng)建 View霹俺。