Android资源文件的管理

android程序中,不管是普通的drawable、anim、color、layout、value等类型的资源文件,还是raw类型的资源文件,或者asset类型的资源文件,所有的资源文件都是通过AssetManager管理的。其中asset资源文件,在打包apk时,不会有任何的改动,会被原封不动的打包进apk内,可以通过文件名和路径访问;而raw资源文件打包时也会原封不动的打包,只不过使用资源id访问;而其它类型的资源文件,在打包的时候,可能会被优化(bitmap),使用资源id访问。

应用程序资源的编译、打包以及查找过程

以下,我们通过代码分析资源文件的管理。

1、Resources.java
位于目录/frameworks/base/core/java/android/content/res/Resources.java。Resources的代码逻辑很简单,基本就是直接调用了AssetManager的方法。需要注意的是有2个方法startPreloading()和finishPreloading()。这两个方法仅在ZygoteInit.java(Zygote进程)中调用,专门用于加载需要预加载的资源文件(在com.android.internal.R.array.preloaded_drawables中定义),并将这些资源文件存储在Resources的全局变量中。在应用进程启动(由Zygote进程fork)时,这些数据将从Zygote进程复制到应用进程中。

/**  * Start preloading of resource data using this Resources object.  Only* for use by the zygote process for loading common system resources.* {@hide}*/public final void startPreloading() {synchronized (mSync) {if (sPreloaded) {throw new IllegalStateException("Resources already preloaded");}sPreloaded = true;mPreloading = true;sPreloadedDensity = DisplayMetrics.DENSITY_DEVICE;mConfiguration.densityDpi = sPreloadedDensity;// 最终会调用AssetManager.setConfiguration(),已经缓存的数据可能会被删除updateConfiguration(null, null);}}    /**  * Called by zygote when it is done preloading resources, to change back* to normal Resources operation.*/public final void finishPreloading() {if (mPreloading) {mPreloading = false;flushLayoutCache(); // 刷新数据,config不匹配的数据将被移除}}public String getString(int id) throws NotFoundException {CharSequence res = getText(id);if (res != null) {return res.toString();}throw new NotFoundException("String resource ID #0x"+ Integer.toHexString(id));}public CharSequence getText(int id) throws NotFoundException {// 调用了AssetManager的getResourceText()方法CharSequence res = mAssets.getResourceText(id);if (res != null) {return res; }throw new NotFoundException("String resource ID #0x"+ Integer.toHexString(id));}   

2、AssetManager.java
位于/frameworks/base/core/java/android/content/res/目录。其内部通过一个StringBlock类管理所有string的资源。对应的还有一个XmlBlock类,管理所有的xml资源文件。但是,XmlBlock却是直接在Resource.java中缓存的,并且只能缓存最近加载的4个,是出于内存占用的考虑么???。另外,AssetManager还有一个比较重要的方法int addAssetPath(String path),用于导入zip资源文件或者资源目录。例如,可以使用该方法导入apk自带资源文件,或者导入theme apk等。在Activity初始化的时候,就是用了该方法导入的apk自带资源文件。有兴趣的话,可以自行查阅ActivityThread.java文件(搜索getTopLevelResources()方法,该方法在ContextImpl.java对象init时调用)。

    public AssetManager() {synchronized (this) {init(); // jni方法,创建native asset manager,并将其指针存放在mObject字段ensureSystemAssets();}}private static void ensureSystemAssets() {  // 创建system asset managersynchronized (sSync) {if (sSystem == null) {AssetManager system = new AssetManager(true);// 初始化system asset manager的StringBlockssystem.makeStringBlocks(false);sSystem = system;}}}// 初始化system string blocks 或者 复制system string blocks. /*package*/ final void makeStringBlocks(boolean copyFromSystem) {final int sysNum = copyFromSystem ? sSystem.mStringBlocks.length : 0;final int num = getStringBlockCount();mStringBlocks = new StringBlock[num];if (localLOGV) Log.v(TAG, "Making string blocks for " + this+ ": " + num);for (int i=0; iif (i < sysNum) {// 浅拷贝system string blocksmStringBlocks[i] = sSystem.mStringBlocks[i];} else {// getNativeStringBlock()是一个jni方法mStringBlocks[i] = new StringBlock(getNativeStringBlock(i), true);}}}/** * Retrieve the string value associated with a particular resource* identifier for the current configuration / skin.*//*package*/ final CharSequence getResourceText(int ident) {synchronized (this) {TypedValue tmpValue = mValue;// jni方法,猜想:多个StringBlocks,每个StringBlocks管理一种类型的资源int block = loadResourceValue(ident, (short) 0, tmpValue, true);if (block >= 0) {if (tmpValue.type == TypedValue.TYPE_STRING) {return mStringBlocks[block].get(tmpValue.data);}return tmpValue.coerceToString();}}return null;}   /*** Add an additional set of assets to the asset manager.  This can be* either a directory or ZIP file.  Not for use by applications.  Returns* the cookie of the added asset, or 0 on failure.* {@hide}*/public native final int addAssetPath(String path);

3、StringBlocks.java
位于/frameworks/base/core/java/android/content/res/目录。该对象主要是从底层的一个类似数组的结构体中使用index获取数据,并将数据缓存在java层的string数组或者SparseArray中。

    // 该构造方法只在system asset manager创建的时候调用// obj:对应底层的一个类似数组的结构体;useSparse:是否使用SparseArray缓存数据StringBlock(int obj, boolean useSparse) {mNative = obj;mUseSparse = useSparse;mOwnsNative = false;}public StringBlock(byte[] data, int offset, int size, boolean useSparse) {// nativeCreate()实际就是使用data、offset、size做浅复制,并将复制之后的一个结构体// 指针保存在mNative字段中mNative = nativeCreate(data, offset, size);mUseSparse = useSparse;mOwnsNative = true;}public CharSequence get(int idx) {synchronized (this) {if (mStrings != null) {CharSequence res = mStrings[idx];if (res != null) {return res;}} else if (mSparseStrings != null) {CharSequence res = mSparseStrings.get(idx);if (res != null) {return res;}} else {final int num = nativeGetSize(mNative);if (mUseSparse && num > 250) { // 大于250时使用SparseArray缓存mSparseStrings = new SparseArray();} else {mStrings = new CharSequence[num];}}String str = nativeGetString(mNative, idx);CharSequence res = str;int[] style = nativeGetStyle(mNative, idx);if (style != null) {if (mStyleIDs == null) {mStyleIDs = new StyleIDs();}// 。。。。省略,构建style idsString styleTag = nativeGetString(mNative, styleId);                                    res = applyStyles(str, style, mStyleIDs);}// 缓存资源if (mStrings != null) mStrings[idx] = res;else mSparseStrings.put(idx, res);return res;

4、android_util_AssetManager.cpp
位于/frameworks/base/core/jni/android_util_AssetManager.cpp。对接上层的AssetManager.java,逻辑不多,基本是直接调用到native AssetManager的方法。

static void android_content_AssetManager_init(JNIEnv* env, jobject clazz)
{AssetManager* am = new AssetManager();if (am == NULL) {jniThrowException(env, "java/lang/OutOfMemoryError", ""); return;}    // 加载/framework/framework-res.apk资源am->addDefaultAssets();ALOGV("Created AssetManager %p for Java object %p\n", am, clazz);// 将native asset manager对象的指针存入java asset manager的mObject字段env->SetIntField(clazz, gAssetManagerOffsets.mObject, (jint)am);
}// ident:资源id,density:0,outValue:TypedValue,resolve:true
static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject clazz,jint ident,jshort density,jobject outValue,jboolean resolve) 
{// 从java层的AssetManager对象的mObject字段中获取native AssetManager对象。// 该字段在java AssetManager对象init时设置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);uint32_t ref = ident;if (resolve) {block = res.resolveReference(&value, block, &ref, &typeSpecFlags, &config);}// copyValue()将底层的Res_value对象的字段值复制到上层的TypedValue对象中    return block >= 0 ? copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config) : block;
}// 加载xml资源文件
static jint android_content_AssetManager_openXmlAssetNative(JNIEnv* env, jobject clazz,jint cookie,jstring fileName)
{AssetManager* am = assetManagerForJavaObject(env, clazz);if (am == NULL) {return 0;}ALOGV("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;
}

5、AssetManager.cpp
位于/frameworks/base/libs/androidfw/目录。

static const char* kDefaultLocale = "default";
static const char* kDefaultVendor = "default";
static const char* kAssetsRoot = "assets";
static const char* kAppZipName = NULL; //"classes.jar";
static const char* kSystemAssets = "framework/framework-res.apk";
static const char* kIdmapCacheDir = "resource-cache";bool AssetManager::addDefaultAssets()
{const char* root = getenv("ANDROID_ROOT");  // 默认是/system路径LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_ROOT not set");String8 path(root);path.appendPath(kSystemAssets); // 绝对路径/system/framework/framework-res.apkreturn addAssetPath(path, NULL);
}bool AssetManager::addAssetPath(const String8& path, void** cookie)
{AutoMutex _l(mLock);asset_path ap;String8 realPath(path);if (kAppZipName) {realPath.appendPath(kAppZipName);}ap.type = ::getFileType(realPath.string());if (ap.type == kFileTypeRegular) {ap.path = realPath;} else {ap.path = path;ap.type = ::getFileType(path.string());if (ap.type != kFileTypeDirectory && ap.type != kFileTypeRegular) {ALOGW("Asset path %s is neither a directory nor file (type=%d).",path.string(), (int)ap.type);return false;}}// Skip if we have it already.// 从已加载的资源中查找同一cookie的资源文件index,确保资源文件不会被重复加载for (size_t i=0; iif (mAssetPaths[i].path == ap.path) {if (cookie) {*cookie = (void*)(i+1);}return true;}}ALOGV("In %p Asset %s path: %s", this,ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string());mAssetPaths.add(ap);// new paths are always added at the endif (cookie) {*cookie = (void*)mAssetPaths.size();}// add overlay packages for /system/framework; apps are handled by the// (Java) package managerif (strncmp(path.string(), "/system/framework/", 18) == 0) { // 匹配/system/framework// When there is an environment variable for /vendor, this// should be changed to something similar to how ANDROID_ROOT// and ANDROID_DATA are used in this file.String8 overlayPath("/vendor/overlay/framework/");overlayPath.append(path.getPathLeaf());if (TEMP_FAILURE_RETRY(access(overlayPath.string(), R_OK)) == 0) {asset_path oap;oap.path = overlayPath;oap.type = ::getFileType(overlayPath.string());bool addOverlay = (oap.type == kFileTypeRegular); // only .apks supported as overlayif (addOverlay) {// id map文件路径:将overlayPath中的所有/转换为@oap.idmap = idmapPathForPackagePath(overlayPath);if (isIdmapStaleLocked(ap.path, oap.path, oap.idmap)) {addOverlay = createIdmapFileLocked(ap.path, oap.path, oap.idmap);}}if (addOverlay) {mAssetPaths.add(oap);} else {ALOGW("failed to add overlay package %s\n", overlayPath.string());}}}return true;
}// 加载xml资源文件
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;
}

注意:AssetManager类的成员函数addAssetPath的最后一个工作是检查刚刚添加的Apk是否是在/system/framework/目录下面。如果是,就会在/vendor/overlay/framework/目录下找到一个同名的Apk文件,并且也会将该Apk文件添加到成员变量mAssetPaths(Vector)中去。这是一种资源覆盖机制,即用自定义的系统资源来覆盖系统默认的系统资源,以达到个性化系统界面的目的。如果要使用这种资源覆盖机制来自定义自己的系统资源,那么还需要提供一个idmap文件,用来说明它在/vendor/overlay/framework/目录下的Apk文件要覆盖系统的哪些资源,使用资源ID来描述。因此,这个idmap文件实际上就是一个资源ID映射文件。这个idmap文件最终保存在/data/resource-cache/目录下,并且按照一定的格式来命名。例如,如果apk为/vendor/overlay/framework/framework-res.apk,那么对应的idmap文件就会以名称为@vendor@overlay@framework@framework-res.apk@idmap。

另外:资源解析之后,在底层是存放在一个ResTable中的。当系统的config改变时,最终会通知到ResTable。ResTable会更新缓存数据。

6、总结

  • zygote进程初始化时,调用startPreloading()预加载了一部分系统资源
  • 应用进程由zygote进程fork启动时,复制了这些资源文件,并保存在一个static system asset manager中的StringBlock数组中
  • 应用进程在ActivityThread中调用getTopLevelResources()加载了自身apk的资源文件,存储在底层数据结构ResTable中
  • 应用在查找string资源时,如果从StringBlock缓存中无法找到,则从底层ResTable中查找,之后在保存在StringBlock中
  • 应用在查找xml资源时,如果从Resource.java的XmlBlock缓存中无法找到,则直接从文件中查找,最终返回XmlBlock的一个内部Parser


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部