首先我们需要查看view加载到屏幕的过程,只有知道他们是怎么加载的后我们才能进行针对性的优化,
搞清楚了加载流程,我们就掌握了优化的利器。我们先从源头进入,然后一步步的进入到OS内部看他们怎么进行处理的。
在我们创建界面Activity时,会在oncreat方法中把我们定义的layoutxml文件使用setContentView赋值给系统,让OS加载xml文件然后显示到屏幕中。
1,我们先看一下Activity.setContentView()的具体实现,
public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); }
从代码中我们能看到Activity.setcontentView中主要做两个工作,一个是把xml文件进行上传,另外一个是进行actionbar的初始化,从这里我们能知道若是设置为notitlebar属性的话,启动的速度会稍微会一点点,今天我们主要是查看布局优化暂时不去考试actionbar相关的问题,让我们继续去查看getWindow().setContentView()方法,其中getWindow方法得到的是一个phoneWindow,在android中左右的控件的显示其根本就是显示在phoneWindow中,这是一个根,首先让我们看一下phoneWindow的定义,从代码我们能看到他extendsWindow,window在我的理解中就是phone的屏幕窗口,由此我们可以知道phoneWindow这个是屏幕窗口的对外的接口。
public class PhoneWindow extends Window implements MenuBuilder.Callback
@Override public void setContentView(int layoutResID) { // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window // decor, when theme attributes and the like are crystalized. Do not check the feature // before this happens. if (mContentParent == null) { installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()); transitionTo(newScene); } else { mLayoutInflater.inflate(layoutResID, mContentParent); } mContentParent.requestApplyInsets(); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } mContentParentExplicitlySet = true; }
代码中红色的部分就是我们需要了解的详细信息,也是我们创建activity时做走的流程,从这部分我们能看到最后还是使用mLayoutInflater进行创建view,我们继续进入LayoutInflater.inflate()
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) + ")"); } final XmlResourceParser parser = res.getLayout(resource); try { return inflate(parser, root, attachToRoot); } finally { parser.close(); } }
从代码中我们能看到会根据resourceid得到一个XmlResourceParser的xml解析工具,然后把这个得到的工具传入到inflate中,现在让我们进入到inflate的具体实现中去,因为inflate()比较长我们截取其中加载view的部分。
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, inflaterContext, attrs, false); } else { // Temp is the root view that was found in the xml final View temp = createViewFromTag(root, name, inflaterContext, attrs); ViewGroup.LayoutParams params = null; if (root != null) { if (DEBUG) { System.out.println("Creating params from root: " + root); } // 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) temp.setLayoutParams(params); } } if (DEBUG) { System.out.println("-----> start inflating children"); } // Inflate all children under temp against its context. rInflateChildren(parser, temp, attrs, true); if (DEBUG) { System.out.println("-----> done inflating children"); } // 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; } } } catch (XmlPullParserException e) { final InflateException ie = new InflateException(e.getMessage(), e); ie.setStackTrace(EMPTY_STACK_TRACE); throw ie; } catch (Exception e) { final InflateException ie = new InflateException(parser.getPositionDescription() + ": " + e.getMessage(), e); ie.setStackTrace(EMPTY_STACK_TRACE); throw ie; } finally { // Don't retain static reference on context. mConstructorArgs[0] = lastContext; mConstructorArgs[1] = null; Trace.traceEnd(Trace.TRACE_TAG_VIEW); } return result;
从代码中我们可以看到根据Tag的不同会执行代码,让我们先看下若是TAG是TAG_MERGE的话会走到rInflate()中,让我们去看下rInflate()的具体实现,
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 { final View view = createViewFromTag(parent, name, context, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); rInflateChildren(parser, view, attrs, true); viewGroup.addView(view, params); } } if (pendingRequestFocus) { parent.restoreDefaultFocus(); } if (finishInflate) { parent.onFinishInflate(); } }
从代码中我们看到,在此方法中遍历所有的控件进行处理,根据tag的不同进行不同的处理或者返回,在这段代码中最重要的代码是:
final View view = createViewFromTag(parent, name, context, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); rInflateChildren(parser, view, attrs, true); viewGroup.addView(view, params);
创建view并加到viewGroup中,这个方法中主要处理的是merge控件相关的内容,有此我们可以推断在layout中若是有机会的话可以使用merge控件,并且尽量的只使用一层,这样在加载的时候可以直接创建view不会在做其他的处理。
然后让我们回过头来看非TAG_MERGE的情况下,是如何进行加载控件的,在代码中我们发现若是不是TAG_MERGE的情况下就调用函数createViewFromTag()方法创建一个view,并且若是root不为null的话就会加上
if (root != null && attachToRoot) { root.addView(temp, params); }
其实其他所有的view创建都是在这里进行的使用递归的方式调用createViewFromTag()方法,这个就是view创建的过程。