[关闭]
@linux1s1s 2017-01-22T08:23:03.000000Z 字数 8813 阅读 2513

Android View 分析(上)

AndroidView 2015-05


Set ContentView

做Android开发,在Activity中最熟悉的莫过于这么一个方法:

  1. @Override
  2. public void onActivityCreated(Bundle savedInstanceState)
  3. {
  4. setContentView(R.layout.activity_main);
  5. }

不妨追踪setContentView下去看看是神马情况。
Activity.java

  1. public void setContentView(int layoutResID) {
  2. getWindow().setContentView(layoutResID);
  3. initWindowDecorActionBar();
  4. }
  5. ...
  6. public Window getWindow() {
  7. return mWindow;
  8. }

mWindow 这货是什么?是谁创造了它,我们找一下成员变量mWindow
发现mWindow的赋值在attach(...)方法中

  1. final void attach(Context context, ActivityThread aThread,
  2. Instrumentation instr, IBinder token, int ident,
  3. Application application, Intent intent, ActivityInfo info,
  4. CharSequence title, Activity parent, String id,
  5. NonConfigurationInstances lastNonConfigurationInstances,
  6. Configuration config, IVoiceInteractor voiceInteractor) {
  7. attachBaseContext(context);
  8. mFragments.attachActivity(this, mContainer, null);
  9. mWindow = PolicyManager.makeNewWindow(this);
  10. ...
  11. }

注意L11行 PolicyManager又是神马玩意?进去看看

  1. public final class PolicyManager {
  2. private static final String POLICY_IMPL_CLASS_NAME =
  3. "com.android.internal.policy.impl.Policy";
  4. private static final IPolicy sPolicy;
  5. static {
  6. // Pull in the actual implementation of the policy at run-time
  7. try {
  8. Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
  9. sPolicy = (IPolicy)policyClass.newInstance();
  10. } catch (ClassNotFoundException ex) {
  11. throw new RuntimeException(
  12. POLICY_IMPL_CLASS_NAME + " could not be loaded", ex);
  13. 。。。。。。
  14. // The static methods to spawn new policy-specific objects
  15. public static Window makeNewWindow(Context context) {
  16. return sPolicy.makeNewWindow(context);
  17. }
  18. }

还好这货也就几行代码,通过反射生成一个单例,而最终的实现交给了sPolicy.makeNewWindow(context); 我们接着定位到IPolicy的实现类Policy

  1. public class Policy implements IPolicy {
  2. private static final String TAG = "PhonePolicy";
  3. public Window makeNewWindow(Context context) {
  4. return new PhoneWindow(context);
  5. }
  6. }

到此我们终于找到了mWindow引用指向了哪里?最终执行了PhoneWindow的实例。那么我们直接找PhoneWindowsetContentView方法即可

  1. @Override
  2. public void setContentView(int layoutResID) {
  3. // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
  4. // decor, when theme attributes and the like are crystalized. Do not check the feature
  5. // before this happens.
  6. if (mContentParent == null) {
  7. installDecor();
  8. } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
  9. mContentParent.removeAllViews();
  10. }
  11. if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
  12. final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
  13. getContext());
  14. transitionTo(newScene);
  15. } else {
  16. mLayoutInflater.inflate(layoutResID, mContentParent);
  17. }
  18. final Callback cb = getCallback();
  19. if (cb != null && !isDestroyed()) {
  20. cb.onContentChanged();
  21. }
  22. }

当第一次进入这个方法的时候mContentParent 为 null,所以首先会执行installDecor();

  1. private void installDecor() {
  2. if (mDecor == null) {
  3. mDecor = generateDecor();//创建mDecor,它是DecorView类型,继承于FrameLayout。
  4. mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
  5. mDecor.setIsRootNamespace(true);
  6. }
  7. if (mContentParent == null) {
  8. mContentParent = generateLayout(mDecor);
  9. //创建标题栏
  10. mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
  11. ......
  12. }
  13. }

