1. LayoutInflater 简单使用
在 Activity 的 onCreate 方法中使用 setContentView
方法加载布局,可以将 xml 格式的 layout 布局文件解析并展示到屏幕上。
来看一下 setContentView 的具体实现:
java 代码解读复制代码// androidx/appcompat/app/AppCompatDelegateImpl.java
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
setContentView 方法内部使用了 LayoutInflater 来加载布局。LayoutInflater 是加载布局的核心。
LayoutInflater:布局填充器,主要应用于动态添加 View,将 XML 布局文件实例化到相应的 View 对象中。
除了通过 setContentView 方法加载布局,我们有时还会直接使用 LayoutInflater 加载布局。
Eg:使用 LayoutInflater 将一个按钮动态添加到 activity_main.xml 中:
新建一个布局文件 button_layout.xml:
xml 代码解读复制代码<Button xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button" >
Button>
新建 activity_main.xml:
xml 代码解读复制代码<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
LinearLayout>
新建 MainActivity.java:
java 代码解读复制代码public class MainActivity extends Activity {
private LinearLayout mainLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mainLayout = (LinearLayout) findViewById(R.id.main_layout);
// 1. 创建 LayoutInflater 对象
LayoutInflater layoutInflater = LayoutInflater.from(this);
// 2. inflate 方法加载视图
View buttonLayout = layoutInflater.inflate(R.layout.button_layout, null);
// 3. 添加视图
mainLayout.addView(buttonLayout);
}
}
接下来对使用 LayoutInflater 加载布局的过程进行源码解析。
2. LayoutInflater 源码解析
2.1 LayoutInflater 对象的创建
LayoutInflater 的基本用法,通过 from
获取 LayoutInflater 对象:
java 代码解读复制代码LayoutInflater layoutInflater = LayoutInflater.from(context);
form 方法对 getSystemService
进行了简单的封装:
java 代码解读复制代码public static LayoutInflater from(@UiContext Context context) {
LayoutInflater LayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
传递不同的 Context 对象,获取到的 LayoutInflater 对象也不同。
注意参数 Context,这里可以是 Activity、Application 或 Service。
每一个 Activity 对应一个 LayoutInflater 对象,如果每次传递的 Context 对象都是同一个 Activity 对象,只会创建一个 LayoutInflater 对象。
java 代码解读复制代码LayoutInflater activityLayoutInflater1 = LayoutInflater.from(LayoutInflaterDemoActivity.this);
LayoutInflater activityLayoutInflater2 = LayoutInflater.from(LayoutInflaterDemoActivity.this);
LayoutInflater appLayoutInflater = LayoutInflater.from(getApplicationContext());
Log.i(TAG, "activityLayoutInflater1: "+ activityLayoutInflater1 + "; activityLayoutInflater2: "+ activityLayoutInflater2 + "; appLayoutInflater: " + appLayoutInflater);
打印结果:
txt代码解读复制代码activityLayoutInflater1: com.android.internal.policy.PhoneLayoutInflater@62615a3; activityLayoutInflater2: com.android.internal.policy.PhoneLayoutInflater@62615a3; appLayoutInflater: com.android.internal.policy.PhoneLayoutInflater@7e110a0
以 Context 为 Activity 举例,分析 getSystemService 流程
如果传入 Context 为 Activity,则会调用 Activity 中的 getSystemService 方法:
java 代码解读复制代码// Activity.java
@Override
public Object getSystemService(@ServiceName @NonNull String name) {
if (getBaseContext() == null) {
throw new IllegalStateException(
"System services not available to Activities before onCreate()");
}
if (WINDOW_SERVICE.equals(name)) {
return mWindowManager;
} else if (SEARCH_SERVICE.equals(name)) {
ensureSearchManager();
return mSearchManager;
}
return super.getSystemService(name);
}
Activity 中只对 WINDOW_SERVICE
和 SEARCH_SERVICE
这两个 ServiceName 进行了处理。我们进入 Activity 的父类 ContextThemeWrapper 中查看 getSystemService 方法:
java 代码解读复制代码@Override
public Object getSystemService(String name) {
if (LAYOUT_INFLATER_SERVICE.equals(name)) {
if (mInflater == null) {
mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
}
return mInflater;
}
return getBaseContext().getSystemService(name);
}
LayoutInflater 为 null,使用 getBaseContext 新建一个 LayoutInflater 对象。否则直接返回 mInflater,不会重复创建。
getBaseContext 方法获取的就是该 Activity 绑定的 mBase 对象,这个 mBase 是一个 Context 对象 ,但 Context 是一个抽象类,那么它实际上是什么对象呢?
mBase 是一个 ContextImpl 对象,实际上会调用 ContextImpl 的 getSystemService 方法:
java 代码解读复制代码// android/app/ContextImpl.java
@Override
public Object getSystemService(String name) {
if (vmIncorrectContextUseEnabled()) {
// Check incorrect Context usage.
if (WINDOW_SERVICE.equals(name) && !isUiContext()) {
final String errorMessage = "Tried to access visual service "
+ SystemServiceRegistry.getSystemServiceClassName(name)
+ " from a non-visual Context:" + getOuterContext();
final String message = "WindowManager should be accessed from Activity or other "
+ "visual Context. Use an Activity or a Context created with "
+ "Context#createWindowContext(int, Bundle), which are adjusted to "
+ "the configuration and visual bounds of an area on screen.";
final Exception exception = new IllegalAccessException(errorMessage);
StrictMode.onIncorrectContextUsed(message, exception);
Log.e(TAG, errorMessage + " " + message, exception);
}
}
return SystemServiceRegistry.getSystemService(this, name);
}
在 ContextImpl 内部,又委托给了 SystemServiceRegistry。SystemServiceRegistry 主要负责缓存、注册和获取系统服务。在其内部的静态代码块中默认注册了大量系统服务,包括 WINDOW_SERVICE、LOCATION_SERVICE、LAYOUT_INFLATER_SERVICE 等等。
java 代码解读复制代码// android/app/SystemServiceRegistry.java
static {
...
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
// LayoutInflater 实际上是 PhoneLayoutInflater 类型
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
...
}
在 Java 中,静态代码块(static block)是一个在类加载时执行的代码块。它被用来进行类级别的初始化,比如静态变量的初始化或执行一些需要在类加载时就准备好的任务。静态代码块在类第一次被加载到 JVM 时执行,且只会执行一次。
看一下 SystemServiceRegistry 的 getSystemService:
java 代码解读复制代码// android/app/SystemServiceRegistry.java
private static final Map> SYSTEM_SERVICE_FETCHERS =
new ArrayMap>();
public static Object getSystemService(ContextImpl ctx, String name) {
if (name == null) {
return null;
}
final ServiceFetcher> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
...
final Object ret = fetcher.getService(ctx);
...
return ret;
}
SYSTEM_SERVICE_FETCHERS 是一个静态的 Map 容器,在 static {} 代码块中注册的服务都将保存在该容器中。这里首先根据服务的 name 获取对应的 Fetcher,然后通过该 Fetcher 的 getService 方法创建相应 LayoutInflater 对象。
当我们首次获取某个服务类型时,fetcher.getService 会执行其内部的 createService 创建对应服务,然后每个服务都会被保存在 SystemServiceRegistry 中,这里实际间接保存在 Fetcher 中。
java 代码解读复制代码// 优先取缓存
static abstract class CachedServiceFetcher implements ServiceFetcher {
private final int mCacheIndex;
CachedServiceFetcher() {
// Note this class must be instantiated only by the static initializer of the
// outer class (SystemServiceRegistry), which already does the synchronization,
// so bare access to sServiceCacheSize is okay here.
mCacheIndex = sServiceCacheSize++;
}
@Override
@SuppressWarnings("unchecked")
public final T getService(ContextImpl ctx) {
final Object[] cache = ctx.mServiceCache;
final int[] gates = ctx.mServiceInitializationStateArray;
boolean interrupted = false;
T ret = null;
for (;;) {
boolean doInitialize = false;
synchronized (cache) {
// Return it if we already have a cached instance.
// 优先取缓存
T service = (T) cache[mCacheIndex];
if (service != null) {
ret = service;
break; // exit the for (;;)
}
// If we get here, there's no cached instance.
// Grr... if gate is STATE_READY, then this means we initialized the service
// once but someone cleared it.
// We start over from STATE_UNINITIALIZED.
// Similarly, if the previous attempt returned null, we'll retry again.
if (gates[mCacheIndex] == ContextImpl.STATE_READY
|| gates[mCacheIndex] == ContextImpl.STATE_NOT_FOUND) {
gates[mCacheIndex] = ContextImpl.STATE_UNINITIALIZED;
}
// It's possible for multiple threads to get here at the same time, so
// use the "gate" to make sure only the first thread will call createService().
// At this point, the gate must be either UNINITIALIZED or INITIALIZING.
if (gates[mCacheIndex] == ContextImpl.STATE_UNINITIALIZED) {
doInitialize = true;
gates[mCacheIndex] = ContextImpl.STATE_INITIALIZING;
}
}
if (doInitialize) {
// Only the first thread gets here.
T service = null;
@ServiceInitializationState int newState = ContextImpl.STATE_NOT_FOUND;
try {
// This thread is the first one to get here. Instantiate the service
// *without* the cache lock held.
service = createService(ctx);
newState = ContextImpl.STATE_READY;
} catch (ServiceNotFoundException e) {
onServiceNotFound(e);
} finally {
synchronized (cache) {
cache[mCacheIndex] = service;
gates[mCacheIndex] = newState;
cache.notifyAll();
}
}
ret = service;
break; // exit the for (;;)
}
// The other threads will wait for the first thread to call notifyAll(),
// and go back to the top and retry.
synchronized (cache) {
// Repeat until the state becomes STATE_READY or STATE_NOT_FOUND.
// We can't respond to interrupts here; just like we can't in the "doInitialize"
// path, so we remember the interrupt state here and re-interrupt later.
while (gates[mCacheIndex] < ContextImpl.STATE_READY) {
try {
// Clear the interrupt state.
interrupted |= Thread.interrupted();
cache.wait();
} catch (InterruptedException e) {
// This shouldn't normally happen, but if someone interrupts the
// thread, it will.
Slog.w(TAG, "getService() interrupted");
interrupted = true;
}
}
}
}
if (interrupted) {
Thread.currentThread().interrupt();
}
return ret;
}
public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException;
}
在 createService 方法,我们发现 LayoutInflater 的实际类型是 PhoneLayoutInflater。
2.2 LayoutInflater inflate 流程
LayoutInflater 中极其重要的一个方法,inflate 方法。
在获取到具体的 PhoneLayoutInflater 对象后,使用 inflate 方法从指定的 XML 文件中加载视图:
java 代码解读复制代码public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
两个参数:
- resource:资源 id
- root:父布局,为生成层次结构的父级,没有可以传 null
接着会调用:
java 代码解读复制代码public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
...
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
root 不为空时,attachToRoot 默认为 true。三个参数:
- resource:资源id
- root:生成层次结构的父级,可选
- attachToRoot:是否将生成的层次结构添加到 root
- 如果 root 为 null,attachToRoot 无论设置为什么都无意义。
- 如果 root 不为 null,attachToRoot 设为 true,则会给加载的布局文件的指定一个父布局,即 root。
- 如果 root 不为 null,attachToRoot 设为 false,则会将布局文件最外层的所有 layout 属性进行设置,当该 view 被添加到父 view 当中时,这些 layout 属性会自动生效。
- 在不设置 attachToRoot 参数的情况下,如果 root 不为 null,attachToRoot 参数默认为 true。
无论使用哪个 inflate
方法加载布局,都会辗转到这个方法:
java 代码解读复制代码public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
...
// 获取 XML 的 AttributeSet
final AttributeSet attrs = Xml.asAttributeSet(parser);
...
View result = root;
try {
// 将给定的分析器前进到第一个 START_TAG,根节点
advanceToRootNode(parser);
// 第一个 START_TAG 的标签名
final String name = parser.getName();
...
// 如果是 merge 标签
if (TAG_MERGE.equals(name)) {
// 对于 merge 标签,必须有 root 并且 attachToRoot==true
// merge 标签必须作为 root 标签使用,并且不能用在子标签中
if (root == null || !attachToRoot) {
throw new InflateException(" can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
//r 代表 recurse 递归方法
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// 通过 Tag name 和 attrs 创建视图
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)
// attachToRoot = false,立即设置属性
temp.setLayoutParams(params);
}
}
...
// Inflate all children under temp against its context.
// 循环遍历这个根布局下的子元素
// 递归方法,根据传入的 parser 包含的层级,加载此层级的子 vie w并挂载到 temp 下面
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;
}
}
}
...
//attachToRoot为true,就返回root,反之false就返回加载的XML文件的根节点View。
return result;
}
}
LayoutInflater 使用 pull解析 方式解析布局文件。调用了 createViewFromTag 这个方法,并把节点名和参数传了进去。它是用于根据节点名来创建 View 对象的。在 createViewFromTag 方法的内部又会去调用 createView 方法,然后使用反射的方式创建出 View 的实例并返回。
java 代码解读复制代码View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
// 如果传入的视图名为"view",则尝试从属性集`attrs`中读取"class"属性作为视图的真正名字。
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
// Apply a theme wrapper, if allowed and one is specified.
// 如果传入的 attr 中包含 theme 属性,则使用 attr 中的 theme。将 theme 属性绑定到 context 上面
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 view = tryCreateView(parent, name, context, attrs);
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
//如果 tryCreateView 方法中还没加载到 view。判断 name 中是否包含“.”
if (-1 == name.indexOf('.')) {
//onCreateView 最终会调用到 createView(name, "android.view.", attrs);,会在View 名字前面添加"android.view."前缀。
view = onCreateView(context, parent, name, attrs);
} else {
//如果没有“.”,就表明是 Android 原生的 View
view = createView(context, name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
}
...
}
看一下 tryCreateView 方法:
java 代码解读复制代码@UnsupportedAppUsage(trackingBug = 122360734)
@Nullable
public final View tryCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context, @NonNull AttributeSet attrs) {
// 如果 Tag 名为 TAG_1995,返回一个 BlinkLayout 对象
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return new BlinkLayout(context, attrs);
}
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);
}
return view;
}
LayoutInflater.Factory2 继承 Factory 接口,是设计出来灵活构造 View 的接口,可以用来实现换肤或者替换 View 的功能,同时也是 AppcompatActivity 用来做兼容和版本替换的接口。允许开发者在 XML 布局文件被转换成 View 对象时,插入自己的逻辑处理。这可以用于动态地替换布局文件中的某些组件。Activity 实际上是实现了LayoutInflater.Factory2
接口的:
Factory2
是Factory
的扩展,它提供了一个额外的参数:父视图(parent view)。这使得开发者在创建视图时可以考虑到视图的父子关系,从而进行更加精细的控制。
java 代码解读复制代码public interface Factory {
@Nullable
View onCreateView(@NonNull String name, @NonNull Context context,
@NonNull AttributeSet attrs);
}
public interface Factory2 extends Factory {
@Nullable
View onCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context, @NonNull AttributeSet attrs);
}
Activity中:
java 代码解读复制代码public View onCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context, @NonNull AttributeSet attrs) {
if (!"fragment".equals(name)) {
return onCreateView(name, context, attrs);
}
return mFragments.onCreateView(parent, name, context, attrs);
}
所以我们可以直接在 Activity 里面重写 onCreateView
方法,这样就可以根据 View 的名字来实现我们的一些操作,比如换肤的操作,比如定义一个名字来表示某种自定义 View。可以看这样一个用法:
xml 代码解读复制代码<PlaceHolder
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="include LinearLayout"
android:textColor="#fff"
android:textSize="15sp" />
然后我们在重写的onCreateView
里面判断name:
java 代码解读复制代码@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
if ("PlaceHolder".equals(name)) {
return new TextView(this, attrs);
}
return super.onCreateView(name, context, attrs);
}
通过 createView 方法进行反射,创建出 View 的实例并返回:
Java 代码解读复制代码public final View createView(@NonNull Context viewContext, @NonNull String name,
@Nullable String prefix, @Nullable AttributeSet attrs)
throws ClassNotFoundException, InflateException {
// 确保 viewContext 和 name 不为 null
Objects.requireNonNull(viewContext);
Objects.requireNonNull(name);
//sConstructorMap 缓存 constructor,取出名为 name 的构造器
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);
// 没有缓存构造器,就反射得到构造器并添加到sConstructorMap中以便后面使用。
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
// constructor 为 null,加载类
clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
mContext.getClassLoader()).asSubclass(View.class);
// 如果设置过滤器 mFilter
if (mFilter != null && clazz != null) {
// 是否允许加载这个类
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
// 不允许加载,执行下列逻辑,抛出异常: Class not allowed to be inflated
failNotAllowed(name, prefix, viewContext, 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?
// 通过 name 从 mFilterMap 获取这个类的允许状态
Boolean allowedState = mFilterMap.get(name);
// 之前没有检查过这个类
if (allowedState == null) {
// New class -- remember whether it is allowed
// 加载这个类
clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
mContext.getClassLoader()).asSubclass(View.class);
boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
// 将结果放入 mFilterMap 中
mFilterMap.put(name, allowed);
if (!allowed) {
failNotAllowed(name, prefix, viewContext, attrs);
}
} else if (allowedState.equals(Boolean.FALSE)) {
failNotAllowed(name, prefix, viewContext, attrs);
}
}
}
// 设置构造器参数
Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = viewContext;
Object[] args = mConstructorArgs;
args[1] = attrs;
try {
// 通过构造器和参数创建视图实例
final View view = constructor.newInstance(args);
// 如果视图是 ViewStub 类型,则进行特定的初始化,并返回创建的视图。
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;
} finally {
mConstructorArgs[0] = lastContext;
}
}
...
}
同样的,通过上面的代码我们知道LayoutInflater是通过反射拿到构造方法来创建View的,那众所周知反射是有性能损耗的,那么我们可以在 onCreateView
方法中判断名字直接 new 出来,当然也可以跟 AppcompatActivity 里面做的一样,做一些兼容的操作来替换成不同版本的View:
java 代码解读复制代码public final View createView(View parent, final String name, @NonNull Context context,
View view = null;
switch (name) {
case "TextView":
view = new AppCompatTextView(context, attrs);
break;
case "ImageView":
view = new AppCompatImageView(context, attrs);
break;
case "Button":
view = new AppCompatButton(context, attrs);
break;
case "EditText":
view = new AppCompatEditText(context, attrs);
break;
case "Spinner":
view = new AppCompatSpinner(context, attrs);
break;
case "ImageButton":
view = new AppCompatImageButton(context, attrs);
break;
...
}
...
return view;
}
这里只是创建一个根布局的实例。
接下来,调用 rInflateChildren() 方法来查找这个 View 下的子元素,每次递归完成后则将这个 View 添加到父布局当中。
java 代码解读复制代码// 递归方法
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
// 获取解析器初始深度
final int depth = parser.getDepth();
int type;
// 是否处理焦点请求,初始为 false
boolean pendingRequestFocus = false;
// 递归结束条件:文档结束或返回到初始深度 保证当前循环只读取本层的 view
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();
//name 为 requestFocus 表示将当前控件设为焦点
if (TAG_REQUEST_FOCUS.equals(name)) {
pendingRequestFocus = true;
consumeChildElements(parser);
// tag 标签,自定义标签
} else if (TAG_TAG.equals(name)) {
parseViewTag(parser, parent, attrs);
// include 标签
} else if (TAG_INCLUDE.equals(name)) {
if (parser.getDepth() == 0) {
throw new InflateException(" cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
// merge 标签做二次判断,确保标签不会出现在非 root 元素位置
} else if (TAG_MERGE.equals(name)) {
throw new InflateException(" must be the root element");
} else {
// 若不是特殊标签,同样使用 createViewFromTag 方法创建 view 实例
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
// 用当前的 attrs 加载成 LayoutParams 设置给当前 View。
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
// 递归调用查找 view 下的子元素、同时每次递归完成后将这个 view 添加到父布局当中。
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
}
}
if (pendingRequestFocus) {
parent.restoreDefaultFocus();
}
if (finishInflate) {
parent.onFinishInflate();
}
}
parser.getDepth()
xml 代码解读复制代码 0
<root> 1
sometext 1
<foobar> 2
foobar> 2
root> 1
0
java 代码解读复制代码private void parseInclude(XmlPullParser parser, Context context, View parent,
AttributeSet attrs) throws XmlPullParserException, IOException {
int type;
// include 标签只能用在 ViewGroup 内部
if (!(parent instanceof ViewGroup)) {
throw new InflateException(" can only be used inside of a ViewGroup");
}
// Apply a theme wrapper, if requested. This is sort of a weird
// edge case, since developers think the overwrites
// values in the AttributeSet of the included View. So, if the
// included View has a theme attribute, we'll need to ignore it.
// 如果有 theme 属性从当前 View 的 attrs 里面看是否有 theme 属性,如果有就重新创建 ContextThemeWrapper
// 用当前的 theme 属性替换之前 ContextThemeWrapper 里面的 theme
final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
final int themeResId = ta.getResourceId(0, 0);
final boolean hasThemeOverride = themeResId != 0;
if (hasThemeOverride) {
context = new ContextThemeWrapper(context, themeResId);
}
ta.recycle();
// If the layout is pointing to a theme attribute, we have to
// massage the value to get a resource identifier out of it.
// 查看当前 view 的 attrs 里面是否有 layout 的 id,如果没有就返回0
int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
if (layout == 0) {
//找不到先找这个layout属性的值,看layout属性的string是否为空。为空直接报异常,不为空才去找layoutId
final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
if (value == null || value.length() <= 0) {
throw new InflateException("You must specify a layout in the"
+ " include tag: @layout /layoutID" />");
}
// Attempt to resolve the "?attr/name" string to an attribute
// within the default (e.g. application) package.
//找不到,就尝试去“?attr/”下面找对应的属性
layout = context.getResources().getIdentifier(
value.substring(1), "attr", context.getPackageName());
}
// The layout might be referencing a theme attribute.
if (mTempValue == null) {
mTempValue = new TypedValue();
}
if (layout != 0 && context.getTheme().resolveAttribute(layout, mTempValue, true)) {
layout = mTempValue.resourceId;
}
if (layout == 0) {
final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
throw new InflateException("You must specify a valid layout "
+ "reference. The layout ID " + value + " is not valid.");
}
final View precompiled = tryInflatePrecompiled(layout, context.getResources(),
(ViewGroup) parent, /*attachToRoot=*/true);
if (precompiled == null) {
final XmlResourceParser childParser = context.getResources().getLayout(layout);
try {
final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
while ((type = childParser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty.
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(getParserStateDescription(context, childAttrs)
+ ": No start tag found!");
}
final String childName = childParser.getName();
if (TAG_MERGE.equals(childName)) {
// The tag doesn't support android:theme, so
// nothing special to do here.
//如果是merge标签,不支持属性的设置。此处直接把parent作为父布局传入,也就是加载出来的子view直接挂到parent上。
rInflate(childParser, parent, context, childAttrs, false);
} else {
final View view = createViewFromTag(parent, childName,
context, childAttrs, hasThemeOverride);
final ViewGroup group = (ViewGroup) parent;
//获取include设置的id和visible。
//如果include设置了id和visible,会使用include设置的属性。
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.Include);
final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
final int visibility = a.getInt(R.styleable.Include_visibility, -1);
a.recycle();
// We try to load the layout params set in the tag.
// If the parent can't generate layout params (ex. missing width
// or height for the framework ViewGroups, though this is not
// necessarily true of all ViewGroups) then we expect it to throw
// a runtime exception.
// We catch this exception and set localParams accordingly: true
// means we successfully loaded layout params from the
// tag, false means we need to rely on the included layout params.
//先尝试使用标签的属性去创建params,判断的标准是有没有width/height属性
//若没有使用view的属性去创建,然后调用view.setLayoutParams给View设置属性
ViewGroup.LayoutParams params = null;
try {
params = group.generateLayoutParams(attrs);
} catch (RuntimeException e) {
// Ignore, just fail over to child attrs.
}
if (params == null) {
params = group.generateLayoutParams(childAttrs);
}
view.setLayoutParams(params);
// Inflate all children.
rInflateChildren(childParser, view, childAttrs, true);
// 如果标签设置了id和visibility属性则一定会替换里面的id和visibility属性
if (id != View.NO_ID) {
view.setId(id);
}
switch (visibility) {
case 0:
view.setVisibility(View.VISIBLE);
break;
case 1:
view.setVisibility(View.INVISIBLE);
break;
case 2:
view.setVisibility(View.GONE);
break;
}
group.addView(view);
}
} finally {
childParser.close();
}
}
LayoutInflater.consumeChildElements(parser);
}
最终会把最顶层的根布局返回,至此 inflate 过程全部结束。
评论记录:
回复评论: