上一篇文章,講到了Android中進程的啓動和一個Activity的建立到顯示流程,如今本篇要分析的是在Android中資源的裝載機制,例如字符串資源,圖片資源是如何被裝載的。這裏將從字符串和圖片兩種類型資源展開分析,同時對於後面所利用的資源裝載的內容也會作簡單的分析。java
對於資源的裝載機制,這裏核心的幾個類是Resources,ResourcesImpl,AssetManager。Resources算是對於ResourcesImpl的一個代理,Resources的全部調用都是會調用到ResourcesImpl上,在ResourcesImpl的內部,具有對於資源的Cache和AssetManager,對於資源的裝載會首先從其Cache中進行查找,當查找不到的時候,會調用AssetManager進行相應資源的裝載,裝載以後會在ResourcesImpl中將資源緩存下來。android
Resource 中有一個內部靜態變量緩存
static Resources mSystem = null;
在getSystem方法中進行了初始化,做爲對於內部變量的持有被保存着,其初次的調用是在zygote建立新進程的時候,預加載資源的時候被調用。cookie
public static Resources getSystem() { synchronized (sSync) { Resources ret = mSystem; if (ret == null) { ret = new Resources(); mSystem = ret; } return ret; } }
Resrouce對象的建立,在Resrouce中的各類操做,最終真正的執行者是ResourcesImpl。數據結構
private Resources() { this(null); final DisplayMetrics metrics = new DisplayMetrics(); metrics.setToDefaults(); final Configuration config = new Configuration(); config.setToDefaults(); mResourcesImpl = new ResourcesImpl(AssetManager.getSystem(), metrics, config, new DisplayAdjustments()); }
在Resources的構造函數中建立ResourcesImpl的實例。app
public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics, @Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) { mAssets = assets; mMetrics.setToDefaults(); mDisplayAdjustments = displayAdjustments; updateConfiguration(config, metrics, displayAdjustments.getCompatibilityInfo()); mAssets.ensureStringBlocks(); }
在建立ResoucesImpl實例的時候,得到了AssetManager的實例,其負責了應用層和資源文件的交互。Resource對象的得到,是經過ContextImpl方法中得到,得到方式是返回了其內部的變量mResource變量,ide
resources = mResourcesManager.getResources( activityToken, packageInfo.getResDir(), packageInfo.getSplitResDirs(), packageInfo.getOverlayDirs(), packageInfo.getApplicationInfo().sharedLibraryFiles, displayId, overrideConfiguration, compatInfo, packageInfo.getClassLoader());
調用了ResourcesManager的getOrCreateResources方法。其實現爲從activityResources中查找,若是查找不到,則會從新建立一個,而後加入到activityResources中,並返回。函數
從一個獲取資源文件的方法看起,這裏從一個獲取文字的方法入手。ui
@NonNull public CharSequence getText(@StringRes int id) throws NotFoundException { CharSequence res = mResourcesImpl.getAssets().getResourceText(id); if (res != null) { return res; } throw new NotFoundException("String resource ID #0x" + Integer.toHexString(id)); }
public AssetManager getAssets() { return mAssets; }
調用AssetManager的getResourceTextthis
final CharSequence getResourceText(@StringRes int resId) { synchronized (this) { final TypedValue outValue = mValue; if (getResourceValue(resId, 0, outValue, true)) { return outValue.coerceToString(); } return null; } }
首先根據id得到TypedValue,而後根據TypedValue得到咱們須要的資源。
final boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue, boolean resolveRefs) { final int block = loadResourceValue(resId, (short) densityDpi, outValue, resolveRefs); if (block < 0) { return false; } if (outValue.type == TypedValue.TYPE_STRING) { outValue.string = mStringBlocks[block].get(outValue.data); } return true; }
對於字符串資源,其值就存在TypedValue中,因此在得到了TypedValue以後,就能夠經過其來得到資源值。
因爲圖片資源的特殊性,相比於字符串資源的獲取,要複雜一些,這裏從上層的獲取方法開始進行分析。
public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme) throws NotFoundException { final TypedValue value = obtainTempTypedValue(); try { final ResourcesImpl impl = mResourcesImpl; impl.getValue(id, value, true); return impl.loadDrawable(this, value, id, theme, true); } finally { releaseTempTypedValue(value); } }
和對於字符串資源的裝載相似,首先根據資源ID獲取一個TypedValue對象,而後利用TypedValue實例,經過AssetManager進行裝載。
void getValue(@AnyRes int id, TypedValue outValue, boolean resolveRefs) throws NotFoundException { boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs); if (found) { return; } }
final boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue, boolean resolveRefs) { final int block = loadResourceValue(resId, (short) densityDpi, outValue, resolveRefs); if (block < 0) { return false; } if (outValue.type == TypedValue.TYPE_STRING) { outValue.string = mStringBlocks[block].get(outValue.data); } return true; }
Drawable資源的獲取核心代碼是在對於ResourcesImpl
的loadDrawable
函數的調用。
@Nullable Drawable loadDrawable(Resources wrapper, TypedValue value, int id, Resources.Theme theme, boolean useCache) throws NotFoundException { try { if (TRACE_FOR_PRELOAD) { if ((id >>> 24) == 0x1) { final String name = getResourceName(id); if (name != null) { Log.d("PreloadDrawable", name); } } } //判斷是否爲ColorDrawable final boolean isColorDrawable; final DrawableCache caches; final long key; if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT && value.type <= TypedValue.TYPE_LAST_COLOR_INT) { isColorDrawable = true; caches = mColorDrawableCache; key = value.data; } else { isColorDrawable = false; caches = mDrawableCache; key = (((long) value.assetCookie) << 32) | value.data; } //,是否存在查找的Drawable if (!mPreloading && useCache) { final Drawable cachedDrawable = caches.getInstance(key, wrapper, theme); if (cachedDrawable != null) { return cachedDrawable; } } // 檢查預加載的資源文件中,是否存在要查找的Drawable final Drawable.ConstantState cs; if (isColorDrawable) { cs = sPreloadedColorDrawables.get(key); } else { cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key); } //建立Drawable Drawable dr; if (cs != null) { dr = cs.newDrawable(wrapper); } else if (isColorDrawable) { dr = new ColorDrawable(value.data); } else { dr = loadDrawableForCookie(wrapper, value, id, null); } // 對Drawable的主題進行處理 final boolean canApplyTheme = dr != null && dr.canApplyTheme(); if (canApplyTheme && theme != null) { dr = dr.mutate(); dr.applyTheme(theme); dr.clearMutated(); } // 將裝載的Drawable資源加入到緩存之中 if (dr != null && useCache) { dr.setChangingConfigurations(value.changingConfigurations); cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr); } return dr; } catch (Exception e) { ... } }
loadDrawableForCookie
根據TypedValue中存儲的信息,從XML文件或者資源流中構建Drawable
private Drawable loadDrawableForCookie(Resources wrapper, TypedValue value, int id, Resources.Theme theme) { if (value.string == null) { throw new NotFoundException("Resource \"" + getResourceName(id) + "\" (" + Integer.toHexString(id) + ") is not a Drawable (color or path): " + value); } //解析值的文件名 final String file = value.string.toString(); if (TRACE_FOR_MISS_PRELOAD) { // Log only framework resources if ((id >>> 24) == 0x1) { final String name = getResourceName(id); if (name != null) { Log.d(TAG, "Loading framework drawable #" + Integer.toHexString(id) + ": " + name + " at " + file); } } } final Drawable dr; //若是文件後綴爲xml,經過XmlResourceParser構建Drawable對象 try { if (file.endsWith(".xml")) { final XmlResourceParser rp = loadXmlResourceParser( file, id, value.assetCookie, "drawable"); dr = Drawable.createFromXml(wrapper, rp, theme); rp.close(); } else { //從文件流中構建Drawable對象 final InputStream is = mAssets.openNonAsset( value.assetCookie, file, AssetManager.ACCESS_STREAMING); dr = Drawable.createFromResourceStream(wrapper, value, is, file, null); is.close(); } } catch (Exception e) { ... } return dr; }
XmlResourceParser loadXmlResourceParser(@NonNull String file, @AnyRes int id, int assetCookie, @NonNull String type) throws NotFoundException { if (id != 0) { try { synchronized (mCachedXmlBlocks) { final int[] cachedXmlBlockCookies = mCachedXmlBlockCookies; final String[] cachedXmlBlockFiles = mCachedXmlBlockFiles; final XmlBlock[] cachedXmlBlocks = mCachedXmlBlocks; // 檢測緩存是否在咱們須要的資源 final int num = cachedXmlBlockFiles.length; for (int i = 0; i < num; i++) { if (cachedXmlBlockCookies[i] == assetCookie && cachedXmlBlockFiles[i] != null && cachedXmlBlockFiles[i].equals(file)) { return cachedXmlBlocks[i].newParser(); } } // 若是資源不在緩存之中,這經過AssetManager去裝載,而後加入到緩存中 final XmlBlock block = mAssets.openXmlBlockAsset(assetCookie, file); if (block != null) { final int pos = (mLastCachedXmlBlockIndex + 1) % num; mLastCachedXmlBlockIndex = pos; final XmlBlock oldBlock = cachedXmlBlocks[pos]; if (oldBlock != null) { oldBlock.close(); } cachedXmlBlockCookies[pos] = assetCookie; cachedXmlBlockFiles[pos] = file; cachedXmlBlocks[pos] = block; return block.newParser(); } } } catch (Exception e) { .... } } }
圖片資源的裝載流程是首先將根據ID得到TypedValue實例,而後根據TypedValue進行查找Drawable資源,首先檢測緩存中是否有該資源,若是沒有從預加載資源中查找,若是預加載資源中也沒有,判斷要加載的資源類型,若是爲colorDrawable,這根據Typedvalue進行建立,不然經過加載xml或者文件輸入流進行處理來得到Drawable對象。
資源的裝載分爲兩步,一個是經過資源ID獲得ID對應的TypedValue對象,對於簡單的資源,經過TypedValue便可,對於複雜的資源,須要第二步來把資源文件裝載到內存之中。
在建立Resources的構造函數,建立ResourcesImpl的時候調用了AssetManager的getSystem方法,該方法用來確保建立惟一的AssetManager實例。
public static AssetManager getSystem() { ensureSystemAssets(); return sSystem; }
保證全局只有一個AssetManager
private static void ensureSystemAssets() { synchronized (sSync) { if (sSystem == null) { AssetManager system = new AssetManager(true); system.makeStringBlocks(null); sSystem = system; } } }
private AssetManager(boolean isSystem) { if (DEBUG_REFS) { synchronized (this) { mNumRefs = 0; incRefsLocked(this.hashCode()); } } init(true); }
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)); }
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, false /* appAsLib */, true /* isSystemAsset */); }
設置資源路徑,經過對這個值的修改能夠實現動態加載。
public final int addAssetPath(String path) { return addAssetPathInternal(path, false); }
private final int addAssetPathInternal(String path, boolean appAsLib) { synchronized (this) { int res = addAssetPathNative(path, appAsLib); makeStringBlocks(mStringBlocks); return res; } }
添加資源路徑
bool AssetManager::addAssetPath( const String8& path, int32_t* cookie, bool appAsLib, bool isSystemAsset) { 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. for (size_t i=0; i<mAssetPaths.size(); i++) { if (mAssetPaths[i].path == ap.path) { if (cookie) { *cookie = static_cast<int32_t>(i+1); } return true; } } ALOGV("In %p Asset %s path: %s", this, ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string()); ap.isSystemAsset = isSystemAsset; mAssetPaths.add(ap); // new paths are always added at the end if (cookie) { *cookie = static_cast<int32_t>(mAssetPaths.size()); } #ifdef __ANDROID__ // Load overlays, if any asset_path oap; for (size_t idx = 0; mZipSet.getOverlay(ap.path, idx, &oap); idx++) { oap.isSystemAsset = isSystemAsset; mAssetPaths.add(oap); } #endif if (mResources != NULL) { appendPathToResTable(ap, appAsLib); } return true; }
APK文件中有一個文件resource.arsc。這個文件存放的是APK中資源的ID和資源類型,屬性,文件名的讀經關係表和全部的字符串,裝載APK,就是解析該文件生成ResRTable對象,經過ResTable對象來解析資源ID。解壓相應的路徑,從中得到相應的資源表,而後加入到其中。每一次的調用都會調用,appendPathToResTable,將新增的路徑的資源表添加到其中。
void getValue(@AnyRes 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)); }
final boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue, boolean resolveRefs) { final int block = loadResourceValue(resId, (short) densityDpi, outValue, resolveRefs); if (block < 0) { return false; } if (outValue.type == TypedValue.TYPE_STRING) { outValue.string = mStringBlocks[block].get(outValue.data); } return true; }
從AssetManager中根據ID獲取Value值。
static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject clazz, jint ident, jshort density, jobject outValue, jboolean resolve) { if (outValue == NULL) { jniThrowNullPointerException(env, "outValue"); return 0; } 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 (kThrowOnBadId) { if (block == BAD_INDEX) { jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); return 0; } } uint32_t ref = ident; if (resolve) { block = res.resolveReference(&value, block, &ref, &typeSpecFlags, &config); if (kThrowOnBadId) { if (block == BAD_INDEX) { jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); return 0; } } } if (block >= 0) { return copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config); } return static_cast<jint>(block); }
對於資源的TypedValue的獲取核心代碼。
const ResTable& res(am->getResources()); ssize_t block = res.getResource(ident, &value, false, density, &typeSpecFlags, &config);
const ResTable& AssetManager::getResources(bool required) const { const ResTable* rt = getResTable(required); return *rt; }
根據AssetManager中設置的資源路徑來查找資源Table
首先判斷是否已經存在ResTable,若是不存在則建立,存在則會直接返回。而後根據路徑去查找相應的Resources文件,而後將其轉化爲ResTable,方便後面的查找。
const ResTable* AssetManager::getResTable(bool required) const { ResTable* rt = mResources; //已經存在 if (rt) { return rt; } AutoMutex _l(mLock); if (mResources != NULL) { return mResources; } if (mCacheMode != CACHE_OFF && !mCacheValid) { const_cast<AssetManager*>(this)->loadFileNameCacheLocked(); } //建立ResTable mResources = new ResTable(); updateResourceParamsLocked(); bool onlyEmptyResources = true; const size_t N = mAssetPaths.size(); //遍歷Asset路徑,對於路徑調用appendPathToResTable for (size_t i=0; i<N; i++) { bool empty = appendPathToResTable(mAssetPaths.itemAt(i)); onlyEmptyResources = onlyEmptyResources && empty; } //若是爲空,則證實沒有找到resources.arsc文件 if (required && onlyEmptyResources) { ALOGW("Unable to find resources file resources.arsc"); delete mResources; mResources = NULL; } return mResources; }
Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs, String[] libDirs, int displayId, LoadedApk pkgInfo) { return mResourcesManager.getResources(null, resDir, splitResDirs, overlayDirs, libDirs, displayId, null, pkgInfo.getCompatibilityInfo(), pkgInfo.getClassLoader()); }
經過mResourcesManager得到Resources對象,若是不存在,則建立一個。
對於資源的加載,大概能夠經過上圖進行歸納,根據ID獲取TypedValue,TypedValue的獲取是在AssetManager添加資源路徑的時候,經過對資源表的解析來構建的一個ResTable,經過該數據結構根據ID做爲索引查找並構建TypedValue,而後再根據資源文件的類型,藉助TypedValue內存儲的關於資源的詳細信息來獲取資源,同時將加載的資源進行緩存。所以在插件化的方案中,經過建立新的Resource對象,爲其添加新的Asset路徑,從而構建出一個新的ResTable,實現經過ID進行非宿主App資源的裝載。
[Android 資源ID生成規則
](http://blog.csdn.net/nio96/ar...