这里忽略了大部分页面切换的过度动画,我们只关注主要流程就好。
先来看一下generateDecor()
这个过程比较复杂,我就不贴代码了,大概说一下流程

  1. 根据getWindowStyle()返回的数组来设定一些窗口属性值feature,如是否全屏,是否带标题栏。
  2. 根据上面设定的features值,决定加载何种窗口布局文件。
  3. 把特定的view添加到decorView里。
  4. contentParent由findViewById返回,实际上就是mDecorView一部分

接着看一下 mLayoutInflater.inflate(layoutResID, mContentParent), 这个方法将我们的资源文件通过LayoutInflater对象转换为View树,并且添加至mContentParent视图中。

Inflate

LayoutInflaterinflate 成员方法

  1. public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
  2. final Resources res = getContext().getResources();
  3. if (DEBUG) {
  4. Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
  5. + Integer.toHexString(resource) + ")");
  6. }
  7. final XmlResourceParser parser = res.getLayout(resource);
  8. try {
  9. return inflate(parser, root, attachToRoot);
  10. } finally {
  11. parser.close();
  12. }
  13. }

继续下去:

  1. public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
  2. synchronized (mConstructorArgs) {
  3. Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
  4. final AttributeSet attrs = Xml.asAttributeSet(parser);
  5. Context lastContext = (Context)mConstructorArgs[0];
  6. mConstructorArgs[0] = mContext;
  7. View result = root;
  8. try {
  9. // Look for the root node.
  10. int type;
  11. while ((type = parser.next()) != XmlPullParser.START_TAG &&
  12. type != XmlPullParser.END_DOCUMENT) {
  13. // Empty
  14. }
  15. if (type != XmlPullParser.START_TAG) {
  16. throw new InflateException(parser.getPositionDescription()
  17. + ": No start tag found!");
  18. }
  19. final String name = parser.getName();
  20. if (DEBUG) {
  21. System.out.println("**************************");
  22. System.out.println("Creating root view: "
  23. + name);
  24. System.out.println("**************************");
  25. }
  26. if (TAG_MERGE.equals(name)) {
  27. if (root == null || !attachToRoot) {
  28. throw new InflateException("<merge /> can be used only with a valid "
  29. + "ViewGroup root and attachToRoot=true");
  30. }
  31. rInflate(parser, root, attrs, false, false);
  32. } else {
  33. // Temp is the root view that was found in the xml
  34. final View temp = createViewFromTag(root, name, attrs, false);
  35. ViewGroup.LayoutParams params = null;
  36. if (root != null) {
  37. if (DEBUG) {
  38. System.out.println("Creating params from root: " +
  39. root);
  40. }
  41. // Create layout params that match root, if supplied
  42. params = root.generateLayoutParams(attrs);
  43. if (!attachToRoot) {
  44. // Set the layout params for temp if we are not
  45. // attaching. (If we are, we use addView, below)
  46. temp.setLayoutParams(params);
  47. }
  48. }
  49. if (DEBUG) {
  50. System.out.println("-----> start inflating children");
  51. }
  52. // Inflate all children under temp
  53. rInflate(parser, temp, attrs, true, true);
  54. if (DEBUG) {
  55. System.out.println("-----> done inflating children");
  56. }
  57. // We are supposed to attach all the views we found (int temp)
  58. // to root. Do that now.
  59. if (root != null && attachToRoot) {
  60. root.addView(temp, params);
  61. }
  62. // Decide whether to return the root that was passed in or the
  63. // top view found in xml.
  64. if (root == null || !attachToRoot) {
  65. result = temp;
  66. }
  67. }
  68. } catch (XmlPullParserException e) {
  69. InflateException ex = new InflateException(e.getMessage());
  70. ex.initCause(e);
  71. throw ex;
  72. } catch (IOException e) {
  73. InflateException ex = new InflateException(
  74. parser.getPositionDescription()
  75. + ": " + e.getMessage());
  76. ex.initCause(e);
  77. throw ex;
  78. } finally {
  79. // Don't retain static reference on context.
  80. mConstructorArgs[0] = lastContext;
  81. mConstructorArgs[1] = null;
  82. }
  83. Trace.traceEnd(Trace.TRACE_TAG_VIEW);
  84. return result;
  85. }
  86. }

