您的位置:首页 > 移动开发 > Android开发

android2.3 View视图框架源码分析之一:android是如何创建一个view的?

2012-04-17 18:23 676 查看
View是所有控件的一个基类,无论是布局(Layout),还是控件(Widget)都是继承自View类。只不过layout是一个特殊的view,它里面创建一个view的数组可以包含其他的view而已。
这一篇文章把所有的layout和widget都统称为view,那么android是如何创建一个view的呢?


[size=medium]一。在代码中直接new出来。[/size]
比如说你要创建一个TextView的实例,那么你可以这样写:

TextView text = new TextView(c); //c为context对象,表明textview是在此对象中运行的。


[size=medium]
二。把控件写在xml文件中然后通过LayoutInflater初始化一个view。[/size]

注意:下面的内容不是顺序的看的而是交替的看的。否则可能弄迷糊。
可以通过

//通过系统提供的实例获得一个LayoutInflater对象
LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//第一个参数为xml文件中view的id,第二个参数为此view的父组件,可以为null,android会自动寻找它是否拥有父组件
View view = inflater.inflate(R.layout.resourceid, null);

这样也得到了一个view的实例,让我们一步一步来看,这个view是怎么new出来的。
看类android.view.LayoutInflater

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

public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
/*可以看到通过resource id返回了一个XmlResourceParser,通过类名就可以猜测
这是一个xml的解析类。但点进去一看,发现它只是一个接口,它继承自 XmlPullParser用于pull方式解析xml的接口。和AttributeSet用于获取此view的所有属性。
那么需要能找到它的实现类。先看下面resource类。
*/
XmlResourceParser parser = getContext().getResources().getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}

/**
* 终于到了重点,获取一个这个View的实例
*/
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
/**
* 获取一个实现此AttributeSet的实例。因为此XmlPullParser是继承自AttributeSet
* 的,所以parser对象可以直接作为一个AttributeSet对象。也可以用组合的方式
* 把parser传递给另外一个实现自AttributeSet的对象,来获取一个AttributeSet实例。
**/
final AttributeSet attrs = Xml.asAttributeSet(parser);
mConstructorArgs[0] = mContext; //构造函数的参数,第一个值是此view运行所在的对象context
View result = root;

try {
// parser同时也继承了xmlPullParser,所以可以用pull解析来获取此view的根节点
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();

//如果它的根节点是一个merge对象,则必须手动设置此view的父节点,否则抛出异常
//因为由merge创建的xml文件,常常被其他layout所包含
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 {
// 此inflate的xml文件中的root view。即我们通过inflate返回得到的view
View temp = createViewFromTag(name, attrs);

ViewGroup.LayoutParams params = null;

if (root != null) {
// 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);
}
}


// 加载temp下所有的子view
rInflate(parser, temp, attrs);


//如果给出了root,则把此view添加到root中去
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;
}

return result;
}
}


/**
*
* 有上至下递归的初始化所有子view和子view的子view。在此方法被调用完成后
* 会调用此view的parent view的onFinishInflate方法。表明其子view全部加载完毕
*/
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 { //看这里,创建view的方法。而且这里已经重新获得了它的
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();
}


View createViewFromTag(String name, AttributeSet attrs) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}

if (DEBUG) System.out.println("******** Creating view: " + name);

