温故知新.探究Android 页面解析加载流程

210 阅读10分钟

"我正在参加「掘金·启航计划」" 这是我的第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. 看上面代码的 1.4.1标记处 rInflate(parser, root, inflaterContext, attrs, false);
  2. 看上面代码的 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 &gt;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

image.png

看AppCompatDelegateImpl中的onCreateView方法如下图

image.png

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方法

image.png

image.png

上图的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创建出我们需要的对象。