每个apk有一个Resources
-
getTopLevelResources
synchronized (this) { // Resources is app scale dependent. if (DEBUG) Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale); WeakReference<Resources> wr = mActiveResources.get(key); r = wr != null ? wr.get() : null; //if (r != null) Log.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate()); if (r != null && r.getAssets().isUpToDate()) { if (DEBUG) Slog.w(TAG, "Returning cached resources " + r + " " + resDir + ": appScale=" + r.getCompatibilityInfo().applicationScale + " key=" + key + " overrideConfig=" + overrideConfiguration); return r; }
- mActiveResources. ActivityThread类的成员变量mActiveResources指向的是一个HashMap。这个HashMap用来维护在当前应用程序进程中加载的每一个Apk文件及其对应的Resources对象的对应关系.
- 给定一个Apk文件路径,ActivityThread类的成员函数getTopLevelResources可以在成员变量mActiveResources中检查是否存在一个对应的Resources对象。如果不存在,那么就会新建一个,并且保存在ActivityThread类的成员变量mActiveResources中。
- 参数resDir即为要获取其对应的Resources对象的Apk文件路径
-
创建AssetManager对象
public AssetManager() { synchronized (this) { if (DEBUG_REFS) { mNumRefs = 0; incRefsLocked(this.hashCode()); } init(false); if (localLOGV) Log.v(TAG, "New asset manager: " + this); ensureSystemAssets(); } }
先调用了init(false)方法。而init通过jni调用到了底层。
AssetManager的文件定义在
android-6.0.0_r1\frameworks\base\core\java\android\content\res\AssetManager.java
中。而c++层的函数定义在frameworks/base/core/jni/android_util_AssetManager.cpp
中static void android_content_AssetManager_init(JNIEnv* env, jobject clazz, jboolean isSystem) { if (isSystem) { verifySystemIdmaps(); } AssetManager* am = new AssetManager(); if (am == NULL) { jniThrowException(env, "java/lang/OutOfMemoryError", ""); return; } am->addDefaultAssets(); ALOGV("Created AssetManager %p for Java object %p\n", am, clazz); env->SetLongField(clazz, gAssetManagerOffsets.mObject, reinterpret_cast<jlong>(am)); }
-
这里先创建了一个AssetManager对象。AssetManager.cpp定义在
android-6.0.0_r1\frameworks\base\libs\androidfw\AssetManager.cpp
中。-
然后进入到addDefaultAssets()方法。该方法添加默认的资源路径
bool AssetManager::addDefaultAssets() { const char* root = getenv("ANDROID_ROOT"); LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_ROOT not set"); String8 path(root); path.appendPath(kSystemAssets); return addAssetPath(path, NULL); }
- s首先通过环境变量ANDROID_ROOT来获得Android的系统路径
- 接着再将全局变量kSystemAssets所指向的字符串“framework/framework-res.apk”附加到这个系统路径的后面去
- addAssetPath来将它添加到当前正在初始化的AssetManager对象中去。
-
然后执行
env->SetLongField(clazz, gAssetManagerOffsets.mObject, reinterpret_cast<jlong>(am));
将c++层生成的AssetManager设置到java层的mObject中// For communication with native code. private long mObject;
-
到这里AssetManager创建完毕。然后设置相关的路径
AssetManager assets = new AssetManager(); // resDir can be null if the 'android' package is creating a new Resources object. // This is fine, since each AssetManager automatically loads the 'android' package // already. if (resDir != null) { if (assets.addAssetPath(resDir) == 0) { return null; } } if (splitResDirs != null) { for (String splitResDir : splitResDirs) { if (assets.addAssetPath(splitResDir) == 0) { return null; } } } if (overlayDirs != null) { for (String idmapPath : overlayDirs) { assets.addOverlayPath(idmapPath); } } if (libDirs != null) { for (String libDir : libDirs) { if (libDir.endsWith(".apk")) { // Avoid opening files we know do not have resources, // like code-only .jar files. if (assets.addAssetPath(libDir) == 0) { Log.w(TAG, "Asset path '" + libDir + "' does not exist or contains no resources."); } } } }
接着就创建Resource对象
r = new Resources(assets, dm, config, compatInfo);
这里看到AssetManager保存到了Resources对象中。接着进入到Resources的构造方法中
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config, CompatibilityInfo compatInfo) { mAssets = assets; mMetrics.setToDefaults(); if (compatInfo != null) { mCompatibilityInfo = compatInfo; } updateConfiguration(config, metrics); assets.ensureStringBlocks(); }
最后进入到
updateConfiguration(Configuration config, DisplayMetrics metrics, CompatibilityInfo compat)
mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc, locale, mConfiguration.orientation, mConfiguration.touchscreen, mConfiguration.densityDpi, mConfiguration.keyboard, keyboardHidden, mConfiguration.navigation, width, height, mConfiguration.smallestScreenWidthDp, mConfiguration.screenWidthDp, mConfiguration.screenHeightDp, mConfiguration.screenLayout, mConfiguration.uiMode, Build.VERSION.RESOURCES_SDK_INT);
-
mConfiguration指向的是一个Configuration对象,用来描述设备当前的配置信息
-
Resources类的成员函数updateConfiguration首先是根据参数config和metrics来更新设备的当前配置信息,例如,屏幕大小和密码、国家地区和语言、键盘配置情况等等,接着再调用成员变量mAssets所指向的一个Java层的AssetManager对象的成员函数setConfiguration来将这些配置信息设置到与之关联的C++层的AssetManager对象中去。
-
进入到c++层中的定义
static void android_content_AssetManager_setConfiguration(JNIEnv* env, jobject clazz, jint mcc, jint mnc, jstring locale, jint orientation, jint touchscreen, jint density, jint keyboard, jint keyboardHidden, jint navigation, jint screenWidth, jint screenHeight, jint smallestScreenWidthDp, jint screenWidthDp, jint screenHeightDp, jint screenLayout, jint uiMode, jint sdkVersion) { AssetManager* am = assetManagerForJavaObject(env, clazz); if (am == NULL) { return; } ResTable_config config; memset(&config, 0, sizeof(config)); const char* locale8 = locale != NULL ? env->GetStringUTFChars(locale, NULL) : NULL; // Constants duplicated from Java class android.content.res.Configuration. static const jint kScreenLayoutRoundMask = 0x300; static const jint kScreenLayoutRoundShift = 8; config.mcc = (uint16_t)mcc; config.mnc = (uint16_t)mnc; config.orientation = (uint8_t)orientation; config.touchscreen = (uint8_t)touchscreen; config.density = (uint16_t)density; config.keyboard = (uint8_t)keyboard; config.inputFlags = (uint8_t)keyboardHidden; config.navigation = (uint8_t)navigation; config.screenWidth = (uint16_t)screenWidth; config.screenHeight = (uint16_t)screenHeight; config.smallestScreenWidthDp = (uint16_t)smallestScreenWidthDp; config.screenWidthDp = (uint16_t)screenWidthDp; config.screenHeightDp = (uint16_t)screenHeightDp; config.screenLayout = (uint8_t)screenLayout; config.uiMode = (uint8_t)uiMode; config.sdkVersion = (uint16_t)sdkVersion; config.minorVersion = 0; // In Java, we use a 32bit integer for screenLayout, while we only use an 8bit integer // in C++. We must extract the round qualifier out of the Java screenLayout and put it // into screenLayout2. config.screenLayout2 = (uint8_t)((screenLayout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift); am->setConfiguration(config, locale8); if (locale != NULL) env->ReleaseStringUTFChars(locale, locale8); }
将ResTable_config设置到AssetManager中。继续进入到
setConfiguration
中void AssetManager::setConfiguration(const ResTable_config& config, const char* locale) { AutoMutex _l(mLock); *mConfig = config; if (locale) { setLocaleLocked(locale); } else if (config.language[0] != 0) { char spec[RESTABLE_MAX_LOCALE_LEN]; config.getBcp47Locale(spec); setLocaleLocked(spec); } else { updateResourceParamsLocked(); } }
-
AssetManager类的成员变量mConfig指向的是一个ResTable_config对象,用来描述设备的当前配置信息,AssetManager类的成员函数setConfiguration首先将参数config所描述的设备配置信息拷贝到它里面去。
-
local的值不等于NULL,那么它指向的字符串就是用来描述设备的国家、地区和语言信息的,这时候AssetManager类的成员函数setConfiguration就会调用另外一个成员函数setLocalLocked来将它们设置到AssetManager类的另外一个成员变量mLocale中去。
-
local的值等于NULL,并且参数config指向的一个ResTable_config对象包含了设备的国家、地区和语言信息,那么AssetManager类的成员函数setConfiguration同样会调用另外一个成员函数setLocalLocked来将它们设置到AssetManager类的另外一个成员变量mLocale中去。
-
如果参数local的值等于NULL,并且参数config指向的一个ResTable_config对象没有包含设备的国家、地区和语言信息,那么就说明设备的国家、地区和语言等信息不需要更新,这时候AssetManager类的成员函数setConfiguration就会直接调用另外一个成员函数updateResourceParamsLocked来更新资源表中的设备配置信息。
注意,AssetManager类的成员函数setLocalLocked来更新了成员变量mLocale的内容之后,同样会调用另外一个成员函数updateResourceParamsLocked来更新资源表中的设备配置信息。
-
updateResourceParamsLocked()方法
void AssetManager::updateResourceParamsLocked() const { ResTable* res = mResources; if (!res) { return; } if (mLocale) { mConfig->setBcp47Locale(mLocale); } else { mConfig->clearLocale(); } res->setParameters(mConfig); }
- AssetManager类的成员变量mResources指向的是一个ResTable对象,这个ResTable对象描述的就是一个资源索引表
- AssetManager类的成员函数updateResourceParamsLocked首先是将成员变量mLocale所描述的国家、地区和语言信息更新到另外一个成员变量mConfig中去,接着再将成员变量mConfig所包含的设备配置信息设置到成员变量mResources所描述的一个资源索引表中去,这是通过调用成员变量mResources所指向的一个ResTable对象的成员函数setParameters来实现的。
updateConfiguration执行完后就调用到
assets.ensureStringBlocks()
/*package*/ final void ensureStringBlocks() { if (mStringBlocks == null) { synchronized (this) { if (mStringBlocks == null) { makeStringBlocks(sSystem.mStringBlocks); } } } }
-
mStringBlocks指向的是一个StringBlock数组,其中,每一个StringBlock对象都是用来描述一个字符串资源池。每一个资源表都包含有一个资源项值字符串资源池,AssetManager类的成员变量mStringBlocks就是用来保存所有的资源表中的资源项值字符串资源池的.
-
ensureStringBlocks首先检查成员变量mStringBlocks的值是否等于null。如果等于null的话,那么就说明当前应用程序使用的资源表中的资源项值字符串资源池还没有读取出来,这时候就会调用另外一个成员函数makeStringBlocks来进行读取。
/*package*/ final void makeStringBlocks(StringBlock[] seed) { final int seedNum = (seed != null) ? seed.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; i<num; i++) { if (i < seedNum) { mStringBlocks[i] = seed[i]; } else { mStringBlocks[i] = new StringBlock(getNativeStringBlock(i), true); } } }
-
getStringBlockCount
获取字符串资源池的数量。这里又通过jni进行了调用static jint android_content_AssetManager_getStringBlockCount(JNIEnv* env, jobject clazz) { AssetManager* am = assetManagerForJavaObject(env, clazz); if (am == NULL) { return 0; } return am->getResources().getTableCount(); }
const ResTable& AssetManager::getResources(bool required) const { const ResTable* rt = getResTable(required); return *rt; }
-
通过循环先将系统的资源添加到数组中,然后再讲剩余的资源池添加到数组中。
-
-
到这里整个Resources和AssetManager的创建已经完成。
-
网友评论