转载请注明出处: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; }
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);
return 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; }
return result;
上面是个人对layout以及资源加载的一些总结,欢迎大家交流。