转载请注明出处:http://blog.csdn.net/droyon/article/details/22429191
本文主要介绍一下问题:
1、android资源相关的简要介绍。2、xml文件,在android中的加载、解析过程。3、以layout文件为例,介绍layout文件是如何一步一步的被andrioid加载、解析、生成View树。
--------------------------------------------------------------------------------------------------------------------------------------------
Android的资源类型包括:drawable、layout、string、color、menu、animation等。这些资源我们会定义在xml文件中,在具体使用时,我们加载xml,但是xml里定义的属性和赋值是如何被android解析的,这是本文要介绍的重点:
xml文件样例:
<view xmlns:android="http://schemas.android.com/apk/res/android"
class="com.android.internal.widget.ActionBarView$HomeView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="?android:attr/actionBarItemBackground" >
<ImageView android:id="@android:id/up"
android:src="?android:attr/homeAsUpIndicator"
android:layout_gravity="center_vertical|left"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="-8dip" />
<ImageView android:id="@android:id/home"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="8dip"
android:layout_marginTop="@android:dimen/action_bar_icon_vertical_padding"
android:layout_marginBottom="@android:dimen/action_bar_icon_vertical_padding"
android:layout_gravity="center"
android:adjustViewBounds="true"
android:scaleType="fitCenter" />
</view>
一、我们为什么能够使用android:layout_width这样进行view的定义?
原因是我们在framework/base/core/res/res/values/attr.xml文件中定义了如下代码:
<declare-styleable name="ViewGroup_Layout">
<!-- Specifies the basic width of the view. This is a required attribute
for any view inside of a containing layout manager. Its value may
be a dimension (such as "12dip") for a constant width or one of
the special constants. -->
<attr name="layout_width" format="dimension">
<!-- The view should be as big as its parent (minus padding).
This constant is deprecated starting from API Level 8 and
is replaced by {@code match_parent}. -->
<enum name="fill_parent" value="-1" />
<!-- The view should be as big as its parent (minus padding).
Introduced in API Level 8. -->
<enum name="match_parent" value="-1" />
<!-- The view should be only big enough to enclose its content (plus padding). -->
<enum name="wrap_content" value="-2" />
</attr>
<!-- Specifies the basic height of the view. This is a required attribute
for any view inside of a containing layout manager. Its value may
be a dimension (such as "12dip") for a constant height or one of
the special constants. -->
<attr name="layout_height" format="dimension">
我们看到layout_width下方定义了enum属性,就是这些属性的定义,将我们的fill_parent以及wrap_content属性转化为-1、-2等值。
二、上面提到了属性的定义,什么是属性?属性定义和引用的区别?
android:layout_width="wrap_content"
如上代码,layout_width是属性,wrap_content就是属性对应的值。
在framework/base/core/res/res/values/attr.xml文件中:
定义的形式:
<attr name="layout_width" format="dimension">
使用的形式:
<declare-styleable name="ViewGroup_MarginLayout">
<attr name="layout_width" />
我们可以看到,属性的声明和使用,不同在于声明的地方使用format,一个属性可以使用多次,但仅能定义/声明一次。
三、styleable和style的区别?
从定义上来说:
style是样式,是一系列属性的集合。style是android将某一类属性集中定义[赋值]的一种表现形式。
styleable翻译为可样式化的,styleable是android将某一类属性集中访问的一种表现形式。
从使用上来说:
使用style
<application
android:theme="@style/AppTheme" >
使用styleable:
TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View,
defStyle, 0);
for (int i = 0; i < N; i++) {
int attr = a.getIndex(i);
switch (attr) {
case com.android.internal.R.styleable.View_background:
background = a.getDrawable(attr);
break;
case com.android.internal.R.styleable.View_padding:
padding = a.getDimensionPixelSize(attr, -1);
break;
styleable在编译时会在R.java文件中增加一个int[]数组,aapt为每一包含在styleable中的attr分配了一个指定的id值。
//----------------------------------------------------------------------------------------------------------
四、layout资源文件是如何被加载与解析的?里面定义了ImageView、View等节点。
我们在activity中通过setContentView使用layout文件,代码如下:
1、/android-4.0-mr1/frameworks/base/core/java/android/app/Activity.java
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initActionBar();
}
我们看getWindow().setContentView(),这里的window是phoneWindow
2、path /android-4.0-mr1/frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
这其中关键代码为:mLayoutInflater.inflate。我们继续跟踪:
3、path /android-4.0-mr1/frameworks/base/core/java/android/view/LayoutInflater.java
public View inflate(int resource, ViewGroup root) {
return inflate(resource, root, root != null);
}
最终会执行到下面的代码:
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();
}
}
在这段代码中,构建了XmlResourceParser对象。对象创建过程如下:
3.1、调用Resouces.java中的getLayout方法:
/android-4.0-mr1/frameworks/base/core/java/android/content/res/Resources.java
public XmlResourceParser getLayout(int id) throws NotFoundException {
return loadXmlResourceParser(id, "layout");
}
接着调用loadXmlResouceParser
3.2、
/*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");
}
}
这个方法中,首先调用getValue方法,这里传递进去一个TypeValue对象(value),layout对应的文件的内容会被加载到这个对象中
。getValue方法第一个参数为layout文件的id值,第二个参数是TypeValue对象,第三个对象是true,表明此资源文件非文件,仅仅是一个引用。
getValue方法内容如下:
public void getValue(int id, TypedValue outValue, boolean resolveRefs)
throws NotFoundException {
boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs);
if (found) {
return;
}
throw new NotFoundException("Resource ID #0x"
+ Integer.toHexString(id));
}
调用mAsserts对象的getResouceValue方法,如果found为false,抛出NotFoundException异常。
path /android-4.0-mr1/frameworks/base/core/java/android/content/res/AssetManager.java
/*package*/ final boolean getResourceValue(int ident,
int density,
TypedValue outValue,
boolean resolveRefs)
{
int block = loadResourceValue(ident, (short) density, outValue, resolveRefs);
if (block >= 0) {
if (outValue.type != TypedValue.TYPE_STRING) {
return true;
}
outValue.string = mStringBlocks[block].get(outValue.data);
return true;
}
return false;
}
在此方法中调用loadResouceValue方法:此方法的实现时通过JNI实现的,实现代码如下:
path /android-4.0-mr1/frameworks/base/core/jni/android_util_AssetManager.cpp
static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject clazz,
jint ident,
jshort density,
jobject outValue,
jboolean resolve)
{
AssetManager* am = assetManagerForJavaObject(env, clazz);
if (am == NULL) {
return 0;
}
const ResTable& res(am->getResources());
Res_value value;
ResTable_config config;
uint32_t typeSpecFlags;
ssize_t block = res.getResource(ident, &value, false, density, &typeSpecFlags, &config);
#if THROW_ON_BAD_ID
if (block == BAD_INDEX) {
jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
return 0;
}
#endif
uint32_t ref = ident;
if (resolve) {
block = res.resolveReference(&value, block, &ref);
#if THROW_ON_BAD_ID
if (block == BAD_INDEX) {
jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
return 0;
}
#endif
}
return block >= 0 ? copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config) : block;
}
首先通过如下代码进行加载解析二进制layout文件:
ssize_t block = res.getResource(ident, &value, false, density, &typeSpecFlags, &config);
接着根据resolver这个bool变量,进行下一步加载。(resolver代表ident是否为引用,引用到另一个文件,如果为false,则表明ident为文件本身)
if (resolve) {
block = res.resolveReference(&value, block, &ref);
最后根据block是否>0,来决定是否将二进制layout拷贝到value中。如果layotu文件存在,则block>0。
层层返回,就可以接着执行loadXmlResouceParse。
if (value.type == TypedValue.TYPE_STRING) {
return loadXmlResourceParser(value.string.toString(), id,
value.assetCookie, type);
}
代码如下:
/*package*/ XmlResourceParser loadXmlResourceParser(String file, int id,
int assetCookie, String type) throws NotFoundException {
if (id != 0) {
try {
// These may be compiled...
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();
}
}
// Not in the cache, create a new block and put it at
// the next slot in the cache.
XmlBlock block = mAssets.openXmlBlockAsset(
assetCookie, file);
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;
//System.out.println("**** CACHING NEW XML BLOCK! id="
// + id + ", index=" + pos);
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));
}
3.3、这段代码的作用就是根据上一步执行的结果得到真实的layout文件,解析layot文件,得到键值对。调用XmlBlock block = mAssets.openXmlBlockAsset(assetCookie, file);,得到XmlBlock对象,并将对象加入到Cache中(mCachedXmlBlocks[pos] = block;)。最后调用block.newParser得到XmlResourceParser对象。
详细流程如下:
/*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 res = new XmlBlock(this, xmlBlock);
incRefsLocked(res.hashCode());
return res;
}
}
throw new FileNotFoundException("Asset XML file: " + fileName);
}
调用openXmlAssetNative方法:
static jint android_content_AssetManager_openXmlAssetNative(JNIEnv* env, jobject clazz,
jint cookie,
jstring fileName)
{
AssetManager* am = assetManagerForJavaObject(env, clazz);
if (am == NULL) {
return 0;
}
LOGV("openXmlAsset in %p (Java object %p)\n", am, clazz);
ScopedUtfChars fileName8(env, fileName);
if (fileName8.c_str() == NULL) {
return 0;
}
Asset* a = cookie
? am->openNonAsset((void*)cookie, fileName8.c_str(), Asset::ACCESS_BUFFER)
: am->openNonAsset(fileName8.c_str(), Asset::ACCESS_BUFFER);
if (a == NULL) {
jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str());
return 0;
}
ResXMLTree* block = new ResXMLTree();
status_t err = block->setTo(a->getBuffer(true), a->getLength(), true);
a->close();
delete a;
if (err != NO_ERROR) {
jniThrowException(env, "java/io/FileNotFoundException", "Corrupt XML binary file");
return 0;
}
return (jint)block;
}
path
/android-4.0-mr1/frameworks/base/core/java/android/content/res/XmlBlock.java
public XmlResourceParser newParser() {
synchronized (this) {
if (mNative != 0) {
return new Parser(nativeCreateParseState(mNative), this);
}
return null;
}
}
path
/android-4.0-mr1/frameworks/base/core/jni/android_util_XmlBlock.cpp
static jint android_content_XmlBlock_nativeCreateParseState(JNIEnv* env, jobject clazz,
jint token)
{
ResXMLTree* osb = (ResXMLTree*)token;
if (osb == NULL) {
jniThrowNullPointerException(env, NULL);
return 0;
}
ResXMLParser* st = new ResXMLParser(*osb);
if (st == NULL) {
jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
return 0;
}
st->restart();
return (jint)st;
}
3.4、得到了XmlResouceParse对象,那么就可以继续往下执行了。接下来就是继续执行inflate方法。
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();
}
}
在3.2,3.3中,我们从二进制layout文件id得到了layout真实文件,并从layout真实文件中加载出键值对以及各种属性。那么此处就是将得到的view、IamgeView等tag解析并且构建View对象树。
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
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);
} else {
// Temp is the root view that was found in the xml
View temp;
if (TAG_1995.equals(name)) {
temp = new BlinkLayout(mContext, attrs);
} else {
temp = createViewFromTag(root, name, attrs);
}
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 = 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 temp
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) {
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;
}
}
解析过程:
1、解析开始,确认非空。
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
2、得到tag的名字。
final String name = parser.getName();
3、根据tag的name创建View
temp = createViewFromTag(root, name, attrs);
创建过程如下:
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;
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;
}
}
创建tag的name指定的view的过程解析:
3.1、判断tag的name是否为view,如果为view,得到view指示的class。
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
由于我们的xml文件以view开头,故而,此处name为:class="com.android.internal.widget.ActionBarView$HomeView"
<view xmlns:android="http://schemas.android.com/apk/res/android"
class="com.android.internal.widget.ActionBarView$HomeView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="?android:attr/actionBarItemBackground" >
3.2、
是否指定了ViewFactory用户指定创建view的规则。android允许设定ViewFactory,从ViewFactory中用户可以设定特定tag所对应的View文件。
Preference的加载方式也是这样的,但据我所查,android指示定义,但未使用。android提供了良好的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);
}
3.3、根据View的名字进行View的实例化。
if (view == null) {
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
}
如果tag的名字不含有“.”,那么执行onCreateView。如果含有“.”,那么就认为此处的tag的name对应的View为自定义View。这两行代码最终会殊途同归,最终都会通过反射进行View对象的创建。只不过,如果不含有“.”,例如此处的ImageView,不含有“.”,那么就实例化andriod.view.ImageView这个class对象,如果是自定义View,那么就省去了拼接操作,直接去实例化相应的对象。例如:<com.android.myView />这样一个xml标记,就直接去实例化com.android.myView这个View。
protected View onCreateView(String name, AttributeSet attrs)
throws ClassNotFoundException {
return createView(name, "android.view.", attrs);
}
反射实例化View对象:
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);
Object[] args = mConstructorArgs;
args[1] = attrs;
return constructor.newInstance(args);
3.4、将创建的View对象返回。
return view;
以上就是View的实例化创建过程。
4、上面是解析得到tag,并根据tag的name进行反射操作,得到View的实例化对象。接着上面说,第4步操作:
上面得到的tag为一级tag,如果我们的tag为LinearLayout,那么下面还会有子对象。我们的第4步就是实例化子View对象。
rInflate(parser, temp, attrs, true);
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 if (TAG_1995.equals(name)) {
final View view = new BlinkLayout(mContext, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflate(parser, view, attrs, true);
viewGroup.addView(view, params);
} else {
final View view = createViewFromTag(parent, name, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflate(parser, view, attrs, true);
viewGroup.addView(view, params);
}
创建过程不再重复,和之前的创建View的过程类似。这个过程会遍历加载,会加载include属性指定的layout文件,还会加载requestFocus属性等。
5、应用ViewGroup.paras参数,如果root根View不为空,将创建的View树加到root根View中,如果root根View为null,则新创建的View为根View。
ViewGroup.LayoutParams params = null;
params = root.generateLayoutParams(attrs);
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;
}
6、返回View树的根View。
return result;
以上就是layout文件加载,解析的大体过程。
上面是个人对layout以及资源加载的一些总结,欢迎大家交流。