这个方法比较长,稍微忍受一下,梳理一下上面inflate成员方法到底做啥牛逼的事情了。LayoutInflater其实就是使用Android提供的pull解析方式来解析布局文件的,我们重点注意下 L41 调用了createViewFromTag()这个方法,并把节点名和参数传了进去。看到这个方法名,我们就应该能猜到,它是用于根据节点名来创建View对象的。确实如此,在createViewFromTag()方法的内部又会去调用createView()方法,然后使用反射的方式创建出View的实例并返回。

当然,这里只是创建出了一个根布局的实例而已,接下来会在第31行调用rInflate()方法来循环遍历这个根布局下的子元素,代码如下所示:

  1. void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs,
  2. boolean finishInflate, boolean inheritContext) throws XmlPullParserException,
  3. IOException {
  4. final int depth = parser.getDepth();
  5. int type;
  6. while (((type = parser.next()) != XmlPullParser.END_TAG ||
  7. parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
  8. if (type != XmlPullParser.START_TAG) {
  9. continue;
  10. }
  11. final String name = parser.getName();
  12. if (TAG_REQUEST_FOCUS.equals(name)) {
  13. parseRequestFocus(parser, parent);
  14. } else if (TAG_TAG.equals(name)) {
  15. parseViewTag(parser, parent, attrs);
  16. } else if (TAG_INCLUDE.equals(name)) {
  17. if (parser.getDepth() == 0) {
  18. throw new InflateException("<include /> cannot be the root element");
  19. }
  20. parseInclude(parser, parent, attrs, inheritContext);
  21. } else if (TAG_MERGE.equals(name)) {
  22. throw new InflateException("<merge /> must be the root element");
  23. } else {
  24. final View view = createViewFromTag(parent, name, attrs, inheritContext);
  25. final ViewGroup viewGroup = (ViewGroup) parent;
  26. final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
  27. rInflate(parser, view, attrs, true, true);
  28. viewGroup.addView(view, params);
  29. }
  30. }
  31. if (finishInflate) parent.onFinishInflate();
  32. }

可以看到,在第29行同样是createViewFromTag()方法来创建View的实例,然后还会在第32行递归调用rInflate()方法来查找这个View下的子元素,每次递归完成后则将这个View添加到父布局当中。

这样的话,把整个布局文件都解析完成后就形成了一个完整的DOM结构,最终会把最顶层的根布局返回,至此inflate()过程全部结束。

这里说的View、DecorView等都是UI单元,这些UI单元工作都在onDraw函数中完成。如果把onDraw想象成画图过程,那我们需要知道画布是什么?查阅资料后,得出答案就是Surface
如何确定就是 Surface 呢?我们还得看一下Activity组件启动过程

Activity组件启动过程

我们先来看张比较直观的序列图

下面这个序列图已经过时了,为了方便,没有及时更改这个序列图,这里特别说明一下:
LocalWindowManager ---> WindowManagerGlobal
ViewRoot ---> ViewRootImpl
然后把WindowManagerImplWindowManagerGlobal倒换一下
此处输入图片的描述

上面分析从setContentView开始,也就是上图的第4步开始,然后执行第5步 PhoneWindow.installDecor(),然后我们直接跳过第7--10步,进入第11步:WindowManagerImpl.addView()

  1. @Override
  2. public void addView(View view, ViewGroup.LayoutParams params) {
  3. mGlobal.addView(view, params, mDisplay, mParentWindow);
  4. }

然后进入第12步,调用:WindowManagerGlobal.addView()

  1. public void addView(View view, ViewGroup.LayoutParams params,
  2. Display display, Window parentWindow) {
  3. ...
  4. // do this last because it fires off messages to start doing things
  5. try {
  6. root.setView(view, wparams, panelParentView);
  7. } catch (RuntimeException e) {
  8. // BadTokenException or InvalidDisplayException, clean up.
  9. synchronized (mLock) {
  10. final int index = findViewLocked(view, false);
  11. if (index >= 0) {
  12. removeViewLocked(index, true);
  13. }
  14. }
  15. throw e;
  16. }
  17. }

注意L6 root.setView(view, wparams, panelParentView);直接把我们带入了第13步,而ViewRootImpl中正好有个成员变量:

  1. 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,特此说明。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注