改章节是一篇关于资源成员函数的帖子
我们晓得,在Android系统中,每个应用程序一般都市配置很多资源,用来适配不同密度、小大和向方的屏幕,以及适配不同的家国、区地和言语等等。这些资源是在应用程序运行时主动根据设备的以后配置信息停止适配的。这也就是说,给定一个同相的资源ID,在不同的设备配置之下,查找到的多是不同的资源。这个资源查找程过对应用程序说来,是完整明透的。在本文中,我们就详细析分资源管理框架是如何根据ID来查找资源的。
老罗的浪新微博:http://weibo.com/shengyangluo,欢送存眷!
从后面Android应用程序资源管理器(Asset Manager)的建创程过析分一文可以晓得,Android资源管理框架现实就是由AssetManager和Resources两个类来实现的。其中,Resources类可以根据ID来查找资源,而AssetManager类根据件文名来查找资源。事实上,如果一个资源ID对应的是一个件文,那么Resources类是先根据ID来找到资源件文名称,然后再将该件文名称交给AssetManager类来打开对应的件文的,这个程过如图1所示。
图1 应用程序查找资源的程过示意图
在图1中,Resources类根据资源ID来查到资源名称现实上也是要通过AssetManager类来实现的,这是因为资源ID与资源名称的对应系关是由打包在APK面里的resources.arsc件文中的。当Resources类查找的资源对应的是一个件文的时候,它就会再次将资源名称交给AssetManager,以便后者可以打开对应的件文,否则的话,上一步找到的资源名称就是终最的查找结果。
从后面Android应用程序资源的编译和打包程过析分一文可以晓得,APK包面里的resources.arsc件文是在编译应用程序资源的时候生成的,然后连同其它被编译的以及原生的资源起一打包在一个APK包面里。
从后面Android资源管理框架(Asset Manager)扼要分析和学习筹划一文又可以晓得,Android应用程序资源是可以分别是很多类别的,但是从资源查找的程过来看,它们可以归结为两大类。第一类资源是不对应有件文的,而第二类资源是对应有件文的,例如,字符串资源是直接编译在resources.arsc件文中的,而界面布局资源是在APK包面里是对应的独自的件文的。如上所述,不对应件文的资源只要需执行从资源ID到资源名称的转换可即,而对应有件文的资源还要需根据资源名称来打开对应的件文。在本文中,我们就以界面布局资源的查找程过为例,来明说Android资源管理框架查找资源的程过。
我们晓得,每个Activity件组建创的时候,它的成员数函onCreate都市被用调,而在Activity件组的成员数函onCreate中,我们基本上都无一例外地用调setContentView来置设Activity件组的界面。在用调Activity件组的成员数函setContentView的时候,要需指定一个layout类型的资源ID,以便Android资源管理框架可以找到指定的Xml资源件文来充填(inflate)为Activity件组的界面。接上去,我们就从Activity类的成员数函setContentView开始,析分Android资源管理框架查找layout资源的程过,如图2所示。
图2 类型为layout的资源的查找程过
这个程过可以分为22个骤步,接上去我们就详细析分每个骤步。
Step 1. Activity.setContentView
public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory, Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener, ComponentCallbacks { ...... private Window mWindow; ...... public Window getWindow() { return mWindow; } ..... public void setContentView(int layoutResID) { getWindow().setContentView(layoutResID); } ...... }
这个数函定义在件文frameworks/base/core/java/android/app/Activity.java中。
从后面Android应用程序窗口(Activity)的窗口对象(Window)的建创程过析分一文可以晓得,Activity类的成员量变mWindow指向的是一个PhoneWindow对象,因此,Activity类的成员数函setContentView现实上是用调PhoneWindow类的成员数函setContentView来进一步操纵。
Step 2. PhoneWindow.setContentView
public class PhoneWindow extends Window implements MenuBuilder.Callback { ...... // This is the view in which the window contents are placed. It is either // mDecor itself, or a child of mDecor where the contents go. private ViewGroup mContentParent; ...... private LayoutInflater mLayoutInflater; ...... @Override public void setContentView(int layoutResID) { if (mContentParent == null) { installDecor(); } else { mContentParent.removeAllViews(); } mLayoutInflater.inflate(layoutResID, mContentParent); final Callback cb = getCallback(); if (cb != null) { cb.onContentChanged(); } } ...... }
这个数函定义在件文frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java中。
PhoneWindow类的成员量变mContentParent用来描述一个类型为DecorView的视图对象,或者这个类型为DecorView的视图对象的一个子视图对象,用作UI容器。当它的值即是null的时候,就明说以后正在理处的Activity件组的视图对象还没有建创。在种这情况下,就会用调成员数函installDecor来建创以后正在理处的Activity件组的视图对象。否则的话,就明说是要从新置设以后正在理处的Activity件组的视图。在从新置设之前,首先用调成员量变mContentParent所描述的一个ViewGroup对象来移除来原的UI容内。
PhoneWindow类的成员量变mLayoutInflater指向的是一个PhoneLayoutInflater对象。PhoneLayoutInflater类是从LayoutInflater类续继上去的,同时它也继承了LayoutInflater类的成员数函inflate。通过用调PhoneWindow类的成员量变mLayoutInflater所指向的一个PhoneLayoutInflater对象的成员数函inflate,也就是从父类继承上去的成员数函inflate,以可就将参数layoutResID所描述的一个UI布局置设到mContentParent所描述的一个视图容器中去。这样以可就将以后正在理处的Activity件组的UI建创出来。
最后,PhoneWindow类的成员数函还会用调一个Callback口接的成员数函onContentChanged来通知以后正在理处的Activity件组,它的视图容内产生改变了。从后面Android应用程序窗口(Activity)的窗口对象(Window)的建创程过析分一文可以晓得,每个Activity件组都实现了一个Callback口接,并且将这个Callback口接置设到了与它所关联的PhoneWindow的部内去,因此,最后用调的现实上是Activity类的成员数函onContentChanged。
接上去,我们就续继析分LayoutInflater类的成员数函inflate的实现,以便可以解了Android资源管理框架是如何找到参数layoutResID所描述的UI布局件文的。
Step 3. LayoutInflater.inflate
public abstract class LayoutInflater { ...... public View inflate(int resource, ViewGroup root) { return inflate(resource, root, root != null); } ...... public View inflate(int resource, ViewGroup root, boolean attachToRoot) { ...... XmlResourceParser parser = getContext().getResources().getLayout(resource); try { return inflate(parser, root, attachToRoot); } finally { parser.close(); } } ...... }
这个数函定义在件文frameworks/base/core/java/android/view/LayoutInflater.java中。
LayoutInflater类两个参数版本的成员数函inflate通过用调三个参数版本的成员数函inflate来查找参数resource所描述的UI布局件文。
在LayoutInflater类三个参数版本的成员数函inflate中,首先是得获用来描述以后运行上下文境环的一个Resources对象,然后接用调这个Resources对象的成员数函getLayout来查找参数resource所描述的UI布局件文。
Resources类的成员数函getLayout找到了指定的UI布局件文之后,就会打开它。由于Android系统的UI布局件文是一个Xml件文,因此,Resources类的成员数函getLayout打开它之后,到得的是一个XmlResourceParser对象。有了这个XmlResourceParser对象之后,LayoutInflater类三个参数版本的成员数函inflate就将它传递给另外一个三个参数版本的成员数函inflate,以便后者可以通过它来建创一个UI界面。
接上去,我们就首先析分Resources类的成员数函getLayout的实现,然后再析分LayoutInflater类的另外一个三个参数版本的成员数函inflate的实现。
Step 4. Resources.getLayout
public class Resources { ...... public XmlResourceParser getLayout(int id) throws NotFoundException { return loadXmlResourceParser(id, "layout"); } ...... }
这个数函定义在件文frameworks/base/core/java/android/content/res/Resources.java中。
Resources类的成员数函getLayout的实现很简单,它通过用调另外一个成员数函loadXmlResourceParser来查找并且打开由参数id所描述的一个UI布局件文。
Step 5. Resources.loadXmlResourceParser
public class Resources { ...... /*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"); } } ...... }
这个数函定义在件文frameworks/base/core/java/android/content/res/Resources.java中。
参数id描述的是一个资源ID,Resources类的成员数函loadXmlResourceParser首先用调另外一个成员数函getValue来得获该资源ID所对应的资源值,并且保存在一个类型为TypedValue的量变value中。在我们这个景情中,参数id描述的是一个类型为layout的资源ID,从后面Android应用程序资源的编译和打包程过析分一文可以晓得,类型为layout的资源ID对应的资源值即为一个UI布局件文名称。有了这个UI布局件文名称之后,Resources类的成员数函loadXmlResourceParser接着再用调另外一个四个参数版本的成员数函loadXmlResourceParser来加载对应的UI布局件文,并且到得一个XmlResourceParser对象回返给用调者。
意注,如果Resources类的成员数函getValue没有找到与参数id所描述的资源,或者找到的资源的值不是字符串类型的,那么Resources类的成员数函loadXmlResourceParser就会抛出一个类型为NotFoundException的常异。
接上去,我们就首先析分Resources类的成员数函getValue的实现,接着再析分Resources类四个参数版本的成员数函loadXmlResourceParser的实现。
Step 6. Resources.getValue
public class Resources { ...... /*package*/ final AssetManager mAssets; ...... 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)); } ...... }
这个数函定义在件文frameworks/base/core/java/android/content/res/Resources.java中。
Resources类的成员量变mAssets指向的是一个AssetManager对象,Resources类的成员数函getValue通过用调它的成员数函getResourceValue来得获与参数id所对应的资源的值。意注,如果AssetManager类的成员数函getResourceValue查找不到与参数id所对应的资源,那么Resources类的成员数函getValue就会抛出一个类型为NotFoundException的常异。
接上去,我们就续继析分AssetManager类的成员数函getResourceValue的实现。
Step 7. AssetManager.getResourceValue
public final class AssetManager { ...... private StringBlock mStringBlocks[] = null; ...... /*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; } outValue.string = mStringBlocks[block].get(outValue.data); return true; } return false; } ...... }
这个数函定义在件文frameworks/base/core/java/android/content/res/AssetManager.java中。
AssetManager类的成员数函getResourceValue通过用调另外一个成员数函loadResourceValue来加载参数ident所描述的资源。如果加载功成,那么结果就会保存在参数outValue所描述的一个TypedValue对象中,并且AssetManager类的成员数函loadResourceValue的回返值block大于即是0。
从后面Android应用程序资源管理器(Asset Manager)的建创程过析分一文可以晓得,AssetManager类的成员量变mStringBlock描述的是一个StringBlock组数。这个StringBlock组数中的每个StringBlock对象描述的都是以后应用程序应用的每个资源索引表的资源项值字符串资源池。关于资源索引表的格式以及生成程过,可以参考后面Android应用程序资源的编译和打包程过析分一文。
解了了上述背景之后,我们以可就晓得,当AssetManager类的成员数函loadResourceValue的回返值block大于即是0的时候,现实上就示表参数ident所描述的资源项在以后应用程序应用的第block个资源索引表中,而当参数ident所描述的资源项是一个字符串时,那么以可就在第block个资源索引表的资源项值字符串资源池中找到对应的字符串,并且保存在参数outValue所描述的一个TypedValue对象的成员量变string中,以便回返给用调者应用。意注,终最到得的字符串在第block个资源索引表的资源项值字符串资源池中的位置就保存在参数outValue所描述的一个TypedValue对象的成员量变data中。
接上去,我们就续继析分AssetManager类的成员数函loadResourceValue的实现。
Step 8. AssetManager.loadResourceValue
public final class AssetManager { ...... /** Returns true if the resource was found, filling in mRetStringBlock and * mRetData. */ private native final int loadResourceValue(int ident, TypedValue outValue, boolean resolve); ...... }
这个数函定义在件文frameworks/base/core/java/android/content/res/AssetManager.java中。
AssetManager类的成员数函loadResourceValue是一个JNI方法,它是由C++层的数函android_content_AssetManager_loadResourceValue来实现的,如下所示:
static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject clazz, jint ident, 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, &typeSpecFlags, &config); ...... uint32_t ref = ident; if (resolve) { block = res.resolveReference(&value, block, &ref); ...... } return block >= 0 ? copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config) : block; }
这个数函定义在件文frameworks/base/core/jni/android_util_AssetManager.cpp中。
数函android_content_AssetManager_loadResourceValue要主是执行以下五个操纵:
1. 用调数函assetManagerForJavaObject来将参数clazz所描述的一个Java层的AssetManager对象的成员量变mObject转换为一个C++层的AssetManager对象。
2. 用调上述到得的C++层的AssetManager对象的成员数函getResources来得获一个ResTable对象,这个ResTable对象描述的是一个资源表。
3. 用调上述到得的ResTable对象的成员数函getResource来得获与参数ident所对应的资源项值及其配置信息,并且保存在类型为Res_value的量变value以及类型为ResTable_config的量变config中。
4. 如果参数resolve的值即是true,那么就续继用调上述到得的ResTable对象的成员数函resolveReference来剖析后面所到得的资源项值。
5. 用调数函copyValue将上述到得的资源项值及其配置信息拷贝到参数outValue所描述的一个Java层的TypedValue对象中去,回返用调者可以得获与参数ident所对应的资源项容内。
接上去,我们就要主析分第2~4操纵,即AssetManager对象的成员数函getResources以及ResTable类的成员数函getResource和resolveReference的实现,以便可以解了Android应用程序资源的查找程过。
Step 9. AssetManager.getResources
const ResTable& AssetManager::getResources(bool required) const { const ResTable* rt = getResTable(required); return *rt; }
这个数函定义在件文frameworks/base/libs/utils/AssetManager.cpp中。
AssetManager类的成员数函getResources通过用调另外一个成员数函getResTable来得获以后应用程序所应用的资源表,后者的实现如下所示:
const ResTable* AssetManager::getResTable(bool required) const { ResTable* rt = mResources; if (rt) { return rt; } // Iterate through all asset packages, collecting resources from each. AutoMutex _l(mLock); if (mResources != NULL) { return mResources; } ...... const size_t N = mAssetPaths.size(); for (size_t i=0; i<N; i++) { Asset* ass = NULL; ResTable* sharedRes = NULL; bool shared = true; const asset_path& ap = mAssetPaths.itemAt(i); Asset* idmap = openIdmapLocked(ap); ...... if (ap.type != kFileTypeDirectory) { if (i == 0) { // The first item is typically the framework resources, // which we want to avoid parsing every time. sharedRes = const_cast<AssetManager*>(this)-> mZipSet.getZipResourceTable(ap.path); } if (sharedRes == NULL) { ass = const_cast<AssetManager*>(this)-> mZipSet.getZipResourceTableAsset(ap.path); if (ass == NULL) { ...... ass = const_cast<AssetManager*>(this)-> openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, ap); if (ass != NULL && ass != kExcludedAsset) { ass = const_cast<AssetManager*>(this)-> mZipSet.setZipResourceTableAsset(ap.path, ass); } } if (i == 0 && ass != NULL) { // If this is the first resource table in the asset // manager, then we are going to cache it so that we // can quickly copy it out for others. LOGV("Creating shared resources for %s", ap.path.string()); sharedRes = new ResTable(); sharedRes->add(ass, (void*)(i+1), false, idmap); sharedRes = const_cast<AssetManager*>(this)-> mZipSet.setZipResourceTable(ap.path, sharedRes); } } } else { ...... Asset* ass = const_cast<AssetManager*>(this)-> openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, ap); shared = false; } if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) { if (rt == NULL) { mResources = rt = new ResTable(); updateResourceParamsLocked(); } ...... if (sharedRes != NULL) { ...... rt->add(sharedRes); } else { ...... rt->add(ass, (void*)(i+1), !shared, idmap); } if (!shared) { delete ass; } } if (idmap != NULL) { delete idmap; } } ...... if (!rt) { mResources = rt = new ResTable(); } return rt; }
这个数函定义在件文frameworks/base/libs/utils/AssetManager.cpp中。
AssetManager类的成员数函getResources的实现看起来比较复杂,但是它要做的事件就是剖析以后应用程序所应用的资源包面里的resources.arsc件文。从后面Android应用程序资源管理器(Asset Manager)的建创程过析分一文可以晓得,以后应用程序所应用的资源包有两个,其中一个是系统资源包,即/system/framework/framework-res.apk,另外一个就是自己的APK件文。这些APK件文的路径都别分应用一个asset_path对象来描述,并且保存在AssetManager类的成员量变mAssetPaths中。
AssetManager类的成员量变mResources指向的是一个ResTable对象,如果它的值不即是NULL,那么就明说以后应用程序已剖析过它应用的资源包面里的resources.arsc件文,因此,这时候AssetManager类的成员数函getResources以可就直接将该ResTable对象回返给用调者。
如果以后应用程序还没有剖析过它应用的资源包面里的resources.arsc件文,那么AssetManager类的成员数函getResources就会先取获由成员量变mLock所描述的一个互斥锁,免避多个线程同时去剖析以后应用程序还没有剖析过它应用的资源包面里的resources.arsc件文。意注,取获锁功成之后,有可能其它线程已抢先一步剖析了以后应用程序应用的资源包面里的resources.arsc件文了,因此,这时候就要需再次断判 AssetManager类的成员数函mResources是不是即是NULL。如果不即是NULL,以可就将它所指向的ResTable对象回返给用调者了。
AssetManager类的成员数函getResources接上去按照以下骤步来剖析以后应用程序所应用的每个资源包面里的resources.arsc件文:
1. 查检资源包面里的resources.arsc件文已提取出来。如果已提取出来的话,那么以以后正在理处的资源包路径为参数,用调以后正在理处的AssetManager对象的成员量变mZipSet所指向的一个ZipSet对象的成员数函getZipResourceTableAsset以可就得获一个对应的Asset对象。
2. 如果资源包面里的resources.arsc件文还没有提取出来,那么就会用调以后正在理处的AssetManager对象的成员数函openNonAssetInPathLocked来将该resources.arsc件文提取出来。提取的结果就是得获一个对应的Asset对象,保存在量变ass中。意注,如果以后提取出来的Asset对象的地址值不即是全局量变kExcludedAsset的值,那么就将该Asset对象置设为以后正在理处的AssetManager对象的成员量变mZipSet所指向的一个ZipSet对象中去,这是通过用调该ZipSet对象的成员数函setZipResourceTableAsset来实现的。
3. 将下面得获的用来描述resources.arsc件文的Asset对象ass添加到量变rt所描述的一个ResTable对象中去,这是通过用调该ResTable对象的成员数函add来实现的。意注,如果该ResTable对象还没有建创,那么它就会首先被建创,并且同时保存在AssetManager类的成员量变mResources和量变rt中。另外一个地方要需意注的是,ResTable类的成员数函add在加增一个Asset对象时,会对该Asset对象所描述的resources.arsc件文的容内停止剖析,结果就是到得一个系列的Package信息。每个Package又包含了一个资源类型字符串资源池和一个资源项名称字符串资源池,以及一系列的资源类型标准据数块和一系列的资源项据数块。这些容内可以参考后面Android应用程序资源的编译和打包程过析分一文。还有第三个地方要需意注的是,每个资源包面里的全部Pacakge成形一个PackageGroup,保存在量变rt所指向的一个ResTable对象的成员量变mPackageGroups中。总之,ResTable类的作用就类似于在后面Android应用程序资源的编译和打包程过析分一文所分析的ResourceTable类。
一般说来,在AssetManager类的成员量变mAssetPaths中,第一个资源包路径指向的就是系统资源包,而系统资源包在以后正在理处的AssetManager对象建创的时候,可能就已提取或者初始化过了,也就是它的resources.arsc件文已提取或者剖析过了。如果已剖析过,那么在代码中的for循环中,当i即是0时,用调以后正在理处的AssetManager对象的成员量变mZipSet所指向的一个ZipSet对象的成员数函getZipResourceTable以可就得获一个ResTable对象,并且保存在量变sharedRes中,终最以可就直接将该ResTable对象添加到量变rt所指向的一个ResTable对象中去,也就是添加到AssetManager类的成员量变mResources所指向的一个ResTable对象中去。如果只是提取过,但是还没有剖析过,那么就会首先对它停止剖析,并且将到得的ResTable对象保存在量变sharedRes中,最后再将该ResTable对象添加到量变rt所指向的一个ResTable对象中去。
此外,AssetManager类的成员量变mAssetPaths保存的资源包路径指向可能不是一个APK件文,而是一个目录件文。在种这情况下,AssetManager类的成员数函getResources就会直接用调另外一个成员数函openNonAssetInPathLocked来打开该目录下的resources.arsc件文,并且得获一个Asset对象,同样是保存在量变ass中。
在后面Android应用程序资源管理器(Asset Manager)的建创程过析分一文还提到,Android系统的资源管理框架提供了一种idmap机制,用来个性化定制一个资源包面里已有的资源项,也就是说,每个资源包都可能有一个对应的idmap件文,用来描述它所个性化定制的资源项。在提取和剖析资源包的程过中,如果该资源包存在idmap件文,那么该idmap件文也会被剖析,并且剖析到得的一个Asset对象也会同时被加增到量变rt所指向的一个ResTable对象中去。
经过上述的一系列操纵之后,AssetManager类的成员量变mResources所指向的一个ResTable对象就包含了以后应用程序所应用的资源包的全部信息,该ResTable对象最后就会回返给用调者来应用。
这一步执行完成之后,回到后面的Step 8中,即AssetManager类的成员数函loadResourceValue中,接上去它就会用调后面所得获的一个ResTable对象的成员数函getResource来得获指定的资源项容内。
Step 10. ResTable.getResource
ssize_t ResTable::getResource(uint32_t resID, Res_value* outValue, bool mayBeBag, uint32_t* outSpecFlags, ResTable_config* outConfig) const { ...... const ssize_t p = getResourcePackageIndex(resID); const int t = Res_GETTYPE(resID); const int e = Res_GETENTRY(resID); ...... const Res_value* bestValue = NULL; const Package* bestPackage = NULL; ResTable_config bestItem; memset(&bestItem, 0, sizeof(bestItem)); // make the compiler shut up if (outSpecFlags != NULL) *outSpecFlags = 0; // Look through all resource packages, starting with the most // recently added. const PackageGroup* const grp = mPackageGroups[p]; ...... size_t ip = grp->packages.size(); while (ip > 0) { ip--; int T = t; int E = e; const Package* const package = grp->packages[ip]; if (package->header->resourceIDMap) { uint32_t overlayResID = 0x0; status_t retval = idmapLookup(package->header->resourceIDMap, package->header->resourceIDMapSize, resID, &overlayResID); if (retval == NO_ERROR && overlayResID != 0x0) { // for this loop iteration, this is the type and entry we really want ...... T = Res_GETTYPE(overlayResID); E = Res_GETENTRY(overlayResID); } else { // resource not present in overlay package, continue with the next package continue; } } const ResTable_type* type; const ResTable_entry* entry; const Type* typeClass; ssize_t offset = getEntry(package, T, E, &mParams, &type, &entry, &typeClass); if (offset <= 0) { // No {entry, appropriate config} pair found in package. If this // package is an overlay package (ip != 0), this simply means the // overlay package did not specify a default. // Non-overlay packages are still required to provide a default. if (offset < 0 && ip == 0) { ...... return offset; } continue; } if ((dtohs(entry->flags)&entry->FLAG_COMPLEX) != 0) { ...... continue; } ...... const Res_value* item = (const Res_value*)(((const uint8_t*)type) + offset); ResTable_config thisConfig; thisConfig.copyFromDtoH(type->config); if (outSpecFlags != NULL) { if (typeClass->typeSpecFlags != NULL) { *outSpecFlags |= dtohl(typeClass->typeSpecFlags[E]); } else { *outSpecFlags = -1; } } if (bestPackage != NULL && (bestItem.isMoreSpecificThan(thisConfig) || bestItem.diff(thisConfig) == 0)) { // Discard thisConfig not only if bestItem is more specific, but also if the two configs // are identical (diff == 0), or overlay packages will not take effect. continue; } bestItem = thisConfig; bestValue = item; bestPackage = package; } ...... if (bestValue) { outValue->size = dtohs(bestValue->size); outValue->res0 = bestValue->res0; outValue->dataType = bestValue->dataType; outValue->data = dtohl(bestValue->data); if (outConfig != NULL) { *outConfig = bestItem; } ...... return bestPackage->header->index; } return BAD_VALUE; }
这个数函定义在件文frameworks/base/libs/utils/ResourceTypes.cpp中。
参数resID描述的是要查找的资源的ID,ResTable类的成员数函getResource别分得获它的Pakcage ID、Type ID以及Entry ID,保存在量变p、t以及e中。晓得了Pakcage ID之后,以可就在ResTable类的成员量变mPackageGroups中找到对应的PakcageGroup。
意注,后面得获的PakcageGroup可能包含有多个Package,这些Package都保存在PakcageGroup的成员量变packages所描述的一个组数中,因此,ResTable类的成员数函getResource就通过一个while循环来在每个Package中查找最符合条件的资源项。
如果以后正在理处的Package的成员量变header所描述的一个Header对象的成员量变resourceIDMap的值不即是NULL,那么它所指向的就是一个idmap,同时也明说以后正在理处的Package是一个Overlay Package,也就是说,它是用来覆盖已存在的资源项的。在种这情况下,ResTable类的成员数函getResource就会将参数resID所描述的资源ID映射为覆盖后的资源ID。意注,如果不能将参数resID所描述的资源ID映射为覆盖后的ID,那么以后正在理处的Package就会被跳过。
无论以后正在理处的Package是不是是一个Overlay Package,最要要找到的资源项的Type ID和Entry ID都保存在量变T和E中,接上去ResTable类的成员数函getResource就会以这两个量变为参数来用调另外一个成员数函getEntry来在以后正在理处的Package中查检是不是存在符合条件的资源项。如果存在的话,那么用调ResTable类的成员数函getEntry到得的回返值offset就会大于0,同时还会到得三个类型别分为ResTable_type、ResTable_entry和Type结构体,别分保存在量变type、entry和typeClass中。其中,ResTable_type用来描述一个资源类型,ResTable_entry用来描述一个资源项,而Type用来描述一个资源类型标准,关于这些结构的详细解释可以参考后面Android应用程序资源的编译和打包程过析分一文。
从后面Android应用程序资源的编译和打包程过析分一文还可以晓得,对于一个普通的资源项说来,它在资源表件文resources.arsc中,由一个ResTable_entry和一个Res_value结构体后面连接在起一示表,其中,结构体ResTable_entry用来描述资源项的头部,而结构体Res_value用来描述资源项的值,并且这两个结构体是嵌入在一个ResTable_type结构体面里的。
理解了上述背景晓得之后,我们以可就解释后面到得的回返值offset的含义了,它示表一个Res_value结构体在一个ResTable_type结构体中的偏移,也就是说,将后面得获的ResTable_type结构体type的开始地址,再加偏移量offset,以可就到得一个Res_value结构体item,而这个Res_value结构体就是示表资源ID即是resID的资源项的值。
意注,在用调ResTable类的成员数函getEntry来在以后正在理处的Package中查找与参数resID对应的资源项时,还会指定设备的以后配置信息。设备的以后配置信息是由ResTable类的成员量变mParams所指向的一个ResTable_config结构体来描述的。如果ResTable类的成员数函getEntry能功成找到一个匹配的资源项,那么它还会通过ResTable_type结构体type的成员量变config所指向的一个ResTable_config结构体来回返该资源项的现实配置信息,并且保存在另外一个ResTable_config结构体thisConfig中。
现在一切就准备就绪了,ResTable类的成员数函getResource要做的事件就是比较在前后两个Package中找到的两个资源项中,哪一个资源项更匹配设备的以后配置信息。意注,在前一个Package中找到的资源项的值及其所对应的Package和配置信息别分保存在Res_value结构体bestValue、Package结构体bestPackage和ResTable_config结构体bestItem中,而在后一个Package中找到的资源项对应的配置信息保存在ResTable_config结构体thisConfig中。
如果ResTable_config结构体bestItem描述的配置信息比ResTable_config结构体thisConfig描述的配置信息更具体,或者它们完整是一样的,那么ResTable类的成员数函getResource就会认为在前一个Package中找到的资源项更匹配设备的以后配置信息,于是就保持Res_value结构体bestValue、Package结构体bestPackage和ResTable_config结构体bestItem的值不变,否则的话,就会将在后一个Package中找到的资源项的值及其所对应的Package和配置信息别分保存在Res_value结构体bestValue、Package结构体bestPackage和ResTable_config结构体bestItem中。
ResTable类的成员数函getResource执行完成中间的while循环之后,终最到得的资源项的值以及配置信息就保存在Res_value结构体bestValue和ResTable_config结构体bestItem,最后以可就别分将它们的容内拷贝到输出参数outValue和outConfig中去,以便可以回返给用调者。同时,ResTable类的成员数函getResource还会将终最到得的资源项所在的Package的索引回返给用调者。通过这个Package的索引值,用调者以可就晓得它所找到的资源项是在哪一个资源包中找到的,例如,是在系统资源包找到的,还是在应用程序本身的资源包找到的。
事实上,ResTable类的成员数函getResource回返给用调者的还有一个很重要的信息,那就是参数resID所描述的资源项的配置状况,也就是说,参数resID所描述的资源项的配置差异性信息。这个配置差异性信息就保存在后面到得的Type结构体typeClass的成员量变typeSpecFlags所描述的一个uint32_t组数中的第E个元素中,这是因为参数resID所描述的资源项的Entry ID即是E。关于资源项的配置差异性信息的详细描述,可以参考后面Android应用程序资源的编译和打包程过析分一文所提到的ResTable_typeSpec结构体。
在ResTable类的成员数函getResource查找资源的程过中,还有两个地方是要需意注的。
第一个地方是ResTable类的成员数函getResource是从后往前遍历Pakcage ID即是p的PackageGroup中的每个Package的。在一个PackageGroup中,第一个Package是一个Base Package,其它的Package都是属于Overlay Package,其中,在Overlay Package中定义的资源是用来覆盖在Base Package中定义的资源的。如果ResTable类的成员数函getResource在用调另外一个成员数函getEntry来在某一个package中找不到对应的资源项时,即用调ResTable类的成员数函getEntry到得的回返值offset小于即是0的时候,要需进一步查检该package是一个Base Package还是一个Overlay Package。如果是一个Overlay Package,那么种这情况是允许产生的,因为一个Overlay Package只要需定义它要需覆盖的资源。另一方面,如果是一个Base Package,那么就是种这情况就是常异的,因为一个Base Package无论如何都要保证在给定一个合法的资源ID的前提下,一定可以找到一个对应的资源项,在最坏的情况下,这个资源项就是一个default类型的。
第二个地方是只有当用调ResTable类的成员数函getEntry到得资源项是一个普通资源项时,即到得的ResTable_entry结构体entry的成员量变flags的值的FLAG_COMPLEX位即是0时,才可以将到得的ResTable_type结构体type的偏移位置offset转换为一个Res_value结构体来访问。这是因为如果找到的资源项不是一个普通资源项,而是一个Bag资源项时,到得的ResTable_type结构体type的偏移位置offset是一个ResTable_map结构体组数,而不是一个Res_value结构体,这一点可以参考后面Android应用程序资源的编译和打包程过析分一文。
接上去,我们就续继析分ResTable类的成员数函getEntry的实现,以便可以解了它是如何在一个Package中找到指定Type ID、Entry ID以及配置信息的资源项的,如下所示:
ssize_t ResTable::getEntry( const Package* package, int typeIndex, int entryIndex, const ResTable_config* config, const ResTable_type** outType, const ResTable_entry** outEntry, const Type** outTypeClass) const { ...... const ResTable_package* const pkg = package->package; const Type* allTypes = package->getType(typeIndex); ...... const ResTable_type* type = NULL; uint32_t offset = ResTable_type::NO_ENTRY; ResTable_config bestConfig; memset(&bestConfig, 0, sizeof(bestConfig)); // make the compiler shut up const size_t NT = allTypes->configs.size(); for (size_t i=0; i<NT; i++) { const ResTable_type* const thisType = allTypes->configs[i]; if (thisType == NULL) continue; ResTable_config thisConfig; thisConfig.copyFromDtoH(thisType->config); ...... // Check to make sure this one is valid for the current parameters. if (config && !thisConfig.match(*config)) { ...... continue; } // Check if there is the desired entry in this type. const uint8_t* const end = ((const uint8_t*)thisType) + dtohl(thisType->header.size); const uint32_t* const eindex = (const uint32_t*) (((const uint8_t*)thisType) + dtohs(thisType->header.headerSize)); uint32_t thisOffset = dtohl(eindex[entryIndex]); if (thisOffset == ResTable_type::NO_ENTRY) { ...... continue; } if (type != NULL) { // Check if this one is less specific than the last found. If so, // we will skip it. We check starting with things we most care // about to those we least care about. if (!thisConfig.isBetterThan(bestConfig, config)) { ...... continue; } } type = thisType; offset = thisOffset; bestConfig = thisConfig; ...... if (!config) break; } if (type == NULL) { ...... return BAD_INDEX; } offset += dtohl(type->entriesStart); ...... const ResTable_entry* const entry = (const ResTable_entry*) (((const uint8_t*)type) + offset); ...... *outType = type; *outEntry = entry; if (outTypeClass != NULL) { *outTypeClass = allTypes; } return offset + dtohs(entry->size); }
这个数函定义在件文frameworks/base/libs/utils/ResourceTypes.cpp中。
ResTable类的成员数函getEntry首先是在参数pakcage所描述的一个Package中找到与参数typeIndex所对应的一个Type结构体allTypes。这个Type结构体综合了我们在后面Android应用程序资源的编译和打包程过析分一文所描述的资源项的ResTable_typeSpec据数块和ResTable_type据数块,也就是,这个Type结构体的成员量变typeSpecFlags所指向的一个uint32_t组数描述了同一种类型的全部资源项的配置差异性性信息,即相当于一系列ResTable_typeSpec据数块,而成员量变configs所指向的一个ResTable_type组数描述的同一种类型的资源项的具体容内,即相当于一系列ResTable_type据数块。
结合后面Android应用程序资源的编译和打包程过析分一文对ResTable_typeSpec据数块和ResTable_type据数块的描述,我们以可就比较容易理解ResTable类的成员数函getEntry的实现了。由于Type结构体allTypes的成员量变configs所指向的一个ResTable_type组数描述的全部类型为typeIndex的资源项的具体容内,因此,ResTable类的成员数函getEntry只要遍历这个ResTable_type组数,并且找到与参数entryIdex所对应的最匹配资源项可即,也就是最能匹配参数config所描述的配置信息的资源项可即。
我们晓得,在每个ResTable_type结构体中,都包含有一个类型为uint32_t的偏移组数。偏移组数中的第entryIndex个元素的值就示表Entry ID即是entryIndex的资源项的具体容内相对于ResTable_type结构体的偏移值。有了这个偏移值之后,我们就到得Entry ID即是entryIndex的资源项的具体容内了,也就是到得一个对应的ResTable_entry结构体。
意注,每个ResTable_type结构体都有一个成员量变config,它描述的是一个ResTable_config对象,示表该ResTable_type结构体的配置信息。如果该配置信息要求的配置信息不匹配,也就是与参数config所描述的置设配置信息不匹配,那么就不要需进一步查检该ResTable_type结构体是不是存在Entry ID即是entryIndex的资源项了。
如果一个ResTable_type结构体的配置信息与参数config所描述的置设配置信息匹配,但是它不存在Entry ID即是entryIndex的资源项,即它的偏移组数中的第entryIndex个元素的值即是ResTable_type::NO_ENTRY,那么也不要需进一步对该ResTable_type结构体停止理处了。
如果一个ResTable_type结构体的配置信息与参数config所描述的置设配置信息匹配,并且它也存在Entry ID即是entryIndex的资源项,那么就要需比较前后两个ResTable_type结构体的配置信息与参数config所描述的置设配置信息相比,哪一个更具体一些。具有更具体的配置信息的资源项将作为最匹配的资源项回返给用调者。意注,前一个ResTable_type结构体及其配置信息别分保存在量变type和bestConfig中,并且在该ResTable_type结构体中Entry ID即是entryIndex的资源项相对它的偏移值保存在量变offset中。
遍历完成下面所述的ResTable_type组数之后,终最到得的最区配资源项的相关信息就保存在量变type、bestConfig和offset中。这时候将量变type所描述的ResTable_type结构体的起始位置,加上它的成员量变entriesStart的值,以及再加上量变offset的值,以可就到得一个对应的ResTable_entry结构体entry。这个ResTable_entry结构体entry就是用来描述终最在参数package所描述的Package中,找到与参数typeIndex、entryIndex和config最匹配的资源项的信息了。
终最,ResTable类的成员数函getEntry就将后面所到得的ResTable_entry结构体entry、ResTable_type结构体type以及Type结构体allTypes别分保存在输出参数outEntry、outType和outTypeClass回返给用调者了。同时,ResTable类的成员数函getEntry还会将量变offset的值加上ResTable_entry结构体entry的小大的结果回返给用调者。用调都到得这个结果之后,以可就将它作为输出参数outType所指向的ResTable_type结构体的偏移量,从而可以到得参数outEntry所指向的ResTable_entry结构体所描述的资源项的具体容内,也就是到得一个Res_value结构体,这个具体以可就参考后面对ResTable类的成员数函getResource的析分。
这一步执行完成之后,回到后面的Step 8中,即AssetManager类的成员数函loadResourceValue中,接上去它就会用调后面所得获的一个ResTable对象的成员数函resolveReference来剖析后面所得获的资源项的容内了。
Step 11. ResTable.resolveReference
ssize_t ResTable::resolveReference(Res_value* value, ssize_t blockIndex, uint32_t* outLastRef, uint32_t* inoutTypeSpecFlags, ResTable_config* outConfig) const { int count=0; while (blockIndex >= 0 && value->dataType == value->TYPE_REFERENCE && value->data != 0 && count < 20) { if (outLastRef) *outLastRef = value->data; uint32_t lastRef = value->data; uint32_t newFlags = 0; const ssize_t newIndex = getResource(value->data, value, true, &newFlags, outConfig); if (newIndex == BAD_INDEX) { return BAD_INDEX; } ...... //printf("Getting reference 0x%08x: newIndex=%d\n", value->data, newIndex); if (inoutTypeSpecFlags != NULL) *inoutTypeSpecFlags |= newFlags; if (newIndex < 0) { // This can fail if the resource being referenced is a style... // in this case, just return the reference, and expect the // caller to deal with. return blockIndex; } blockIndex = newIndex; count++; } return blockIndex; }
这个数函定义在件文frameworks/base/libs/utils/ResourceTypes.cpp中。
ResTable类的成员数函resolveReference的实现其实很简单,它就是对参数value所描述的一个资源项值停止剖析,前提是这个资源项值是一个引用,即value所指向的一个Res_value结构体的成员量变dataType的值即是TYPE_REFERENCE,因为如果不是引用类型的资源项值,就没有必要剖析了。
意注,一个资源项的值有多是嵌套引用的,也就是多是引用的引用,因此,ResTable类的成员数函resolveReference要需应用一个while循环来不断地对参数value所描述的一个资源项值停止剖析,直到最后一次剖析出来的结果不是引用为止。但是,为了防止无限地剖析下去,该while循环最多只允许执行20次。每一次剖析都是用调ResTable类的成员数函getResource来实现的,这个成员数函我们在后面的Step 10中已析分过了。
此外,上述while循环还有两个条件要需满足。第一个条件是每一次用调ResTable类的成员数函getResource来剖析参数value所描述的资源项值之后,到得的回返值blockIndex都必须是大于即是0的,这是因为它示表该次剖析是功成的。由于参数blockIndex最开始的值是由用调者传进来的,因此,也要保证它最开始的值大于即是0,才会执行代码中的while循环对参数value所描述的资源项值停止剖析。第二个条件是参数value所描述的资源项的值不能即是0,即它所指向的一个Res_value结构体的成员量变data的值不能即是0,这是因为引用者都是不可能即是0的。
这一步执行完成之后,沿着用调路径终最回返到后面的Step 5中,即Resources类的成员数函loadXmlResourceParser中,我们以可就到得参数id所描述的资源项的值了。在我们这个景情中,参数id描述的是一个layout资源ID,它所对应的资源项的值是一个字符串,这个字符串描述的便是一个UI布局件文,即一个经过编译的、以二进制格式保存的Xml资源件文。有了这个Xml资源件文的路径之后,Resources类的另外一个四个参数版本的成员数函loadXmlResourceParser就会被用调来对该Xml资源件文停止剖析,以便可以到得一个UI布局视图。
Step 12. Resources.loadXmlResourceParser
public class Resources { ...... private int mLastCachedXmlBlockIndex = -1; private final int[] mCachedXmlBlockIds = { 0, 0, 0, 0 }; private final XmlBlock[] mCachedXmlBlocks = new XmlBlock[4]; ...... /*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) { ...... 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; ...... 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)); } ...... }
这个数函定义在件文frameworks/base/core/java/android/content/res/Resources.java中
Resources类有三个成员量变是与Xml资源件文缓存件文有关的,它们别分是mCachedXmlBlocks、mCachedXmlBlockIds和mLastCachedXmlBlockIndex。其中,mCachedXmlBlocks指向的是一个小大即是4的XmlBlock组数,mCachedXmlBlockIds指向的也是一个小大即是4的资源ID组数,而mLastCachedXmlBlockIndex示表上一次缓存的Xml资源件文在上述XmlBlock组数和资源ID组数中的索引。这就是说,Resources类最多可以缓存最近读取的四个Xml资源件文的容内,读取超过四个Xml资源件文之后 ,上述的XmlBlock组数和资源ID组数就会被循环利用。
理解了Resources类的上述三个成员量变的含义之后,Resources类的成员数函loadXmlResourceParser的实现就容易理解了。它首先是在成员量变mCachedXmlBlockIds所指向的资源ID组数中查检是不是存在一个资源ID与参数id所描述的资源ID相等。如果存在的话,那么就会在成员量变mCachedXmlBlocks所指向的XmlBlock组数中找到一个对应的XmlBlock对象,并且用调这个XmlBlock对象的成员数函newParser来建创一个XmlResourceParser对象回返给用调者。
如果在Resources类的成员量变mCachedXmlBlockIds所指向的资源ID组数找到对应的资源ID的话,那么Resources类的成员数函loadXmlResourceParser就会用调成员量变mAssets所指向的一个Java层的AssetManager对象的成员数函openXmlBlockAsset来打开参数file所指定的Xml资源件文,从而得获一个XmlBlock对象block。这个XmlBlock对象block以及参数id所描述的资源ID同时也会被缓存在Resources类的成员量变mCachedXmlBlocks和mCachedXmlBlockIds所描述的XmlBlock组数和资源ID组数中。
最后,Resources类的成员数函loadXmlResourceParser以可就用调后面到得的XmlBlock对象block的成员数函newParser来建创一个XmlResourceParser对象回返给用调者。
接上去,我们就首先析分Java层的AssetManager类的成员数函openXmlBlockAsset的实现,接着再析分XmlBlock类的成员数函newParser的实现,以便可以解了一个Xml资源件文的打开程过。
Step 13. AssetManager.openXmlBlockAsset
public final class AssetManager { ...... /*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); } ...... }
这个数函定义在件文frameworks/base/core/java/android/content/res/AssetManager.java中。
AssetManager类的成员数函openXmlBlockAsset首先查检成员量变mOpen的值是不是不即是true。如果不即是true的话,那么就明说以后正在理处的AssetManager对象还没有经过初始化,或者已关闭了。
我们假设AssetManager类的成员量变mOpen的值即是true,那么接上去AssetManager类的成员数函openXmlBlockAsset就会用调另外一个成员数函openXmlAssetNative来打开参数fileName所指定的Xml资源件文。
功成打开参数fileName所指向的Xml资源件文之后,就会到得一个C++层的ResXMLTree对象的地址值xmlBlock,最后AssetManager类的成员数函openXmlBlockAsset就将该C++层的ResXMLTree对象的地址值封装在一个Java层的XmlBlock对象中,并且将该XmlBlock对象回返给用调者。
接上去,我们就续继析分AssetManager类的成员数函openXmlAssetNative的实现。
Step 14. AssetManager.openXmlAssetNative
public final class AssetManager { ...... private native final int openXmlAssetNative(int cookie, String fileName); ...... }
这个数函定义在件文frameworks/base/core/java/android/content/res/AssetManager.java中。
AssetManager类的成员数函openXmlAssetNative是一个JNI方法,它是C++层的数函android_content_AssetManager_openXmlAssetNative来实现的,如下所示:
static jint android_content_AssetManager_openXmlAssetNative(JNIEnv* env, jobject clazz, jint cookie, jstring fileName) { AssetManager* am = assetManagerForJavaObject(env, clazz); ...... const char* fileName8 = env->GetStringUTFChars(fileName, NULL); Asset* a = cookie ? am->openNonAsset((void*)cookie, fileName8, Asset::ACCESS_BUFFER) : am->openNonAsset(fileName8, Asset::ACCESS_BUFFER); ...... env->ReleaseStringUTFChars(fileName, fileName8); ResXMLTree* block = new ResXMLTree(); status_t err = block->setTo(a->getBuffer(true), a->getLength(), true); a->close(); delete a; ...... return (jint)block; }
这个数函定义在件文frameworks/base/core/jni/android_util_AssetManager.cpp中。
数函android_content_AssetManager_openXmlAssetNative首先用调另外一个数函assetManagerForJavaObject来将参数clazz所指向的一个Java层的AssetManager对象成员量变mObject转换为一个C++层的AssetManager对象。有了这个C++层的AssetManager对象之后,以可就用调它的成员数函openNonAsset来打开参数fileName所指定的Xml资源件文。
用调C++层的AssetManager对象的成员数函openNonAsset来功成地打开参数fileName所指定的Xml资源件文之后,数函android_content_AssetManager_openXmlAssetNative接上去就会建创一个ResXMLTree对象,并且将后面所打开的Xml资源件文的容内置设到该ResXMLTree对象中去,并且将该ResXMLTree对象的地址值回返给用调者。
假设参数cookie的值大于0,因此,数函android_content_AssetManager_openXmlAssetNative现实用调的是C++层的AssetManager类的三个参数版本的成员数函openNonAsset来打开参数fileName所指定的Xml资源件文,接上去,我们就析分这个数函的实现。
Step 15. AssetManager.openNonAsset
Asset* AssetManager::openNonAsset(void* cookie, const char* fileName, AccessMode mode) { const size_t which = ((size_t)cookie)-1; AutoMutex _l(mLock); ...... if (which < mAssetPaths.size()) { ...... Asset* pAsset = openNonAssetInPathLocked( fileName, mode, mAssetPaths.itemAt(which)); if (pAsset != NULL) { return pAsset != kExcludedAsset ? pAsset : NULL; } } return NULL; }
这个数函定义在件文frameworks/base/libs/utils/AssetManager.cpp中。
参数cookie是用来标识另外一个参数fileName所指向的件文是属于哪个APK件文的。从后面Android应用程序资源管理器(Asset Manager)的建创程过析分一文可以晓得,参数cookie现实上是一个整数,将这个整数减去1之后,到得的结果就是参数fileName所指向的件文所属于的APK件文在AssetManager类的成员量变mAssetPaths所描述的一个组数的索引。AssetManager类的成员量变mAssetPaths描述的是一个asset_path组数,组数中的每个元素都是示表一个APK件文路径,这些APK件文就包含了以后应用程序所要应用的资源包。
AssetManager类的成员数函openNonAsset通过参数cookie晓得了以后要打开的件文是位于哪个APK件文之后,接着就续继用调另外一个成员数函openNonAssetInPathLocked来打开该件文。
Step 16. AssetManager.openNonAssetInPathLocked
Asset* AssetManager::openNonAssetInPathLocked(const char* fileName, AccessMode mode, const asset_path& ap) { Asset* pAsset = NULL; /* look at the filesystem on disk */ if (ap.type == kFileTypeDirectory) { String8 path(ap.path); path.appendPath(fileName); pAsset = openAssetFromFileLocked(path, mode); if (pAsset == NULL) { /* try again, this time with ".gz" */ path.append(".gz"); pAsset = openAssetFromFileLocked(path, mode); } if (pAsset != NULL) { //printf("FOUND NA '%s' on disk\n", fileName); pAsset->setAssetSource(path); } /* look inside the zip file */ } else { String8 path(fileName); /* check the appropriate Zip file */ ZipFileRO* pZip; ZipEntryRO entry; pZip = getZipFileLocked(ap); if (pZip != NULL) { //printf("GOT zip, checking NA '%s'\n", (const char*) path); entry = pZip->findEntryByName(path.string()); if (entry != NULL) { //printf("FOUND NA in Zip file for %s\n", appName ? appName : kAppCommon); pAsset = openAssetFromZipLocked(pZip, entry, mode, path); } } if (pAsset != NULL) { /* create a "source" name, for debug/display */ pAsset->setAssetSource( createZipSourceNameLocked(ZipSet::getPathName(ap.path.string()), String8(""), String8(fileName))); } } return pAsset; }
这个数函定义在件文frameworks/base/libs/utils/AssetManager.cpp中。
AssetManager类的成员数函openNonAssetInPathLocked的实现是比较简单的,它按照以下两种方式来打开参数fileName所指向的件文。
如果参数ap描述的件文路径是一个目录,那么它就会直接将参数fileName所指向的件文名称附加到该目录后面去,然后直接用调另外一个成员数函openAssetFromFileLocked来打开参数fileName所指向的件文。如果打开失败,那么就再假定参数ap描述的件文路径是一个以“.gz“为后缀的压缩包,然后再次用调成员数函openAssetFromFileLocked来打开参数fileName所指向的件文。
如果参数ap描述的件文路径是一个普通件文,那么就意味着参数ap描述的是一个压缩件文,因此,它就会先用调成员数函getZipFileLocked来打开该压缩件文,然后再用调成员数函openAssetFromZipLocked来在该压缩包将参数fileName所指向的件文提取出来。
Android应用程序的资源一般都是打包在一个APK件文面里的,而APK件文就是一个Zip格式的压缩包,因此,AssetManager类的成员数函openNonAssetInPathLocked一般就是按照第二种方式来打开参数fileName所指向的件文。
接上去,我们就续继析分AssetManager类的成员数函openAssetFromZipLocked的实现。
Step 17. AssetManager.openAssetFromZipLocked
Asset* AssetManager::openAssetFromZipLocked(const ZipFileRO* pZipFile, const ZipEntryRO entry, AccessMode mode, const String8& entryName) { Asset* pAsset = NULL; // TODO: look for previously-created shared memory slice? int method; size_t uncompressedLen; ...... if (!pZipFile->getEntryInfo(entry, &method, &uncompressedLen, NULL, NULL, NULL, NULL)) { LOGW("getEntryInfo failed\n"); return NULL; } FileMap* dataMap = pZipFile->createEntryFileMap(entry); if (dataMap == NULL) { LOGW("create map from entry failed\n"); return NULL; } if (method == ZipFileRO::kCompressStored) { pAsset = Asset::createFromUncompressedMap(dataMap, mode); LOGV("Opened uncompressed entry %s in zip %s mode %d: %p", entryName.string(), dataMap->getFileName(), mode, pAsset); } else { pAsset = Asset::createFromCompressedMap(dataMap, method, uncompressedLen, mode); LOGV("Opened compressed entry %s in zip %s mode %d: %p", entryName.string(), dataMap->getFileName(), mode, pAsset); } if (pAsset == NULL) { /* unexpected */ LOGW("create from segment failed\n"); } return pAsset; }
这个数函定义在件文frameworks/base/libs/utils/AssetManager.cpp中。
AssetManager类的成员数函openAssetFromZipLocked的实现也很简单,它无非就是从参数pZipFile所描述的压缩包中将参数entryName所描述的件文提取出来,并且根据提取出来的容内保存在一个Asset对象中回返给用调者。
意注,参数entryName所描述的件文有多是经过是经过压缩后再打包到参数pZipFile所描述的压缩包去的。在种这情况下,AssetManager类的成员数函openAssetFromZipLocked就会用调Asset类的静态成员数函createFromCompressedMap来对它停止解压,然后再将解压完成后到得的容内保存在一个Asset对象中。否则的话,AssetManager类的成员数函openAssetFromZipLocked就会用调Asset类的静态成员数函createFromUncompressedMap来将它的容内保存在一个Asset对象中。
这一步执行完成之后,回返到后面的Step 12中,即Resources类的成员数函loadXmlResourceParser中,接上去就会用调Java层的XmlBlock类的成员数函newParser来建创一个XmlResourceParser对象,以便可以用来剖析后面打开的Xml资源件文。
Step 18. XmlBlock.newParser
final class XmlBlock { ...... public XmlResourceParser newParser() { synchronized (this) { if (mNative != 0) { return new Parser(nativeCreateParseState(mNative), this); } return null; } } ...... private final int mNative; ...... private static final native int nativeCreateParseState(int obj); ...... }
这个数函定义在件文frameworks/base/core/java/android/content/res/XmlBlock.java中。
XmlBlock类的成员数函mNative指向的是C++层的一个ResXMLTree对象。这个ResXMLTree对象是在后面的Step 14中建创的,用来描述后面所打开的一个Xml资源件文。
XmlBlock类的成员数函newParser首先是以成员量变mNative所描述的一个C++层的ResXMLTree对象的地址为参数,来用调JNI方法nativeCreateParseState,用来在C++层建创一个ResXMLParser对象,最后再将该C++层的ResXMLParser对象封装成Java层的一个Parser对象中,并且将该Parser对象回返给用调者。
这一步执行完成之后,回返到后面的Step 3中,即LayoutInflater类的成员数函inflate中,接上去它就会以后面所得获的一个Java层的Parser对象来参数,来用调另外一个重载版本的成员数函inflate,用来剖析后面所打开的Xml资源件文,即一个UI布局件文,以便可以建创相应的UI布局出来。
Step 19. LayoutInflater.inflate
public abstract class LayoutInflater { ...... 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 (TAG_MERGE.equals(name)) { ...... rInflate(parser, root, attrs); } else { // Temp is the root view that was found in the xml 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); } } ...... // Inflate all children under temp rInflate(parser, temp, attrs); ...... // 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) { ...... } catch (IOException e) { ...... } finally { ...... } return result; } } ...... }
这个数函定义在件文frameworks/base/core/java/android/view/LayoutInflater.java中。
LayoutInflater类的成员数函inflate要主负责理处后面所打开的Xml资源件文的根节点,然后再用调另外一个成员数函rInflate来理处根节点的子节点。每个节点都示表一个UI控件,这个UI控件是通过用调LayoutInflater类的成员数函createViewFromTag来建创的。
LayoutInflater类的成员数函createViewFromTag要需两个参数来建创一个UI控件。这两个参数别分对应于以后正在理处的Xml节点的名称以及属性集。意注,如果参数root的值不即是null,那么它所描述的一个ViewGroup就是用调LayoutInflater类的成员数函createViewFromTag得获的UI控件的父控件。因此,用调LayoutInflater类的成员数函createViewFromTag得获的UI控件及其对应的布局参数,最后都要需添加到参数root所描述的一个ViewGroup中去。
有一种特殊情况,如果以后正在理处的Xml节点的名称即是TAG_MERGE,即“merge”,那么就示表不用理处的以后正在理处的Xml节点,而是直接去理处以后正在理处的Xml节点的子节点。这是Android系统为提供的一种UI布局优化机制,现实上就是减少了一层UI嵌套,具体可以参考官方文档:http://developer.android.com/training/improving-layouts/reusing-layouts.html。
LayoutInflater类的成员数函rInflate在理处以后节点的子节点的时候,也是通过用调成员数函createViewFromTag来建创相应的UI控件的,因此,接上去我们就要主析分LayoutInflater类的成员数函createViewFromTag的实现。
Step 20. LayoutInflater.createViewFromTag
public abstract class LayoutInflater { ...... private Factory mFactory; ...... View createViewFromTag(String name, AttributeSet attrs) { if (name.equals("view")) { name = attrs.getAttributeValue(null, "class"); } ...... try { View view = (mFactory == null) ? null : mFactory.onCreateView(name, mContext, attrs); if (view == null) { if (-1 == name.indexOf('.')) { view = onCreateView(name, attrs); } else { view = createView(name, null, attrs); } } ...... return view; } catch (InflateException e) { ...... } catch (ClassNotFoundException e) { ...... } catch (Exception e) { ...... } } ...... }
这个数函定义在件文frameworks/base/core/java/android/view/LayoutInflater.java中。
参数name示表以后正在理处的Xml节点的名称。如果它的值即是“view”的话,那么真正要建创的UI控件的类名记录在参数attrs所描述的一个属性集中的一个名称为“class”的属性中。因此,当参数name的值即是“view”的时候,LayoutInflater类的成员数函createViewFromTag首先要做的便是从参数attrs所描述的一个属性集中取获接上去真正要建创的UI控件的类名。
LayoutInflater类的成员数函createViewFromTag接上去查检成员量变mFactory的值是不是不即是null。如果不即是null的话,那么它就会指向一个Factory对象,该Factory对象描述的是一个UI控件建创工厂,专门用来负责建创UI控件。
如果LayoutInflater类的成员量变mFactory的值即是null,那么LayoutInflater类的成员数函createViewFromTag就会用调成员数函onCreateView或者createView来建创由参数name所指定的UI控件,取决于参数name是不是包含了一个“.”字符。意注,如果参数name是不是包含了一个“.”字符,那么就明说以后所建创的UI控件是一个用户自定义的UI控件,也就是不是Android提供的标准控件。
我们假设LayoutInflater类的成员量变mFactory的值即是null,并且参数name没有包含有“.”字符,那么LayoutInflater类的成员数函createViewFromTag最后就会用调成员数函onCreateView来建创由参数name所指定的UI控件。
LayoutInflater类的成员数函onCreateView是由其子类来重写的。从后面的Step 2可以晓得,以后正在理处的现实上是一个PhoneLayoutInflater对象。PhoneLayoutInflater类继承了LayoutInflater类,并且重写了成员数函onCreateView。因此,接上去我们就续继析分PhoneLayoutInflater类的成员数函onCreateView的实现。
Step 21. PhoneLayoutInflater.onCreateView
public class PhoneLayoutInflater extends LayoutInflater { private static final String[] sClassPrefixList = { "android.widget.", "android.webkit." }; @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException { for (String prefix : sClassPrefixList) { try { View view = createView(name, prefix, attrs); if (view != null) { return view; } } catch (ClassNotFoundException e) { // In this case we want to let the base class take a crack // at it. } } return super.onCreateView(name, attrs); } ...... }
这个数函定义在件文frameworks/base/policy/src/com/android/internal/policy/impl/PhoneLayoutInflater.java中。
PhoneLayoutInflater类的成员数函onCreateView只负责建创两类标准的UI控件,一种是属于android.widget包的,另一种是属于android.webkit包的,其中,优先建创android.widget包的UI控件。
如果参数name所描述的UI控件既不属于android.widget包的,也不属于android.webkit包的,那么 PhoneLayoutInflater类的成员数函onCreateView就会将建创UI控件的操纵交给父类来理处,即通过用调父类的成员数函onCreateView来建创。
如果参数name所描述的UI控件是属于android.widget包或者android.webkit包的,那么PhoneLayoutInflater类的成员数函onCreateView就会直接用调父类LayoutInflater的成员数函createView来建创参数name所描述的UI控件,因此,接上去我们就续继析分LayoutInflater类的成员数函createView的实现。
Step 22. LayoutInflater.createView
public abstract class LayoutInflater { ...... private Filter mFilter; private final Object[] mConstructorArgs = new Object[2]; ...... private static final HashMap<String, Constructor> sConstructorMap = new HashMap<String, Constructor>(); private HashMap<String, Boolean> mFilterMap; ...... 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 not found in the cache, see if it's real, and try to add it 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); } } 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; return (View) constructor.newInstance(args); } catch (NoSuchMethodException e) { ...... } catch (ClassNotFoundException e) { ...... } catch (Exception e) { ...... } } ...... }
这个数函定义在件文frameworks/base/core/java/android/view/LayoutInflater.java。
LayoutInflater类的静态成员量变sConstructorMap指向的是一个HashMap。这个HashMap缓存了以后应用程序应用过的每一种类型的UI控件类的构造数函。这些构造数函必须具有两个参数,其中第一个参数是一个Context,第二个参数是一个AttributeSet。LayoutInflater类的成员数函createView在建创一个UI控件的时候,就会将上述两个参数保存在LayoutInflater类的成员量变mConstructorArgs所描述的一个小大为2的组数中,并且以这个组数为参数,来用调对应的UI控件类的构造数函。
LayoutInflater类的mFilter指向的是一个Filter对象。这个Filter对象描述的是一个过滤器,LayoutInflater类的成员数函createView在建创一个UI控件之前,会先用调该过滤器的成员数函onLoadClass,来询问该过滤器是允许建创由参数name所描述的UI控件。如果不允许的话,LayoutInflater类的成员数函createView就会用调另外一个成员数函failNotAllowed来抛出一个常异。
为了免避每次建创一个UI控件时,都去询问过滤器是不是允许建创,LayoutInflater类的成员数函createView只会在第一次建创一个名称为name的UI控件时,才会询问过滤器,并且将询问结果保存在成员量变mFilterMap所指向的一个HashMap。这样当LayoutInflater类的成员数函createView以后再建创同名的UI控件时,以可就直接通过成员量变mFilterMap所指向的一个HashMap来晓得该UI控件是不是是允许建创的。
理解了LayoutInflater类的静态成员量变sConstructorMap以及成员量变mConstructorArgs、mFilter和mFilterMap的含义之后,读者以可就自己去理解LayoutInflater类的成员数函createView的实现了。这里要需重复强调的一点就是,我们在自定义一个UI控件的时候,一定要提供一个具有两个参数类型别分为Context和AttributeSet的构造数函,否则的话,该自定义控件就不可以在UI布局件文中应用。
至此,我们就以layout资源为例,详细地析分了Android应用程序资源的查找程过了,并且也完整地析分完成Android系统的资源管理框架了。从新学习Android系统的资源管理框架,请参考后面Android资源管理框架(Asset Manager)扼要分析和学习筹划一文。
老罗的浪新微博:http://weibo.com/shengyangluo,欢送存眷!
文章结束给大家分享下程序员的一些笑话语录: 《诺基亚投资手机浏览器UCWEB,资金不详或控股》杯具了,好不容易养大的闺女嫁外国。(心疼是你养的吗?中国创业型公司创业初期哪个从国有银行贷到过钱?)