前言

上一篇文章,讲到了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生成规则

更多相关文章

  1. 从AIDL开始谈Android进程间Binder通信机制
  2. Android(安卓)7.1.1 通话记录数据库详解
  3. 关于真机测试的android选择图片在ImageView上显示,调用setImageBi
  4. 开发者必备的十二大Android开发资源
  5. android中Intent传值与Bundle传值的区别详解
  6. Android(安卓)Boot: After kernel stage
  7. Android(安卓)Demo:读取本地图库与调用摄像头拍摄
  8. Android(安卓)Fragment 详解
  9. Android头像上传(本地相册和调用系统相机)

随机推荐

  1. Android(安卓)Gradle 编译问题汇总
  2. Android(安卓)SDK大连东软镜像地址及地址
  3. 详解android:scaleType属性
  4. 把TextView中的文字添加阴影效果及Style
  5. 黑马程序员之手机卫士第八天
  6. Android(安卓)EditView 获取焦点 不弹出
  7. Android(安卓)ListView xml配置
  8. android 图片自动切换
  9. Android应用开发相关下载资源
  10. 【30篇突击 android】源码统计 十