@linux1s1s
2017-01-22T08:23:03.000000Z
字数 8813
阅读 3496
AndroidView 2015-05
做Android开发,在Activity中最熟悉的莫过于这么一个方法:
@Overridepublic void onActivityCreated(Bundle savedInstanceState){setContentView(R.layout.activity_main);}
不妨追踪setContentView下去看看是神马情况。
Activity.java
public void setContentView(int layoutResID) {getWindow().setContentView(layoutResID);initWindowDecorActionBar();}...public Window getWindow() {return mWindow;}
mWindow 这货是什么?是谁创造了它,我们找一下成员变量mWindow:
发现mWindow的赋值在attach(...)方法中
final void attach(Context context, ActivityThread aThread,Instrumentation instr, IBinder token, int ident,Application application, Intent intent, ActivityInfo info,CharSequence title, Activity parent, String id,NonConfigurationInstances lastNonConfigurationInstances,Configuration config, IVoiceInteractor voiceInteractor) {attachBaseContext(context);mFragments.attachActivity(this, mContainer, null);mWindow = PolicyManager.makeNewWindow(this);...}
注意L11行 PolicyManager又是神马玩意?进去看看
public final class PolicyManager {private static final String POLICY_IMPL_CLASS_NAME ="com.android.internal.policy.impl.Policy";private static final IPolicy sPolicy;static {// Pull in the actual implementation of the policy at run-timetry {Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);sPolicy = (IPolicy)policyClass.newInstance();} catch (ClassNotFoundException ex) {throw new RuntimeException(POLICY_IMPL_CLASS_NAME + " could not be loaded", ex);。。。。。。// The static methods to spawn new policy-specific objectspublic static Window makeNewWindow(Context context) {return sPolicy.makeNewWindow(context);}}
还好这货也就几行代码,通过反射生成一个单例,而最终的实现交给了sPolicy.makeNewWindow(context); 我们接着定位到IPolicy的实现类Policy
public class Policy implements IPolicy {private static final String TAG = "PhonePolicy";public Window makeNewWindow(Context context) {return new PhoneWindow(context);}}
到此我们终于找到了mWindow引用指向了哪里?最终执行了PhoneWindow的实例。那么我们直接找PhoneWindow的setContentView方法即可
@Overridepublic 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);}final Callback cb = getCallback();if (cb != null && !isDestroyed()) {cb.onContentChanged();}}
当第一次进入这个方法的时候mContentParent 为 null,所以首先会执行installDecor();
private void installDecor() {if (mDecor == null) {mDecor = generateDecor();//创建mDecor,它是DecorView类型,继承于FrameLayout。mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);mDecor.setIsRootNamespace(true);}if (mContentParent == null) {mContentParent = generateLayout(mDecor);//创建标题栏mTitleView = (TextView)findViewById(com.android.internal.R.id.title);......}}
这里忽略了大部分页面切换的过度动画,我们只关注主要流程就好。
先来看一下generateDecor()
这个过程比较复杂,我就不贴代码了,大概说一下流程
接着看一下 mLayoutInflater.inflate(layoutResID, mContentParent), 这个方法将我们的资源文件通过LayoutInflater对象转换为View树,并且添加至mContentParent视图中。
LayoutInflater类 inflate 成员方法
public View inflate(int resource, 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();}}
继续下去:
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {synchronized (mConstructorArgs) {Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");final AttributeSet attrs = Xml.asAttributeSet(parser);Context lastContext = (Context)mConstructorArgs[0];mConstructorArgs[0] = mContext;View result = root;try {// Look for the root node.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!");}final String name = parser.getName();if (DEBUG) {System.out.println("**************************");System.out.println("Creating root view: "+ name);System.out.println("**************************");}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, false);} else {// Temp is the root view that was found in the xmlfinal View temp = createViewFromTag(root, name, attrs, false);ViewGroup.LayoutParams params = null;if (root != null) {if (DEBUG) {System.out.println("Creating params from root: " +root);}// 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);}}if (DEBUG) {System.out.println("-----> start inflating children");}// Inflate all children under temprInflate(parser, temp, attrs, true, 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) {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;}Trace.traceEnd(Trace.TRACE_TAG_VIEW);return result;}}
这个方法比较长,稍微忍受一下,梳理一下上面inflate成员方法到底做啥牛逼的事情了。LayoutInflater其实就是使用Android提供的pull解析方式来解析布局文件的,我们重点注意下 L41 调用了createViewFromTag()这个方法,并把节点名和参数传了进去。看到这个方法名,我们就应该能猜到,它是用于根据节点名来创建View对象的。确实如此,在createViewFromTag()方法的内部又会去调用createView()方法,然后使用反射的方式创建出View的实例并返回。
当然,这里只是创建出了一个根布局的实例而已,接下来会在第31行调用rInflate()方法来循环遍历这个根布局下的子元素,代码如下所示:
void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs,boolean finishInflate, boolean inheritContext) 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_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, parent, attrs, inheritContext);} else if (TAG_MERGE.equals(name)) {throw new InflateException("<merge /> must be the root element");} else {final View view = createViewFromTag(parent, name, attrs, inheritContext);final ViewGroup viewGroup = (ViewGroup) parent;final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);rInflate(parser, view, attrs, true, true);viewGroup.addView(view, params);}}if (finishInflate) parent.onFinishInflate();}
可以看到,在第29行同样是createViewFromTag()方法来创建View的实例,然后还会在第32行递归调用rInflate()方法来查找这个View下的子元素,每次递归完成后则将这个View添加到父布局当中。
这样的话,把整个布局文件都解析完成后就形成了一个完整的DOM结构,最终会把最顶层的根布局返回,至此inflate()过程全部结束。
这里说的View、DecorView等都是UI单元,这些UI单元工作都在onDraw函数中完成。如果把onDraw想象成画图过程,那我们需要知道画布是什么?查阅资料后,得出答案就是
Surface。
如何确定就是Surface呢?我们还得看一下Activity组件启动过程
我们先来看张比较直观的序列图
下面这个序列图已经过时了,为了方便,没有及时更改这个序列图,这里特别说明一下:
LocalWindowManager ---> WindowManagerGlobal
ViewRoot ---> ViewRootImpl
然后把WindowManagerImpl和WindowManagerGlobal倒换一下

