我们已经知道View的绘制过程包括计算视图大小(measure)、为视图分配位置(layout)以及把视图绘制到屏幕上(draw)三个步骤,下面详细了解如何完成这三个步骤。
我们假设WmS已经将窗口创建完毕,现在我们要在一个Activity的onCreate()方法中调用setContentView(R.layout.chat_mesg);来绘制一个Activity的界面。下面跟着源码一步步解析其过程。
首先,我们通常使用的setContentView()方法是Activity类中定义的方法,Activity的setContentView源码:
* Set the activity content from a layout resource. The resource will be
* inflated, adding all top-level views to the activity.
*
* 为一个Activity设置内容(视图),该内容是从我们的layout目录下的xml文件提供。
* 这个资源会被填充到当前窗口,并将最上面的层级视图添加到此Activity。
*
* @param layoutResID Resource ID to be inflated.
*
* @see #setContentView(android.view.View)
* @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
*/
public void setContentView(int layoutResID) {
//这里获取到一个Window对象,实际上Window类是一个抽象类,它只有一个PhoneWindow类,因此这里实际上是调用PhoneWindow类的setCOntentView(int sourcve)方法
getWindow().setContentView(layoutResID);
//创建一个新的ActionBar
initActionBar();
}
可以看到,Activity的setContentView方法中首先获取一个Window对象,这个Window类是一个抽象类,它里面的setContentView()方法并没有具体实现,而是他唯一的一个实现类PhoneWindow来实现的,因此,这里getWindow()实际上是获取PhoneWindow的对象。那么这个PhoneWindow对象是怎么来的呢?我们看下getWindow()方法里面是什么:
Retrieve the current android.view.Window for the activity. This can be used to directly access parts of the Window API that are not available through Activity/Screen.
Returns:
Window The current window, or null if the activity is not visual.807
public Window getWindow() {
return mWindow;
}
如上所示,getWindow()方法(参数太多省略。。)仅仅是返回一个mWindow对象,此对象是Activity的一个私有的成员变量,那么这个对象是什么时候被赋值的呢?既然mWindow对象是在Activity中,那么我们在Activity中搜索下就知道了,找啊找啊找,终于发现了,在一个叫attach()的方法中发现了这一行代码:
mWindow = PolicyManager.makeNewWindow(this);
由此可见mWindow是在attach()方法中被创建,实际上在attach()法中还创建了N多东西,比如mUiThread线程,ActivityThread线程、Application实例等等,看下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) {
attachBaseContext(context);
mFragments.attachActivity(this);
//这里生产一个Window对象
mWindow = PolicyManager.makeNewWindow(this);
//注册回滚监听,
mWindow.setCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
//UI线程,直接就是获取当前线程作为UI线程
mUiThread = Thread.currentThread();
//主线程,实际上就是ActivityThread
mMainThread = aThread;
mInstrumentation = instr;
mToken = token;
mIdent = ident;
//获取Application实例
mApplication = application;
mIntent = intent;
mComponent = intent.getComponent();
mActivityInfo = info;
mTitle = title;
mParent = parent;
mEmbeddedID = id;
mLastNonConfigurationInstances = lastNonConfigurationInstances;
mWindow.setWindowManager(null, mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
}<span style="font-size:18px;">
</span>
可以看到,attach()方法做了很多有用的工作,那么attach()方法是在什么时候被执行呢?这个attach()方法是在ActivityThread的performLaunchActivity()中被调用(实际上在ActivityThread中有一整套对应于Activity生命周期的performXXXX()方法,这些方法又对应于Instruction类的一整套Activity生命周期的callActivityOnCreate()等等方法,之后我们会详细介绍。),也就是启动Activity之前会调用这个attach()方法,attach()调用之后才会调用onCreate()等等之类的方法。好了,上面说了半天也只是说mWindow是由PolicyManager.makeNewWindow(this);创建的,那么我们去看看这个类吧:
// The static methods to spawn new policy-specific objects
public static Window makeNewWindow(Context context) {
return sPolicy.makeNewWindow(context);
}
如上所示,是PolicyManager类的makeNewWindow()方法,里面是通过sPolicy调用的makeNewWindow()来生产一个Window对象,sPolicy是一个IPolicy接口对象,sPolicy实际上是IPolicy的一个实现类Policy的对象,我们去看看Policy的makeNewWindow()方法:
public Window [More ...] makeNewWindow(Context context) {
return new PhoneWindow(context);
}
看出来了吗,在Policy中的makeNewWindow()方法中创建了一个PhoneWindow对象。OK,现在我们知道了Activity中的mWindow对象是一个PhoneWindow对象,每一个Activity都有一个mWindow对象,也就是每一个Activity都对应于一个Window窗口对象。其生产过程是:ActivityThread--->-performLaunchActivity()---->Activity----attach()----->PolicyManager---->Policy---makeNewWindow()----new PhoneWindo()。
回过头来看setContentView(int resId),这个方法吧,上面说到,这个方法又调用到了PhoneWindow的setContentView(int resId),那么我们来看看PhoneWindow吧:
@Override
public void setContentView(int layoutResID) {
//如果mContentParent是空,说明是第一次创建布局视图,通俗点将就是一个Activity被创建即onCreate()执行的时候,
if (mContentParent == null) {
//初始化mDecorView
installDecor();
} else {
//如果不是就将mContentParent的子视图全部移除,因为mContentParent只有一个
mContentParent.removeAllViews();
}
//解析我们的xml布局文件
mLayoutInflater.inflate(layoutResID, mContentParent);
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
在上面的PhoneWindow的setContentView()方法中,首先会判断mContentParent对象是否为空,如果是空就调用installDecor()初始化一个DecorView、mContentParent等操作,否则就将mContentParent的子视图全部移除,将我们的布局文件添加上去,这里注意,mContentParent是一个ViewGroup对象,它是我们布局文件的根节点,通常我们编写的布局文件的实际根节点就是它了。那么它是什么时候被创建的呢?我们看上面的代码,当mContentParent为null时执行的installDecor()方法吧:
private void installDecor() {
//判断mDecor是否为空,如果为空就创建一个
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
//判断mContentParent是否为空,如果为空就创建一个
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeOptionalFitsSystemWindows();
mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
if (mTitleView != null) {
mTitleView.setLayoutDirection(mDecor.getLayoutDirection());
if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
View titleContainer = findViewById(com.android.internal.R.id.title_container);
if (titleContainer != null) {
titleContainer.setVisibility(View.GONE);
} else {
mTitleView.setVisibility(View.GONE);
}
if (mContentParent instanceof FrameLayout) {
((FrameLayout)mContentParent).setForeground(null);
}
} else {
mTitleView.setText(mTitle);
}
} else {
mActionBar = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
if (mActionBar != null) {
mActionBar.setWindowCallback(getCallback());
if (mActionBar.getTitle() == null) {
mActionBar.setWindowTitle(mTitle);
}
final int localFeatures = getLocalFeatures();
if ((localFeatures & (1 << FEATURE_PROGRESS)) != 0) {
mActionBar.initProgress();
}
if ((localFeatures & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
mActionBar.initIndeterminateProgress();
}
boolean splitActionBar = false;
final boolean splitWhenNarrow =
(mUiOptions & ActivityInfo.UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW) != 0;
if (splitWhenNarrow) {
splitActionBar = getContext().getResources().getBoolean(
com.android.internal.R.bool.split_action_bar_is_narrow);
} else {
splitActionBar = getWindowStyle().getBoolean(
com.android.internal.R.styleable.Window_windowSplitActionBar, false);
}
final ActionBarContainer splitView = (ActionBarContainer) findViewById(
com.android.internal.R.id.split_action_bar);
if (splitView != null) {
mActionBar.setSplitView(splitView);
mActionBar.setSplitActionBar(splitActionBar);
mActionBar.setSplitWhenNarrow(splitWhenNarrow);
final ActionBarContextView cab = (ActionBarContextView) findViewById(
com.android.internal.R.id.action_context_bar);
cab.setSplitView(splitView);
cab.setSplitActionBar(splitActionBar);
cab.setSplitWhenNarrow(splitWhenNarrow);
} else if (splitActionBar) {
Log.e(TAG, "Requested split action bar with " +
"incompatible window decor! Ignoring request.");
}
// Post the panel invalidate for later; avoid application onCreateOptionsMenu
// being called in the middle of onCreate or similar.
mDecor.post(new Runnable() {
public void run() {
// Invalidate if the panel menu hasn't been created before this.
PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
if (!isDestroyed() && (st == null || st.menu == null)) {
invalidatePanelMenu(FEATURE_ACTION_BAR);
}
}
});
}
}
}
}
installDecor()方法的代码比较多,比较重要的操作就是创建了一个mDecor对象和mContentParent对象,mDecor是一个DecorView类,它是PhoneWindow的内部类,mDecor就是一个窗口的根视图,它继承自FrameLayout,generateDecor()方法很简单,就是直接new了一个DecorView对象。在创建mContentParent时使用了generateLayout(DecorView decor)方法,注意,在使用该方法时将mDecor传递过去了,generateLayout()方法代码略多,我们截取最关键的代码如下:
<span style="font-size:14px;"> </span>
....
//前面的代码是经过一系列判断根据根据系统默认的布局产生一个layoutResouce的变量,他就是系统的一个默认的布局
mDecor.startChanging();
//根据系统默认的布局inflate一个视图
View in = mLayoutInflater.inflate(layoutResource, null);
//使用mDecor将视图添加进来,注意,此时mDecor表示的是一个窗口的根节点,同时这个填充的视图是充满屏幕的
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
//根据id查找一个叫做ID_ANDROID_CONTENT的ViewGroup,此常亮是在Window中定义的
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
//下面会将 contentParent返回,它就是mContentParent了,我们所有的xml布局文件将作为它的直接子视图。
好了,通过上面的两个步骤,我们已经创建了mDecor和mContentParent视图了,创建顺序是:先创建mDecor(FrameLayout)作为窗口的根节点,然后创建一个mContentParent(ViewGroup)作为我们所有xml布局文件的父节点,再然后将mContentParent添加到mDecor中去。再回过头看setContentView()方法,此时mDecor和mContentParent已经创建完毕,并且开始执行
mLayoutInflater.inflate(layoutResID, mContentParent);
方法了,这个方法我们都很熟悉,他就会将我们的xml布局layoutResID解析成一个相应的View并添加到mContentParent上去,此时一颗View Tree创建完毕。
注意mDecor和mContentParent对象的区别,前者是应用窗口的根视图,后者是我们布局文件的根视图,也是mDecor的子视图。下面再看LayoutInflate类的inflate()方法:
* Inflate a new view hierarchy from the specified xml resource. Throws
* {@link InflateException} if there is an error.
*/
public View inflate(int resource, ViewGroup root) {
return inflate(resource, root, root != null);
}
该方法调用了本类的重载方法如下:
/* Inflate a new view hierarchy from the specified xml resource. Throws
* {@link InflateException} if there is an error.
*
*/
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();
}
}
注意上面代码中是先从Context中获取一个Resource对象,然后获取Resource类中的XmlResourceParser对象,这里是用于解析xml布局文件(layout xml)所以是调用getLayout()获取一个XmlResourceParser对象,我们知道Android项目中有很多种不同类型的xml,比如color xml、value xml、anim xml等等,都是不同的XmlResourceParser对象完成的,在Resources中有对象的getAnimation()、getLayout()和getXml()来获取不同的xml‘解析器。进一步查看Resource类的getLayout()方法:
public XmlResourceParser getLayout(int id) throws NotFoundException {
//该方法返回,注意这里传递了一个"layout"参数,表示是布局xml文件
return loadXmlResourceParser(id, "layout");
}
进一步查看loadXmlResourceParesr()方法:
/**
*
* @param id 资源id,这个id就是我们应用程序中R.layout.xxx的xml文件id
* @param type 表示需要解析的xml文件类型,比如type的值为anim、xml等,上面方法传入的一个"layout"参数
*
* @return
* @throws NotFoundException
*/
/*package*/ XmlResourceParser loadXmlResourceParser(int id, String type)
throws NotFoundException {
//安全加锁
synchronized (mTmpValue) {
TypedValue value = mTmpValue;
getValue(id, value, true);
if (value.type == TypedValue.TYPE_STRING) {
return loadXmlResourceParser(value.string.toString(), id,
value.assetCookie, type);
}
throw new NotFoundException(
"Resource ID #0x" + Integer.toHexString(id) + " type #0x"
+ Integer.toHexString(value.type) + " is not valid");
}
}
上述方法中加锁的对象是一个动态类型数据值的容器,主要用于Resource类所持有的资源值,其源码解释是:
/**
* Container for a dynamically typed data value. Primarily used with
* {@link android.content.res.Resources} for holding resource values.
*/
public class TypedValue {
OK,上述跟踪到此为止,就不再往下了,我们只需要知道,Android在解析不同的xml资源文件时是使用了不同的解析器的。回过头来看LayoutInflate中的inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)方法,在上面一步中已经根据我们的资源ID生成一个xml解析器了,现在要做的就是解析这个xml文件,也就是下面要做的,这个方法开始真正解析布局文件并且转化为view后添加到其父视图中去,源码如下:
/**
* Inflate a new view hierarchy from the specified XML node. Throws
* {@link InflateException} if there is an error.
*/
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
//mConstructorArgs,构造器参数数组,大小为2
//final Object[] mConstructorArgs = new Object[2];
synchronized (mConstructorArgs) {
//好吧,要想弄懂xml怎么转换成View的,AttributeSet这个接口还需要了解
//这个接口是所有xml布局文件所有tag的属性的集合,谷歌工程师并不建议我们直接使用该接口。
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
//从开始到结束啥也不干
}
//解析到xml文件开头部分,如果没有就会抛出异常
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
//获取标记xml文件属性名字
final String name = parser.getName();
//调试状态下会打印
if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
//判断xml布局文件中是否使用了merge标签
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");
}
//如果有,而且满足条件,那么就调用另一个方法继续解析,此处我不在跟踪下去了~囧~,伤不起。
//不管怎样,使用都是使用pull解析,规则是一样的,无非就是会判断标签然后作不同的处理,
//如果大家对pull解析不熟悉可以网上搜下相关文章
rInflate(parser, root, attrs, false);
} else {
// Temp is the root view that was found in the xml
View temp;
//TAG_1995对应一个叫blink的标签,这个还真没有用过...低调飘过。。
if (TAG_1995.equals(name)) {
temp = new BlinkLayout(mContext, attrs);
} else {
//通过对前面xml文件以下列的判断,调用这个方法真正的视图View!
//根据该方法名字也知道是从xml的标签创建一个View,
//注意此处传入的几个参数,root是我们在应用程序中调用inflate(int source,ViewGroup root)方法时传递的父容器,通常为null
//But!这里由于是使用setContentView()方法,所以默认的父容器即为mContentParent对象。
//name参数就是获取到的xml标签名字
//attrs就是xml文件所有属性的集合了
temp = createViewFromTag(root, name, attrs);
}
//请务必注意LayoutParams类,这行代码已经表明,该类是ViewGroup类,
//我们都知道ViewGroup类是所有容器类的父类,比如LinearLayout、TableLayout、RealtiveLayout等、
//那么这些类都有一个叫做LayoutParams的内部类,而且这些LayoutParams类都是继承自ViewGroup类的内部类MarginLayoutParams
//而ViewGroup的MarginLayoutParams是继承于同样是内部类的LayoutParams,好吧,就这样终于继承完了。。。
//等等,好像跑题了。。。
//那么这些类是干嘛的呢?
//这个就是接下来要说的View绘制的三个过程(measure、layout、draw)。
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对象,调用的ViewGroup的generateLayoutParams(Attribute attrs)方法来创建的
//这些参数就是根据我们传入的attrs属性集合来创建的
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
//设置该视图布局参数
//这一步很重要,因为之后需要将该是视图显示到窗口,需要根据这些参数来measure
temp.setLayoutParams(params);
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
// Inflate all children under temp
//将所有的子视图对象放到temp视图下,此temp就是我们setCOntentView的xml布局文件视图
rInflate(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) {
//OK,终于完成了。。。
//到最后发现还是调用了ViewGroup的addView()方法对不对,是不是有种兜兜转转又回到原地的赶脚
//此方法我们也可以在程序中直接调用相关的View类来创建一个View然后调用addView方法添加
//所以,这里可以看出来,所有的xml布局文件实际上最后经过一系列的转换变为一个View对象,完了之后
//完了之后调用addView()完成添加,即将View控件添加到View容器中 - -、至此,完毕。
//之后再回过头来看下LayoutParams类的具体细节 --、
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;
}
return result;
}
}
在上面代码中,具体的解释已经在代码中说明了,这个方法做了很重要的两件事,第一就是生产一个真正意义上的View,第二就是生产默认的LayoutParams。首先生产View是调用了createViewFromTag(root, name, attrs)方法,而首次生产默认的LayoutParams参数是root.generateLayoutParams(attrs);完成的,我们先来看生产View的createFromTag()方法,这个方法就是根据TAG来生产View,源码如下:
/**
* default visibility so the BridgeInflater can override it.
* 默认该视图是可见的,所以BridgeInflater可以重写他
* @param parent 父容器视图
* @param name 名字
* @param attrs 属性
* @return
*/
View createViewFromTag(View parent, String name, AttributeSet attrs) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
if (DEBUG) System.out.println("******** Creating view: " + name);
try {
View view;
// //Factor2是本类中的一个接口,onCreateView也是一个接口提供的方法
//Factor2是继承Factor接口,一样的功能
// //FactoryMerger类是实现了Factory接口的类,mFactor2也是FactoryMerger的实例,
//因此,这里调用的onCreate()方法均是FactoryMerger类重写的。
//总之经过一系列的onCreateView的调用,一个View算是创建好了
//此类名叫LayoutInflate,顾名思义,是布局填充类,要想完全掌握,需要对此类以及相关的类仔细研究
//这里又调用了onCreateView()方法,此方法是最终生成一个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;
} catch (InflateException e) {
throw e;
} catch (ClassNotFoundException e) {
InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name);
ie.initCause(e);
throw ie;
} catch (Exception e) {
InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name);
ie.initCause(e);
throw ie;
}
}
上面说到,调用onCreateView()方法生成了一个View对象,源码如下:
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;
try {
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
clazz = 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 constructor
if (mFilter != null) {
// Have we seen this name before?
Boolean allowedState = mFilterMap.get(name);
if (allowedState == null) {
// New class -- remember whether it is allowed
clazz = 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;
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) {
// always use ourselves when inflating ViewStub later
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(this);
}
return view;
} catch (NoSuchMethodException e) {
InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class "
+ (prefix != null ? (prefix + name) : name));
ie.initCause(e);
throw ie;
} catch (ClassCastException e) {
// If loaded class is not a View subclass
InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Class is not a View "
+ (prefix != null ? (prefix + name) : name));
ie.initCause(e);
throw ie;
} catch (ClassNotFoundException e) {
// If loadClass fails, we should propagate the exception.
throw e;
} catch (Exception e) {
InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class "
+ (clazz == null ? "<unknown>" : clazz.getName()));
ie.initCause(e);
throw ie;
}
}
可以看到该方法利用name参数最终生成一个View对象,比如xml解析到name是TextView就反射一个TextVIew类对象,如果是EditText就反射以价格EditText对象。至此,以上我们分析了一个xml布局文件是怎么通过我们setCOntentVIew()来完成View的创建的,那么问题又来了,View虽然是被创建出来了,但是它还不具备一些属性,比如布局的位置,尺寸大小等。这正是我们现在要说的,回到inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)方法去看,当View创建完毕后,创建LayoutParams是由root.generateLayoutParams(attrs);来完成的,root对象是一个ViewGroup对象,那么我们就去ViewGroup中去看看generateLayoutParams()方法去看看:
/**
* Creates a new set of layout parameters. The values are extracted from
* the supplied attributes set and context. The XML attributes mapped
* to this set of layout parameters are:
*
* <ul>
* <li><code>layout_width</code>: the width, either an exact value,
* {@link #WRAP_CONTENT}, or {@link #FILL_PARENT} (replaced by
* {@link #MATCH_PARENT} in API Level 8)</li>
* <li><code>layout_height</code>: the height, either an exact value,
* {@link #WRAP_CONTENT}, or {@link #FILL_PARENT} (replaced by
* {@link #MATCH_PARENT} in API Level 8)</li>
* </ul>
*
* @param c the application environment
* @param attrs the set of attributes from which to extract the layout
* parameters' values
*/
public LayoutParams(Context c, AttributeSet attrs) {
TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
setBaseAttributes(a,
R.styleable.ViewGroup_Layout_layout_width,
R.styleable.ViewGroup_Layout_layout_height);
a.recycle();
}
/**
* Creates a new set of layout parameters with the specified width
* and height.
*
* @param width the width, either {@link #WRAP_CONTENT},
* {@link #FILL_PARENT} (replaced by {@link #MATCH_PARENT} in
* API Level 8), or a fixed size in pixels
* @param height the height, either {@link #WRAP_CONTENT},
* {@link #FILL_PARENT} (replaced by {@link #MATCH_PARENT} in
* API Level 8), or a fixed size in pixels
*/
public LayoutParams(int width, int height) {
this.width = width;
this.height = height;
}
由于generateLayoutParams()方法中是直接new一个LayoutParams对象,因此我直接把LayoutParams构造器和一个设置宽和高的方法贴出来,实际上LayoutParams类描述的是View告诉其父容器,它需要布局多大。也就是我们xml中用到的layout_width和layout_height属性。这里我们需要注意并且理解的是,LayoutParams只存在ViewGroup极其拓展子类中,并不存在View和一般控件中,因为LayoutParams子视图告诉父视图它想要多大,LayoutParams是由ViewGroup在绘制子视图时调用的。容器本身是无限大小的,即一个ViewGroup本身是无限大的,但是我们在布局xml文件中无论是容器类LinearLayout还是控件类TextView都必须指定一个layout_width和layout_height属性用于告诉父容器它需要多大的绘制空间。而在View中有一个MeasureSpec类,这个类是由父视图传递给子视图的绘制要求,即父视图告诉你该绘制成多大你就得绘制多大。OK!我们已经从setContentView()方法出发大概了解了一个xml文件布局文件是如何转换为一个view视图并最终添加到视图容器中,这里作一下总结:
首先我们调用setContentView()方法传递一个xml布局文件,该方法是Activity类的方法,接着,Activity类中的setContentView()会先获取一个PhoneWindow对象然后调用其setContentView()方法,在该方法中首先判断是否是第一次调用,如果是第一次调用就会创建mDecor和mContentParent对象,如果不是第一次就调用,就移除mContentParent的所有子视图,因为mContentParent只有一个。然后完成两个对象创建后就调用LayoutInflate类的inflate()方法,该方法我们也经常在程序中用来根据一个xml布局文件来动态创建一个View。inflate()方法会先获取一个XmlResourceParser对象,之后调用Xml.asAttributeSet(parser);将XmlResourceParser对象当参数传递并返回一个AttributeSet对象,该对象就包含了xml文件所有我们定义的属性了,之后再调用createViewFromTag(root, name, attrs)方法正真获取一个View视图对象,该方法会经过一些列的onCreateView()方法的调用....。在将xml文件转换为一个View后,就开始创建LayoutParams参数,这个值的确定对之后View的measure过程产生直接的影响。params也是经过AttributeSet对象的确定:params = root.generateLayoutParams(attrs);最后通过生产的View调用方法完成布局参数的设置:temp.setLayoutParams(params)。至此,xml文件到View视图转化过程完毕。
上面说了半天的xml文件到View对象的转换过程,事实上在View对象转换后并调用addView()方法添加到ViewGroup时就已经开始了测量、布局与绘制过程。我们就接着上面的addView()方法往下走:
{
//获取该视图的布局参数
LayoutParams params = child.getLayoutParams();
//如果没有布局参数,就获取系统默认的参数
if (params == null) {
params = generateDefaultLayoutParams();
//如果还是没有系统的参数,我擦,抛出异常吧....
if (params == null) {
throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
}
}
//从这里正儿八经的添加一个视图
addView(child, index, params);
}
注意,这里还会先判断一下是否有LayoutParams参数,如果没有就生产一个默认的LayoutParams,接下来调用重载的方法,源码如下:
public void addView(View child, int index, LayoutParams params) {
if (DBG) {
System.out.println(this + " addView");
}
// addViewInner() will call child.requestLayout() when setting the new LayoutParams
// therefore, we call requestLayout() on ourselves before, so that the child's request
// will be blocked at our level
//首先请求布局,此方法是View类的
requestLayout();
//这个方法也是View类的方法,此方法里会先进行判断是否有必要进行重绘,如果有必要就重绘
//没有必要就算了
invalidate(true);
//将子视图添加进来
addViewInner(child, index, params, false);
}
接下来继续看最重要的invalidate()方法,该方法在View类里面:
public void invalidate(boolean invalidateCache) {
//如果不需要重新绘制就直接跳过
if (skipInvalidate()) {
return;
}
if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS) ||
(invalidateCache && (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID) ||
(mPrivateFlags & INVALIDATED) != INVALIDATED || isOpaque() != mLastIsOpaque) {
mLastIsOpaque = isOpaque();
mPrivateFlags &= ~DRAWN;
mPrivateFlags |= DIRTY;
if (invalidateCache) {
mPrivateFlags |= INVALIDATED;
mPrivateFlags &= ~DRAWING_CACHE_VALID;
}
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
//noinspection PointlessBooleanExpression,ConstantConditions
if (!HardwareRenderer.RENDER_DIRTY_REGIONS) {
if (p != null && ai != null && ai.mHardwareAccelerated) {
// fast-track for GL-enabled applications; just invalidate the whole hierarchy
// with a null dirty rect, which tells the ViewAncestor to redraw everything
//经过一系列的判断终于来到这里了,此方法是一个重绘入口~
//这个方法会在之前已经存在的视图的基础上重绘,因此不再提供一个矩形对象
p.invalidateChild(this, null);
return;
}
}
if (p != null && ai != null) {
final Rect r = ai.mTmpInvalRect;
r.set(0, 0, mRight - mLeft, mBottom - mTop);
// Don't call invalidate -- we don't want to internally scroll
// our own bounds
//首先得说明,Rect类是一个矩形类,它拥有四个属性即:left、top、right、bottom
//这个方法会把当前视图绘制在此矩形内
//注意p是一个ViewParent接口对象,该接口是由ViewGroup实现,而所有的布局容器类都继承自ViewGroup类
//自然的,ViewGroup就重写了invalidateChild()方法,用于实现View在ViewGroup中的层级的
//该方法最终还是在
p.invalidateChild(this, r);
}
}
}
OK,我们已经理清了一个layout xml文件到View的过程,接下来我们开始详细了解View的绘制过程~
======================================== 华丽分隔线 ======================================
上面一部分我们介绍了一个布局xml文件到View的转变,那么,这里提出一个根本问题:一个View是在什么时候开始被测量的呢?其实我也不知道,那从哪里入手去寻找答案呢?我也不知道,所以我们先不管这个问题,先看看主线程ActivityThread类吧,这里面有一个main()方法,如下:
public static void main(String[] args) {
SamplingProfilerIntegration.start();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
Process.setArgV0("<pre-initialized>");
//准备主Looper
Looper.prepareMainLooper();
//创建一个ActivityThread实例
ActivityThread thread = new ActivityThread();
//调用attach方法,注意这里传递了一个false参数
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
//对AsyncTask进行初始化
AsyncTask.init();
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
//开始消息循环
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
如上在main方法中除了创建了Looper和一个Handler对象,我们先看看attach(false)做了哪些事情:
private void attach(boolean system) {
sThreadLocal.set(this);
//是否是系统线程,刚才也看到了在main()方法中传递过来的是一个false值,表明他不是一个系统线程
mSystemThread = system;
if (!system) {
//ViewRootImpl首次亮相~,它是一个什么东西呢,
ViewRootImpl.addFirstDrawHandler(new Runnable() {
public void run() {
ensureJitEnabled();
}
});
android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
UserHandle.myUserId());
//注意mAppThread是一个ApplicationThread类,通过asBinder()方法获取一个IBinder对象,也就是mRemote实例!
RuntimeInit.setApplicationObject(mAppThread.asBinder());
//获取一个IActivityManager实例
IActivityManager mgr = ActivityManagerNative.getDefault();
try {
//mAppThread是直接被定义成final的直接new的一个ApplicationThread实例
mgr.attachApplication(mAppThread);
} catch (RemoteException ex) {
// Ignore
}
} else {
// Don't set application object here -- if the system crashes,
// we can't display an alert, we just want to die die die.
android.ddm.DdmHandleAppName.setAppName("system_process",
UserHandle.myUserId());
try {
mInstrumentation = new Instrumentation();
ContextImpl context = new ContextImpl();
context.init(getSystemContext().mPackageInfo, null, this);
Application app = Instrumentation.newApplication(Application.class, context);
mAllApplications.add(app);
mInitialApplication = app;
app.onCreate();
} catch (Exception e) {
throw new RuntimeException(
"Unable to instantiate Application():" + e.toString(), e);
}
}
// add dropbox logging to libcore
DropBox.setReporter(new DropBoxReporter());
ViewRootImpl.addConfigCallback(new ComponentCallbacks2() {
public void onConfigurationChanged(Configuration newConfig) {
synchronized (mPackages) {
// We need to apply this change to the resources
// immediately, because upon returning the view
// hierarchy will be informed about it.
if (applyConfigurationToResourcesLocked(newConfig, null)) {
// This actually changed the resources! Tell
// everyone about it.
if (mPendingConfiguration == null ||
mPendingConfiguration.isOtherSeqNewer(newConfig)) {
mPendingConfiguration = newConfig;
queueOrSendMessage(H.CONFIGURATION_CHANGED, newConfig);
}
}
}
}
public void onLowMemory() {
}
public void onTrimMemory(int level) {
}
});
}
在上面attach()中包含了很多操作,主要的有创建IBinder对象(即mRemote),IActivityManager实例,ApplicationThread实例,最关键的是:当Activity启动时都是通过这个ActivityThread中的H类来完成的,H是Handler的子类。上面还出现了一个身影,那就是ViewRootImpl类(在3.0以前是ViewRoot类),这个类控制着DecorView,也就是mDecor实例,可以说是ViewRootImpl控制着View的绘制、刷新等操作,那么我们就从ViewRootImpl开始说起。这篇文章貌似有点长了,还是另外开一篇将View的绘制过程吧!