首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

  • 24-12-06 00:05
  • 2590
  • 7394
juejin.cn

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); }

两个参数:

  1. resource:资源 id
  2. 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。三个参数:

  1. resource:资源id
  2. root:生成层次结构的父级,可选
  3. 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 的构造器 Constructorextends View> constructor = sConstructorMap.get(name); // 构造器不为空,并且类加载器验证失败,将构造器从缓存中移除 if (constructor != null && !verifyClassLoader(constructor)) { constructor = null; sConstructorMap.remove(name); } Classextends 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 过程全部结束。

注:本文转载自juejin.cn的无限进化的文章"https://juejin.cn/post/7438974021942378533"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top