上面分析从setContentView开始,也就是上图的第4步开始,然后执行第5步 PhoneWindow.installDecor(),然后我们直接跳过第7--10步,进入第11步:WindowManagerImpl.addView()
@Overridepublic void addView(View view, ViewGroup.LayoutParams params) {mGlobal.addView(view, params, mDisplay, mParentWindow);}
然后进入第12步,调用:WindowManagerGlobal.addView()
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {...// do this last because it fires off messages to start doing thingstry {root.setView(view, wparams, panelParentView);} catch (RuntimeException e) {// BadTokenException or InvalidDisplayException, clean up.synchronized (mLock) {final int index = findViewLocked(view, false);if (index >= 0) {removeViewLocked(index, true);}}throw e;}}
注意L6 root.setView(view, wparams, panelParentView);直接把我们带入了第13步,而ViewRootImpl中正好有个成员变量:
private final Surface mSurface = new Surface();
源代码 L248 到这里基本验证了上面的结论,这一篇博文中我们基本认识了一下四个类: PhoneWindow,DecorView,ViewRoot,Surface,对于这四个类下面给出比较直观的UML图:

下面这张图同样也过时了,这里更新如下:
ViewRoot ---> ViewRootImpl
之前ViewRoot继承自Handler,本质上可以说是个Handler感觉很奇怪。
所以经过改良以后,ViewRootImpl将Handler作为内部类来实现,这样剥离了View和Handler,思路上更清晰一点。

这里仅仅是开启了认识View的大门,Android View 分析(中) 将带领我们更进一步的认识View。
本文参考互联网博文:http://blog.csdn.net/luoshengyang/article/details/8245546
http://blog.csdn.net/guolin_blog/article/details/12921889,特此说明。