首页 > 系统 > Android > 正文

Android从xml加载到View对象过程解析

2020-01-02 07:00:59
字体:
来源:转载
供稿:网友

我们从Activity的setContentView()入手,开始源码解析,

//Activity.setContentViewpublic void setContentView(int layoutResID) {getWindow().setContentView(layoutResID);initActionBar();}//PhoneWindow.setContentViewpublic void setContentView(int layoutResID) {if (mContentParent == null) {installDecor();} else {mContentParent.removeAllViews();}mLayoutInflater.inflate(layoutResID, mContentParent);final Callback cb = getCallback();if (cb != null && !isDestroyed()) {cb.onContentChanged();}}

发现是使用mLayoutInflater创建View的,所以我们去LayoutInflater.inflate()里面看下,

public View inflate(int resource, ViewGroup root, boolean attachToRoot) {if (DEBUG) System.out.println("INFLATING from resource: " + resource);XmlResourceParser parser = getContext().getResources().getLayout(resource);try {return inflate(parser, root, attachToRoot);} finally {parser.close();}}

先根据resource id 获取到XmlResourceParseer,意如其名,就是xml的解析器,继续往下,进入到inflate的核心方法,有些长,我们只分析关键部分:

public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {......if (TAG_MERGE.equals(name)) {if (root == null || !attachToRoot) {throw new InflateException("<merge /> can be used only with a valid "+ "ViewGroup root and attachToRoot=true");}rInflate(parser, root, attrs, false);} else {// Temp is the root view that was found in the xmlView temp;if (TAG_1995.equals(name)) {temp = new BlinkLayout(mContext, attrs);} else {temp = createViewFromTag(root, name, attrs);}......} catch (XmlPullParserException e) {InflateException ex = new InflateException(e.getMessage());ex.initCause(e);throw ex;} catch (IOException e) {InflateException ex = new InflateException(parser.getPositionDescription()+ ": " + e.getMessage());ex.initCause(e);throw ex;} finally {// Don't retain static reference on context.mConstructorArgs[0] = lastContext;mConstructorArgs[1] = null;}return result;}}

如果tag的名字不是TAG_1995(名字是个梗),就调用函数createViewFromTag()创建View,进去看看,

View createViewFromTag(View parent, String name, AttributeSet attrs) {if (name.equals("view")) {name = attrs.getAttributeValue(null, "class");}......View view;if (mFactory2 != null) view = mFactory2.onCreateView(parent, name, mContext, attrs);else if (mFactory != null) view = mFactory.onCreateView(name, mContext, attrs);else view = null;if (view == null && mPrivateFactory != null) {view = mPrivateFactory.onCreateView(parent, name, mContext, attrs);}if (view == null) {if (-1 == name.indexOf('.')) {view = onCreateView(parent, name, attrs);} else {view = createView(name, null, attrs);}}if (DEBUG) System.out.println("Created view is: " + view);return view;......}

首先尝试用3个Fractory创建View,如果成功就直接返回了。注意,我们可以利用这个机制,创建自己的Factory来控制View的创建过程。

如果没有Factory或创建失败,那么走默认逻辑。

先判断name中是否有'.'字符,如果没有,则认为使用android自己的View,此时会在name的前面加上包名"android.view.";如果有这个'.',则认为使用的自定义View,这时无需添加任何前缀,认为name已经包含全包名了。

最终,使用这个全包名的name来创建实例,

private static final HashMap<String, Constructor<? extends View>> sConstructorMap =new HashMap<String, Constructor<? extends View>>();protected View onCreateView(String name, AttributeSet attrs)throws ClassNotFoundException {return createView(name, "android.view.", attrs);}public final View createView(String name, String prefix, AttributeSet attrs)throws ClassNotFoundException, InflateException {Constructor<? extends View> constructor = sConstructorMap.get(name);Class<? extends View> clazz = null;   ......if (constructor == null) {// Class not found in the cache, see if it's real, and try to add itclazz = 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);sConstructorMap.put(name, constructor);} else {// If we have a filter, apply it to cached constructorif (mFilter != null) {// Have we seen this name before?Boolean allowedState = mFilterMap.get(name);if (allowedState == null) {// New class -- remember whether it is allowedclazz = mContext.getClassLoader().loadClass(prefix != null ? (prefix + name) : name).asSubclass(View.class);boolean allowed = clazz != null && mFilter.onLoadClass(clazz);mFilterMap.put(name, allowed);if (!allowed) {failNotAllowed(name, prefix, attrs);}} else if (allowedState.equals(Boolean.FALSE)) {failNotAllowed(name, prefix, attrs);}}}Object[] args = mConstructorArgs;args[1] = attrs;return constructor.newInstance(args);......}

从源码中看到,在创建实例前,会先从一个静态Map中获取缓存,

Constructor<? extends View> constructor = sConstructorMap.get(name);

缓存的是Constructor对象,目的是用于创建实例,这里要注意sConstructorMap是静态的,并且通过Constructor创建的实例,是使用和Constructor对象同一个ClassLoader来创建的,换句话说,在同一个进程中,同一个自定义View对象,是无法用不同ClassLoader加载的,如果想解决这个问题,就不要让系统使用createView()接口创建View,做法就是自定义Factory或Factory2来自行创建View。

继续往下看,如果缓存里没有,则创建View的Class对象clazz,并缓存到sConstructorMap中,

if (constructor == null) {// Class not found in the cache, see if it's real, and try to add itclazz = 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);sConstructorMap.put(name, constructor);}

然后就是newInstance了,至此这个View便从xml中变成了java对象,我们继续返回到inflate函数中,看看这个View返回之后做了什么,

......// Temp is the root view that was found in the xmlView temp;if (TAG_1995.equals(name)) {temp = new BlinkLayout(mContext, attrs);} else {temp = createViewFromTag(root, name, attrs);}ViewGroup.LayoutParams params = null;if (root != null) {// Create layout params that match root, if suppliedparams = root.generateLayoutParams(attrs);if (!attachToRoot) {// Set the layout params for temp if we are not// attaching. (If we are, we use addView, below)temp.setLayoutParams(params);}}// Inflate all children under temprInflate(parser, temp, attrs, true);// We are supposed to attach all the views we found (int temp)// to root. Do that now.if (root != null && attachToRoot) {root.addView(temp, params);}// Decide whether to return the root that was passed in or the// top view found in xml.if (root == null || !attachToRoot) {result = temp;}......return result;

从createViewFromTag返回后,会调用个rInflate(),其中parent参数就是刚才创建出的View,应该能猜到里面做了什么,

void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs,boolean finishInflate) throws XmlPullParserException, IOException {final int depth = parser.getDepth();int type;while (((type = parser.next()) != XmlPullParser.END_TAG ||parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {if (type != XmlPullParser.START_TAG) {continue;}final String name = parser.getName();if (TAG_REQUEST_FOCUS.equals(name)) {parseRequestFocus(parser, parent);} else if (TAG_INCLUDE.equals(name)) {if (parser.getDepth() == 0) {throw new InflateException("<include /> cannot be the root element");}parseInclude(parser, parent, attrs);} else if (TAG_MERGE.equals(name)) {throw new InflateException("<merge /> must be the root element");} else if (TAG_1995.equals(name)) {final View view = new BlinkLayout(mContext, attrs);final ViewGroup viewGroup = (ViewGroup) parent;final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);rInflate(parser, view, attrs, true);viewGroup.addView(view, params); } else {final View view = createViewFromTag(parent, name, attrs);final ViewGroup viewGroup = (ViewGroup) parent;final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);rInflate(parser, view, attrs, true);viewGroup.addView(view, params);}}if (finishInflate) parent.onFinishInflate();}

没错,就是递归的使用createViewFromTag()创建子View,并通过ViewGroup.addView添加到parent view中。

之后,这个View树上的所有View都创建完毕。然后会根据inflate()的参数(root和attachToRoot)判断是否将新创建的View添加到root view中,

if (root != null && attachToRoot) {root.addView(temp, params);}

然后,inflate()就将View返回了。

以上内容是小编给大家介绍的android从xml加载到view对象过程解析,希望对大家有所帮助!

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表