前言

上一篇文章,讲到了Android中进程的启动和一个Activity的创建到显示流程,现在本篇要分析的是在Android中资源的装载机制,例如字符串资源,图片资源是如何被装载的。这里将从字符串和图片两种类型资源展开分析,同时对于后面所利用的资源装载的内容也会做简单的分析。

Resources源码剖析

对于资源的装载机制,这里核心的几个类是Resources,ResourcesImpl,AssetManager。Resources算是对于ResourcesImpl的一个代理,Resources的所有调用都是会调用到ResourcesImpl上,在ResourcesImpl的内部,具备对于资源的Cache和AssetManager,对于资源的装载会首先从其Cache中进行查找,当查找不到的时候,会调用AssetManager进行相应资源的装载,装载之后会在ResourcesImpl中将资源缓存下来。

Resource 中有一个内部静态变量

static Resources mSystem = null;

在getSystem方法中进行了初始化,作为对于内部变量的持有被保存着,其初次的调用是在zygote创建新进程的时候,预加载资源的时候被调用。

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的实例。

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变量,

resources = mResourcesManager.getResources(activityToken,packageInfo.getResDir(),packageInfo.getSplitResDirs(),packageInfo.getOverlayDirs(),packageInfo.getApplicationInfo().sharedLibraryFiles,displayId,overrideConfiguration,compatInfo,packageInfo.getClassLoader());

调用了ResourcesManager的getOrCreateResources方法。其实现为从activityResources中查找,如果查找不到,则会重新创建一个,然后加入到activityResources中,并返回。

获取字符串资源

从一个获取资源文件的方法看起,这里从一个获取文字的方法入手。

@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的getResourceText

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资源的获取核心代码是在对于ResourcesImplloadDrawable函数的调用。

@NullableDrawable 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即可,对于复杂的资源,需要第二步来把资源文件装载到内存之中。

AssetManager

在创建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(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(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(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,将新增的路径的资源表添加到其中。

根据ID获取TypedValue

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(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(this)->loadFileNameCacheLocked();    }        //创建ResTable    mResources = new ResTable();    updateResourceParamsLocked();    bool onlyEmptyResources = true;    const size_t N = mAssetPaths.size();    //遍历Asset路径,对于路径调用appendPathToResTable    for (size_t i=0; i
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...

更多相关文章

  1. Android文件选择器
  2. Android Studio解决未识别Java文件(出现红J)问题
  3. Android SD卡简单的文件读写操作
  4. Android基础---学习历程【上课用到的资源---学期!汇总!整理】
  5. 《Android Studio开发实战 从零基础到App上线(第2版)》资源下载
  6. 【Android】图片(文件)上传的请求分析结构
  7. Android获取.Gradle文件中的值和Manifests文件中的值

随机推荐

  1. android 屏幕旋转 重新调用onCreate
  2. 搭建 Android 开发环境,初试HelloWorld (w
  3. ADB 命令大全
  4. 因占用IP地址 普林斯顿大学屏蔽Android设
  5. Android获取屏幕宽高要注意的问题
  6. Android应用程序中应用图标和名字的设置
  7. Android系统的Binder机制之四——系统Ser
  8. 图书馆座位管理系统(android,java后台,my
  9. android中使用flexboxlayout
  10. Android : RadioBotton—— 图片浏览器