try {
View view = (mFactory == null) ? null : mFactory.onCreateView(name,
mContext, attrs);

if (view == null) {
if (-1 == name.indexOf('.')) { //这里只是为了判断xml文件中tag的属性是否加了包名
view = onCreateView(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;
}
}

/**
* 真正创建一个view的方法,
* 此方法是用反射获取构造器来实例对象而不是直接new出来这是为了处于性能优化考虑,
* 同一个类名的不同对象,可以直接得到缓存的构造器直接获取一个构造器对象实例。而不需要
* 重复进行new操作。
*
* @param name 此View的全名
* @param prefix 前缀,值为 "android.view."其实就是是否包含包名
* @param attrs 此view的属性值,传递给此view的构造函数
*/
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Constructor constructor = sConstructorMap.get(name); //缓存中是否已经有了一个构造函数
Class clazz = null;

try {
if (constructor == null) {
//通过类名获得一个class对象
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name);

if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
}
//通过参数类型获得一个构造器,参数列表为context,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);

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; //args[0]已经在前面初始好了。这里只要初始化args[1]
return (View) constructor.newInstance(args); //通过反射new出一个对象。。大功告成

} catch (NoSuchMethodException e) {
InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class "
+ (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;
}
}




在类android.content.res.Resources类中获取XmlResourceParser对象;

public XmlResourceParser getLayout(int id) throws NotFoundException {
return loadXmlResourceParser(id, "layout");
}

/*package*/ XmlResourceParser loadXmlResourceParser(int id, String type)
throws NotFoundException {
synchronized (mTmpValue) {
/*TypedValue对象保存了一些有关resource 的数据值,比如说,对于一个view来说,在xml
文件中可以定义许多属性,TypedValue保存了其中一个属性的相关信息,包括此属性的值的类型
type,是boolean还是color还是reference还是String,这些在attr.xml文件下都有定义。
它的值的字符串名称;一个属性有多个值时,它从xml文件中获取的值它的顺序data;如果此属性的值
的类型是一个reference则保存它的resource id的等等。
*/
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");
}
}

/**
* getValue方法,id表示要查找的控件的 id,outValue是一个对象,用于保存一些属性相关信息
* resolveRefs为true表明,当通过属性id找到xml文件中的标签时,比如是一个<Button android:id="@+id/button"/>
* 它的值是一个引用reference,则继续解析获得这个id的值。这里看AssetManager类的实现*/
public void getValue(int id, TypedValue outValue, boolean resolveRefs)
throws NotFoundException {
boolean found = mAssets.getResourceValue(id, outValue, resolveRefs);
if (found) {
return;
}
throw new NotFoundException("Resource ID #0x"
+ Integer.toHexString(id));
}

/*package*/ XmlResourceParser loadXmlResourceParser(String file, int id,
int assetCookie, String type) throws NotFoundException {
if (id != 0) {
try {
//取缓存
synchronized (mCachedXmlBlockIds) {
// First see if this block is in our cache.
final int num = mCachedXmlBlockIds.length;
for (int i=0; i<num; i++) {
if (mCachedXmlBlockIds[i] == id) {
//System.out.println("**** REUSING XML BLOCK! id="
// + id + ", index=" + i);
return mCachedXmlBlocks[i].newParser();
}
}

//第一次加载时,会打开这个文件获取一个xml数据块对象。
// 这里先看AssetManager类的实现
XmlBlock block = mAssets.openXmlBlockAsset(
assetCookie, file);

//下面会把此xmlBlock对象缓存起来,保存id和block,
//以后如果是同样的id,直接在缓存中取XmlBlock。
//这样就不用再在本地方法中打开文件创建解析树了。
if (block != null) {
int pos = mLastCachedXmlBlockIndex+1;
if (pos >= num) pos = 0;
mLastCachedXmlBlockIndex = pos;
XmlBlock oldBlock = mCachedXmlBlocks[pos];
if (oldBlock != null) {
oldBlock.close();
}
mCachedXmlBlockIds[pos] = id;
mCachedXmlBlocks[pos] = block;
//返回的内部类继承了XmlResourceParser,在APi中此类是隐藏的
return block.newParser();
}
}
} catch (Exception e) {
NotFoundException rnf = new NotFoundException(
"File " + file + " from xml type " + type + " resource ID #0x"
+ Integer.toHexString(id));
rnf.initCause(e);
throw rnf;
}
}

throw new NotFoundException(
"File " + file + " from xml type " + type + " resource ID #0x"
+ Integer.toHexString(id));
}


android.content.res.AssetManager类

/*package*/ final boolean getResourceValue(int ident,
TypedValue outValue,
boolean resolveRefs)
{
int block = loadResourceValue(ident, outValue, resolveRefs);
if (block >= 0) {
if (outValue.type != TypedValue.TYPE_STRING) {
return true;
}
//mStringBlocks通过本地方法保存所有布局文件的文件名
outValue.string = mStringBlocks[block].get(outValue.data);
return true;
}
return false;
}

//这是一个本地方法,是在本地方法中获取这个控件信息,返回通过此控件的id找到的文件名
//的位置,由于个人对c++不是很了解,只初略的解释本地方法的一些功能。
//对于的JNI文件位于:\frameworks\base\core\jni\android_util_AssetManager.cpp
private native final int loadResourceValue(int ident, TypedValue outValue,
boolean resolve);


/**
* 通过文件名,在本地方法中找到这个xml文件,并且在本地方法中生成一个xml解析对象。
* 返回一个id,这个id对应java中的xmlBlock对象。这样xml文件就被load进了内存。
* 也就是android所说的预编译,以后再访问只要直接去取数据即可
*/
/*package*/ final XmlBlock openXmlBlockAsset(int cookie, String fileName)
throws IOException {
synchronized (this) {
if (!mOpen) {
throw new RuntimeException("Assetmanager has been closed");
}
int xmlBlock = openXmlAssetNative(cookie, fileName);
if (xmlBlock != 0) {
/*
* 在XmlBlock对象中,终于到找了实现XmlResourceParser接口的类
* Parser,它是XmlBlock的一个内部类。这里面可以获取所有xml文件中的内容。
* 不管是属性还是Tag标签。这里xmlBlock是用来与本地类中的解析树对象对应的。
* 所有的解析方法,其实都是调用的本地xml解析树中的方法。所以此类中有大量的
* 本地方法。
*/
XmlBlock res = new XmlBlock(this, xmlBlock);
incRefsLocked(res.hashCode());
return res;
}
}
throw new FileNotFoundException("Asset XML file: " + fileName);
}



[size=medium]三 。通过view.findViewById(resourceid)获得一个view的实例[/size]
android.View.View类中

//调用了通过id检索view的方法
public final View findViewById(int id) {
if (id < 0) {
return null;
}
return findViewTraversal(id);
}

//不是吧,这不是坑爹吗?猜想肯定是被viewgroup重写了
protected View findViewTraversal(int id) {
if (id == mID) {
return this;
}
return null;
}


android.View.ViewGroup类中


//哈哈,果然重写了此方法。其实就是在viewgroup包含的
//子view数组中进行遍历。那么view是什么时候被加入进
//viewgroup中的呢?如果是在代码中写,肯定是直接使用
//addView方法把view加入viewGroup。如果写在xml布局文件
//中,其实是在第二种方法中被加入view的。inflate加载父view
//时会同时把其所有的子view加载完,同时addView到父view中
protected View findViewTraversal(int id) {
if (id == mID) {
return this;
}

final View[] where = mChildren;
final int len = mChildrenCount;

for (int i = 0; i < len; i++) {
View v = where[i];

if ((v.mPrivateFlags & IS_ROOT_NAMESPACE) == 0) {
v = v.findViewById(id);

if (v != null) {
return v;
}
}
}

return null;
}



[size=medium]四。通过activity的setContentView方法和findViewById获取一个view的实例。[/size]
它是通过
getWindow().setContentView(layoutResID);设置window对象的view
再来看看window对象是在哪里获得到的,在类Activity中找到
mWindow = PolicyManager.makeNewWindow(this);
它是由PolicyManager生成的。
找到com.android.internal.policy.PolicyManager,找到方法



//window是由sPolicy对象创建的
public static Window makeNewWindow(Context context) {
return sPolicy.makeNewWindow(context);
}

//sPolicy对象是通过反射,获取的一个实例
//此类的实现在com.android.internal.policy.impl.Policy中
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-time
try {
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);
} catch (InstantiationException ex) {
throw new RuntimeException(
POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
} catch (IllegalAccessException ex) {
throw new RuntimeException(
POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
}
}



找到com.android.internal.policy.impl.Policy类


public PhoneWindow makeNewWindow(Context context) {
return new PhoneWindow(context);
}



它其实是一个phoneWindow对象,继承自window对象
找到com.android.internal.policy.impl.phoneWindow 看它内部是如何把resourceid加载成一个view的



private ViewGroup mContentParent;
//这是window的顶层视图,它包含一些窗口的装饰,比图title bar,状态栏等等
private DecorView mDecor;

//这里的layoutResID也是由mLayoutInflater进行加载的,加载的方式与第二种方法一样。
//只不过这里把的到的view变成了mContentParent的子view
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);
final Callback cb = getCallback();
if (cb != null) { //这是回调方法,表明mContentParent的子view已经发生改变
cb.onContentChanged();
}
}

//再来看看mContentParent究竟是何物,它肯定是一个viewGroup
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);

mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
if (mTitleView != null) { //这里设置的是是否隐藏titleContainer,即头部titlebar
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);
}
}
}
}

//当顶层view为null是,new了一个DecorView
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}

//这里生成了mContentParent。
protected ViewGroup generateLayout(DecorView decor) {



mDecor.startChanging();
//根据window的不同参数选择layoutResource
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));

ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
return contentParent;
}

//顶层view是一个framelayout
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
public DecorView(Context context, int featureId) {
super(context);
mFeatureId = featureId;
}
}


//下面说明findVIewById

//首先是获取顶层view,即继承自FrameLayout的viewgorup
@Override
public final View getDecorView() {
if (mDecor == null) {
installDecor();
}
return mDecor;
}

//然后mDecor.findViewById根据id获取它的子view
//这里就是通过第三种方法获取它的子view
阅读更多
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