"我正在参加「掘金·启航计划」" 这是我的第3篇文章
之前也研究分析过xml的解析流程,趁这次机会复习下做个笔记 [旺柴]
一、使用分析入手
现在关注下面的setContentView(R.layout.activity_main),从使用开始追踪分析
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
1.1、AppCompatActivity中的setContentView
public void setContentView(@LayoutRes int layoutResID) {
this.getDelegate().setContentView(layoutResID);
}
1.2、追踪到AppCompatDelegate.class,这个为class文件的抽象方法,具体追踪到AppCompatDelegateImpl.class
...
public abstract void setContentView(@LayoutRes int var1);
...
AppCompatDelegateImpl.class,看下面的 LayoutInflater.from(this.mContext).inflate(resId, contentParent);方法的inflate
public void setContentView(int resId) {
this.ensureSubDecor();
ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);
contentParent.removeAllViews();
LayoutInflater.from(this.mContext).inflate(resId, contentParent);//这里
this.mOriginalWindowCallback.onContentChanged();
}
1.3、下面就是我们常见的LayoutInflater文件的解析过程
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();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: "" + res.getResourceName(resource) + "" ("
+ Integer.toHexString(resource) + ")");
}
//1.xml的解析,完成我们的布局文件的读取
final XmlResourceParser parser = res.getLayout(resource);
try {
//2.加载解析文件中的view
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
1. XmlResourceParser,上面标记为1的地方
public XmlResourceParser getLayout(@LayoutRes int id) throws NotFoundException {
return loadXmlResourceParser(id, "layout");
}
XmlResourceParser loadXmlResourceParser(@AnyRes int id, @NonNull String type)
throws NotFoundException {
final TypedValue value = obtainTempTypedValue();
try {
final ResourcesImpl impl = mResourcesImpl;
impl.getValue(id, value, true);
if (value.type == TypedValue.TYPE_STRING) {
//加载xml布局文件
return impl.loadXmlResourceParser(value.string.toString(), id,
value.assetCookie, type);
}
throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ " type #0x" + Integer.toHexString(value.type) + " is not valid");
} finally {
releaseTempTypedValue(value);
}
}
2.inflate(parser, root, attachToRoot),上面标记为2的地方
1.4、inflate(parser, root, attachToRoot)的解析加载view的过程
// parser:XML文档解析器对象,通过pull解析方式解析xml文档
// Resources类中的XmlResourceParser getLayout(@LayoutRes int id)方法获取
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
//进行trace文件生成,用于后续观测
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;
// 返回结果,默认返回root对象,下面的代码会对result进行操作
View result = root;
try {
// 调用 advanceToRootNode(parser) 方法,直接将方法中的代码移动到这里来,具体代码如下
// ================= advanceToRootNode(parser) ================= //
// 将给定的解析器推进到第一个 START_TAG。如果没有找到开始标签,则抛出 InflateException
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!");
}
// ================= advanceToRootNode(parser) ================= //
// 获取第一个节点名称
final String name = parser.getName();
//TAG_MERGE 的值= “merge”
if (TAG_MERGE.equals(name)) {
// 如果节点是merge并且root为空或者attachToRoot为false,抛出异常
// (因为merge标签能够将该标签中的所有控件直接连在上一级布局上面,
// 从而减少布局层级,假如一个线性布局替换为merge标签,那么原线性布局下的多个控件将直接连在上一层结构上)
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
// 实例化 root 的子视图,并且将实例化后的View添加到root中(root.addView()方法)。
// 然后调用 root的onFinishInflate()
//1.4.1,跟进这里
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// 通过createViewFromTag()方法用于根据节点名来创建View对象,这里是创建根视图。
// 在方法内部调用了createView()方法,createView()方法中通过反射创建View对象并返回。
// 这里的 name 就是第一个节点名称,也就是xml布局的根节点,temp 就表示根布局的View
// 1.4.2,跟进这里
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
// root不为null时,获取root中的布局参数
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// root不为null并且attachToRoot为false时,将root的布局参数设置给temp
temp.setLayoutParams(params);
}
}
// 调用rInflateChildren()方法递归创建每一个孩子视图,rInflateChildren()方法内部会调用rInflate()方法。
// 在SDK版本小于23当中就是直接调用rInflate()方法
rInflateChildren(parser, temp, attrs, true);
// 如果root不为null 并且 attachToRoot为true时,就把temp加到root上,相当于给temp增加一个父节点
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// 如果root为null 或者attachToRoot为false,那么就将temp赋值给result作为结果返回
if (root == null || !attachToRoot) {
result = temp;
}
}
}
// 返回 result
return result;
}
}
- 看上面代码的
1.4.1标记处rInflate(parser, root, inflaterContext, attrs, false); - 看上面代码的
1.4.2标记处createViewFromTag(root, name, inflaterContext, attrs);
1.4.1、如下
/**
* Recursive method used to descend down the xml hierarchy and instantiate
* views, instantiate their children, and then call onFinishInflate().
* <p>
* <strong>Note:</strong> Default visibility so the BridgeInflater can
* override it.
递归方法用于向下xml层次结构和实例化
*视图,实例化它们的子视图,然后调用onfinishinflation()。
*/
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 {
// 调用createViewFromTag()方法创建View对象
final View view = createViewFromTag(parent, name, context, attrs);
//父节点
final ViewGroup viewGroup = (ViewGroup) parent;
//获取布局参数
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
// 递归调用rInflateChildren()方法,继续往下层inflate
rInflateChildren(parser, view, attrs, true);
// 将通过createViewFromTag()方法创建View对象添加到父节点中
viewGroup.addView(view, params);
}
}
if (pendingRequestFocus) {
parent.restoreDefaultFocus();
}
if (finishInflate) {
parent.onFinishInflate();
}
}
1.4.2、如下,获取view的对象,有些是直接new对象,有些是反射,具体看factory2
/**
* Convenience method for calling through to the five-arg createViewFromTag
* method. This method passes {@code false} for the {@code ignoreThemeAttr}
* argument and should be used for everything except {@code >include>}
* tag parsing.
*/
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) {
//1.4.2.1 看这里,如果对view的加载流程熟悉的话看到这个Factory2就有些印象了,换肤就是利用这个特性
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 {
//1.4.2.2,没设置任何Factory,这里进行反射加载
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (InflateException e) {
throw e;
} catch (ClassNotFoundException e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
}
}
1.4.2中上面的代码//1.4.2.1 注释处看下面1.5 ,-----与//1.4.2.2看下面1.6注释处如下
1.5、Factory2.onCreateView(parent, name, context, attrs)加载初始化流程
Factory2.onCreateView的分析流程如下:
1.5.1、Factory2.onCreateView跟踪
Factory2它是一个抽象接口,我们可以找到AppCompatDelegateImpl.java,至于下面为什么选择AppCompatDelegateImpl,对activity熟悉都知道大部分ui由AppCompatDelegate初始化控制,而AppCompatDelegateImpl继承于AppCompatDelegate
看AppCompatDelegateImpl中的onCreateView方法如下图
1.5.2、真实创建view的地方createView
mAppCompatViewInflater.createView,关注下面的这段代码
@Override
public View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
if (mAppCompatViewInflater == null) {
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
String viewInflaterClassName =
a.getString(R.styleable.AppCompatTheme_viewInflaterClass);
if ((viewInflaterClassName == null)
|| AppCompatViewInflater.class.getName().equals(viewInflaterClassName)) {
// Either default class name or set explicitly to null. In both cases
// create the base inflater (no reflection)
mAppCompatViewInflater = new AppCompatViewInflater();
} else {
try {
Class viewInflaterClass = Class.forName(viewInflaterClassName);
mAppCompatViewInflater =
(AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor()
.newInstance();
} catch (Throwable t) {
Log.i(TAG, "Failed to instantiate custom view inflater "
+ viewInflaterClassName + ". Falling back to default.", t);
mAppCompatViewInflater = new AppCompatViewInflater();
}
}
}
boolean inheritContext = false;
if (IS_PRE_LOLLIPOP) {
inheritContext = (attrs instanceof XmlPullParser)
// If we have a XmlPullParser, we can detect where we are in the layout
? ((XmlPullParser) attrs).getDepth() > 1
// Otherwise we have to use the old heuristic
: shouldInheritContext((ViewParent) parent);
}
return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */
true, /* Read read app:theme as a fallback at all times for legacy reasons */
VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
);
}
1.5.3、主要关注mAppCompatViewInflater.createView
下面就是兼容new一个对象出来,至于 Factory2 在AppCompatDelegateImpl中的在哪初始化,看下面的1.5.4
final View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext,
boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
final Context originalContext = context;
// We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
// by using the parent's context
if (inheritContext && parent != null) {
context = parent.getContext();
}
if (readAndroidTheme || readAppTheme) {
// We then apply the theme on the context, if specified
context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
}
if (wrapContext) {
context = TintContextWrapper.wrap(context);
}
View view = null;
// We need to 'inject' our tint aware Views in place of the standard framework versions
switch (name) {
case "TextView":
view = createTextView(context, attrs);
verifyNotNull(view, name);
break;
case "ImageView":
view = createImageView(context, attrs);
verifyNotNull(view, name);
break;
case "Button":
view = createButton(context, attrs);
verifyNotNull(view, name);
break;
case "EditText":
view = createEditText(context, attrs);
verifyNotNull(view, name);
break;
case "Spinner":
view = createSpinner(context, attrs);
verifyNotNull(view, name);
break;
case "ImageButton":
view = createImageButton(context, attrs);
verifyNotNull(view, name);
break;
case "CheckBox":
view = createCheckBox(context, attrs);
verifyNotNull(view, name);
break;
case "RadioButton":
view = createRadioButton(context, attrs);
verifyNotNull(view, name);
break;
case "CheckedTextView":
view = createCheckedTextView(context, attrs);
verifyNotNull(view, name);
break;
case "AutoCompleteTextView":
view = createAutoCompleteTextView(context, attrs);
verifyNotNull(view, name);
break;
case "MultiAutoCompleteTextView":
view = createMultiAutoCompleteTextView(context, attrs);
verifyNotNull(view, name);
break;
case "RatingBar":
view = createRatingBar(context, attrs);
verifyNotNull(view, name);
break;
case "SeekBar":
view = createSeekBar(context, attrs);
verifyNotNull(view, name);
break;
default:
// The fallback that allows extending class to take over view inflation
// for other tags. Note that we don't check that the result is not-null.
// That allows the custom inflater path to fall back on the default one
// later in this method.
view = createView(context, name, attrs);
}
if (view == null && originalContext != context) {
// If the original context does not equal our themed context, then we need to manually
// inflate it using the name so that android:theme takes effect.
view = createViewFromTag(context, name, attrs);
}
if (view != null) {
// If we have created a view, check its android:onClick
checkOnClickListener(view, attrs);
}
return view;
}
1.5.4、Factory2 在AppCompatDelegateImpl中什么地方初始化
有个installViewFactory方法初始化了Factory2
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(this.mContext);
if (layoutInflater.getFactory() == null) {
LayoutInflaterCompat.setFactory2(layoutInflater, this);
} else if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
Log.i("AppCompatDelegate", "The Activity's LayoutInflater already has a Factory installed so we can not install AppCompat's");
}
}
调用到了AppCompatActivity---》oncreate方法
上图的getDelegate方法如下
@NonNull
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
它创建了AppCompatDelegateImpl对象,然后初始化了Factory2
/**
* Create a {@link androidx.appcompat.app.AppCompatDelegate} to use with {@code activity}.
*
* @param callback An optional callback for AppCompat specific events
*/
public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
return new AppCompatDelegateImpl(activity, activity.getWindow(), callback);
}
到这里上面1.4.2中上面的1.4.2.1注释处的流程已经完成,下面是1.4.2中上面的1.4.2.2的流程,看1.6
1.6、反射创建view的流程
....
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
//`1.4.2.2`
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
....
/**
* Low-level function for instantiating a view by name. This attempts to
* instantiate a view class of the given <var>name</var> found in this
* LayoutInflater's ClassLoader.
*
* <p>
* There are two things that can happen in an error case: either the
* exception describing the error will be thrown, or a null will be
* returned. You must deal with both possibilities -- the former will happen
* the first time createView() is called for a class of a particular name,
* the latter every time there-after for that class name.
*
* @param name The full name of the class to be instantiated.
* @param attrs The XML attributes supplied for this instance.
*
* @return View The newly instantiated view, or null.
*/
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 lastContext = mConstructorArgs[0];
if (mConstructorArgs[0] == null) {
// Fill in the context if not already within inflation.
mConstructorArgs[0] = mContext;
}
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]));
}
mConstructorArgs[0] = lastContext;
return view;
} catch (NoSuchMethodException e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + (prefix != null ? (prefix + name) : name), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (ClassCastException e) {
// If loaded class is not a View subclass
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Class is not a View " + (prefix != null ? (prefix + name) : name), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (ClassNotFoundException e) {
// If loadClass fails, we should propagate the exception.
throw e;
} catch (Exception e) {
final InflateException ie = new InflateException(
attrs.getPositionDescription() + ": Error inflating class "
+ (clazz == null ? "<unknown>" : clazz.getName()), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
好了到这里流程也就结束了
总结: 对于没继承AppCompatActivity的xml解析基础控件还是通过反射创建对象,继承的AppCompatActivity的话是通过factory根据字符串匹配new出一个新对象,根据这个特点我们可以自定义factory2辅助自定义view创建出我们需要的对象。