首页 > 系统 > Android > 正文

关于Android中View的绘制流程的学习(一)

2019-11-08 00:17:19
字体:
来源:转载
供稿:网友

这是我自己参考了网上的一些文章做得小总结,也许有错误,也有解释不对的地方,请指正,一起进步吧!我尽量做到不打错别字,用词正确,不造成阅读障碍。

源码是有变化的,但大体的结构差不多,依旧有很大的参考价值。

一.从ContentView开始

我觉得应该从一个Activity的onCreate方法中的setContentView的方法讲解可能会让人更好理解。

点击setContentView的源码:

public void setContentView(int layoutResID) {        getWindow().setContentView(layoutResID);        initWindowDecorActionBar();    }

其实它有三个方法,还有两个在这:

publicvoidsetContentView(View view) {

getWindow().setContentView(view);

initWindowDecorActionBar();

}

publicvoidsetContentView(View view, ViewGroup.LayoutParams params) {

getWindow().setContentView(view, params);

initWindowDecorActionBar();

}

可以看见他们都先调运了getWindow()的setContentView方法,然后调运Activity的initWindowDecorActionBar方法,

关于initWindowDecorActionBar方法并不是重点,所以暂时跳过不解释。

看getWindow().setContentView(view, params);其中getWindow()返回一个Window:

public Window getWindow() {     return mWindow;    }

而Window是一个抽象类,来张图;

这里写图片描述

Activity中有一个成员为Window,其实例化对象为PhoneWindow,PhoneWindow为抽象Window类的实现类。Window类上有一段注释:

The only existing implementation of this abstract class is android.policy.PhoneWindow, which you should instantiate when needing a Window.  Eventually that class will be refactored and a factory method added for creating Window instances without knowing about a particular implementation.

注释已经很好的说明了PhoneWindow与Window的关系

这里先简要说明下这些类的职责:

Window是一个抽象类,提供了绘制窗口的一组通用API。

PhoneWindow是Window的具体继承实现类。而且该类内部包含了一个DecorView对象,该DectorView对象是所有应用窗口(Activity界面)的根View。

DecorView是PhoneWindow的内部类,是FrameLayout的子类,是对FrameLayout进行功能的修饰(所以叫DecorXXX),是所有应用窗口的根View 。

依据面向对象从抽象到具体我们可以类比上面关系就像如下:

Window是一块电子屏,PhoneWindow是一块手机电子屏,DecorView就是电子屏要显示的内容,Activity就是手机电子屏安装位置。

注意PhoneWindow是internal的,也就是不太容易找到,我是在SDK中sources/android-21/com/android/internal/policy/impl下找到的。

所以我们就要去看PhoneWindow的setContentView方法了。

巧了!它也有三个setContentView方法,先看第一个:

    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);        }        final Callback cb = getCallback();        if (cb != null && !isDestroyed()) {            cb.onContentChanged();        }    }

可以看见,第五行首先判断mContentParent是否为null,也就是第一次调运);如果是第一次调用,则调用installDecor()方法,否则判断是否设置FEATURE_CONTENT_TRANSITIONS Window属性(默认false),如果没有就移除该mContentParent内所有的所有子View;接着16行mLayoutInflater.inflate(layoutResID, mContentParent);将我们的资源文件通过LayoutInflater对象转换为View树,并且添加至mContentParent视图中(其中mLayoutInflater是在PhoneWindow的构造函数中得到实例对象的LayoutInflater.from(context);)。

其他的方法先过滤掉,其实里面有买很多知

识点,比如installDecor就有很多代码,包括初始化一堆属性,按照主题style设置界面等等。我感觉重点是mLayoutInflater.inflate方法。

而第二个方法:

public void setContentView(View view) {        setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));    }

第三个方法:

public void setContentView(View view, ViewGroup.LayoutParams params) {        // 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)) {            view.setLayoutParams(params);            final Scene newScene = new Scene(mContentParent, view);            transitionTo(newScene);        } else {            mContentParent.addView(view, params);        }        final Callback cb = getCallback();        if (cb != null && !isDestroyed()) {            cb.onContentChanged();        }    }

第二个方法就是调用第三个方法啊!也就是重点是第三个方法,而且第一个方法和第三个方法很像,还是先看第一个方法吧,毕竟有我们熟悉的内容。

重点是mLayoutInflater.inflate(...)看代码:

 public View inflate(int resource, ViewGroup root) {        return inflate(resource, root, root != null);    }

呵呵,调了另一个方法,再看看这个方法:

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();        }    }

我传递的int类型的资源Id在这里res.getLayout(resourse);成了parser,而XmlResourseParser是个接口啊,这是要解析我的xml文件的节奏啊!最后又进入了一个方法inflate(parser,root,attachToRoot);去看看:

public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {      synchronized (mConstructorArgs) {          final AttributeSet attrs = Xml.asAttributeSet(parser);          mConstructorArgs[0] = mContext;          View result = root;          try {              int type;              while ((type = parser.next()) != XmlPullParser.START_TAG &&                      type != XmlPullParser.END_DOCUMENT) {              }              if (type != XmlPullParser.START_TAG) {                  throw new InflateException(parser.getPositionDescription()                          + ": No start tag found!");              }              final String name = parser.getName();              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);              } else {                  View temp = createViewFromTag(name, attrs);                  ViewGroup.LayoutParams params = null;                  if (root != null) {                      params = root.generateLayoutParams(attrs);                      if (!attachToRoot) {                          temp.setLayoutParams(params);                      }                  }                  rInflate(parser, temp, attrs);                  if (root != null && attachToRoot) {                      root.addView(temp, params);                  }                  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;          }          return result;      }  }  

从这里我们就可以清楚地看出,LayoutInflater其实就是使用Android提供的pull解析方式来解析布局文件的。这里我们注意看下第23行,调用了createViewFromTag()这个方法,并把节点名和参数传了进去。看到这个方法名,我们就应该能猜到,它是用于根据节点名来创建View对象的。确实如此,在createViewFromTag()方法的内部又会去调用createView()方法,然后使用反射的方式创建出View的实例并返回。

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

private void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs)          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 {              final View view = createViewFromTag(name, attrs);              final ViewGroup viewGroup = (ViewGroup) parent;              final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);              rInflate(parser, view, attrs);              viewGroup.addView(view, params);          }      }      parent.onFinishInflate();  } 

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

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

参考资料:http://blog.csdn.net/guolin_blog/article/details/12921889

                   http://blog.csdn.net/yanbober/article/details/45970721
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表