Android apk读取资源文件过程详解

上一篇咱们着重分析了资源是如何编译和打包到apk里的,重点在于resources.arsc和R.java两个文件,还不了解的朋友能够先看上一篇的内容:Android资源编译和打包过程分析。熟悉了apk里资源是如何分布的以后,咱们就能够来分析apk读取的时候是如何获取到最适合这个设备的资源的过程了。java

注意:这里源码都是看的Android 8.0的源码,其余系统版本原理基本相似。android

首先咱们来提出问题: Question1:好比一个图片资源被放在drawable-xhdpi和drawable-xxhdpi两个文件夹里,apk读取的时候是如何经过设备信息来获取到最合适的那个图片资源的?c++

通常状况下,咱们在java代码里获取图片,获取string字符串,都是用到以下代码:数组

//设置背景drawable
tvAdvancedTask.setBackground(getResources().getDrawable(R.drawable.learning_task_unselected));
//设置字体颜色
tvAdvancedTask.setTextColor(getResources().getColor(R.color.color_9B6D00));

复制代码

咱们就从这个入口开始一部一部进行剖析。这里咱们主要剖析刚才问题中的图片资源,string,layout等读取过程在底层基本相同,只是在上层解析获取的时候稍微有些不一样而已,都是小问题,首先来看下我理的整个流程图,接下来咱们会按照流程图来一步一步剖析。 缓存

流程图

咱们先来看getResource()方法:bash

@Override
    public Resources getResources() {
        if (mResources == null && VectorEnabledTintResources.shouldBeUsed()) {
            mResources = new VectorEnabledTintResources(this, super.getResources());
        }
        return mResources == null ? super.getResources() : mResources;
    }
复制代码

mResources若是为空的话则调用父类的getResources()来建立,跟进去到父类ContextTheme.java:微信

@Override
    public Resources getResources() {
        return getResourcesInternal();
    }

    private Resources getResourcesInternal() {
        if (mResources == null) {
            if (mOverrideConfiguration == null) {
                mResources = super.getResources();
            } else {
                final Context resContext = createConfigurationContext(mOverrideConfiguration);
                mResources = resContext.getResources();
            }
        }
        return mResources;
    }
复制代码

mOverrideConfiguration是Configuration类的对象,主要负责描述可能影响应用程序检索的资源的全部设备配置信息,包括屏幕大小,屏幕方向等。若是这个对象为空的话,则经过ContextTheme的父类也就是ContextWrapper的getResources()方法获取,否则则建立一个Context对象直接获取。接下来咱们进ContextWrapper的getResources()方法:cookie

@Override
    public Resources getResources() {
        return mBase.getResources();
    }
复制代码

这里其实也就是调用了Context对象的getResources()方法,接着进Context类:app

public abstract Resources getResources();
复制代码

getResources()方法在Context类中是一个抽象函数,那咱们就进他的实现类ContextImpl.java中去看:less

@Override
    public Resources getResources() {
        return mResources;
    }
复制代码

这里咱们就获取到了已经init好了的Resources实例,其实熟悉Activity建立过程的朋友应该清楚,ActivityThread类的成员函数performLaunchActivity在应用程序进程中会建立一个Activity实例,建立实例的同时会为它设置运行上下文也就是Context实例,而Activity的父类ContextThemeWrapper的父类ContextWrapper类里有一个ContextImpl对象mBase,二者就关联起来了。Activity经过这个mBase对象装饰上了不少ContextImpl类的操做,好比上面一直在讲的getResources()方法,还有getAssets(),startService()等操做,这是个很典型的装饰者模式。

public class ContextWrapper extends Context {
    Context mBase;

    public ContextWrapper(Context base) {
        mBase = base;
    }
    
    /**
     * Set the base context for this ContextWrapper.  All calls will then be
     * delegated to the base context.  Throws
     * IllegalStateException if a base context has already been set.
     * 
     * @param base The new base context for this wrapper.
     */
    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }
//...............................
}
复制代码

这里咱们就获取到了Resources类的实例,获取到实例之后咱们就能够来分析getDrawable方法了:

@Deprecated
    public Drawable getDrawable(@DrawableRes int id) throws NotFoundException {
        final Drawable d = getDrawable(id, null);
        if (d != null && d.canApplyTheme()) {
            Log.w(TAG, "Drawable " + getResourceName(id) + " has unresolved theme "
                    + "attributes! Consider using Resources.getDrawable(int, Theme) or "
                    + "Context.getDrawable(int).", new RuntimeException());
        }
        return d;
    }
复制代码

这里传进来的id其实就是该资源对应的那个id,也就是上文中Android资源编译和打包过程分析中第8步给资源生成资源id中的那个id。接着跟下去:

public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme)
            throws NotFoundException {
        return getDrawableForDensity(id, 0, theme);
    }
复制代码

以后进入getDrawableForDensity(@DrawableRes int id, int density, @Nullable Theme theme)方法:

public Drawable getDrawableForDensity(@DrawableRes int id, int density, @Nullable Theme theme) {
        final TypedValue value = obtainTempTypedValue();
        try {
            final ResourcesImpl impl = mResourcesImpl;
            impl.getValueForDensity(id, density, value, true);
            return impl.loadDrawable(this, value, id, density, theme);
        } finally {
            releaseTempTypedValue(value);
        }
    }
复制代码

这里咱们看到新建了一个ResourcesImpl对象,而后调用了该对象的getValueForDensity方法,而后return该对象的loadDrawable方法取加载获取到的Drawable对象。loadDrawable咱们稍后再讲,这已是整个过程的最后收尾了,接下来咱们进ResourcesImpl.java的getValueForDensity方法,根据字面意思这个方法就是根据设备取获取资源。

void getValueForDensity(@AnyRes int id, int density, TypedValue outValue,
            boolean resolveRefs) throws NotFoundException {
        boolean found = mAssets.getResourceValue(id, density, outValue, resolveRefs);
        if (found) {
            return;
        }
        throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id));
    }
复制代码

这个方法调用了AssetManager的getResourceValue方法去判断是否能根据id能够读取到,若是读取不到,则抛出异常。AssetManager这个类也就是Android应用程序资源管理器,也就是咱们这篇文章真正的主角,主要负责读取资源文件。在刚才分析Resources对象建立过程当中讲到ContextImpl类里有一个getResources()方法,这里的AssetManager对象也是同样,经过ContextImpl里的getAssets()方法获取实例。 接下来来看AssetManager.java的getResourceValue方法

final boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue,
            boolean resolveRefs) {
        synchronized (this) {
            final int block = loadResourceValue(resId, (short) densityDpi, outValue, resolveRefs);
            if (block < 0) {
                return false;
            }

            // Convert the changing configurations flags populated by native code.
            outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
                    outValue.changingConfigurations);

            if (outValue.type == TypedValue.TYPE_STRING) {
                outValue.string = mStringBlocks[block].get(outValue.data);
            }
            return true;
        }
    }
复制代码

.这里能够看到AssetManager经过调用loadResourceValue方法去加载相对应资源id的资源,而且获取到一个int整型的block值。若是这个block大于等于0的话,就表示加载成功,而且会把结果保存在参数outValue所描述的一个TypedValue对象中。 接着咱们再进loadResourceValue方法:

private native final int loadResourceValue(int ident, short density, TypedValue outValue,
            boolean resolve);
复制代码

咱们发现这是一个jni方法,因此咱们就须要去看对应的c++方法了,源码地址在 android_util_AssetManager.cpp:

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);
}

复制代码

根据jni的抛出异常咱们能够看出这个方法里的逻辑就是整个的核心逻辑过程,这里咱们一步一步慢慢来分析整个过程,上面的代码大体能够分为如下几步: 一、首先调用assetManagerForJavaObject方法把java层AssetManager对象转换为C++层的AssetManager对象。 二、而后调用他的getResources方法获取到一个ResourceTable对象res,ResourceTable对象其实就是上篇资源打包文章中提到的资源表,它包括了全部的资源信息。 三、建立Res_value对象value,ResTable_config对象config,uint32_t类型typeSpecFlags,调用res的getResource方法获取相对应id的资源选项值以及它的配置信息,并保存下来 四、若是resolve是true的话,调用res的resolveReference方法来解析第3部中获取到的资源选项值和配置信息,若是获取到的block标记项为BAD_INDEX的话则抛出异常。 五、若是block大于等于0的话,则调用copyValue方法返回最终的资源内容。

这基本上就是C++层资源文件读取的核心逻辑过程,第1步建立过程就不详述了,主要来看2-5步,接下来让咱们深刻2-5的代码方法里来看apk是如何找到最适合当前本身的想要的资源选项的。 先来看第2步getResource方法:

const ResTable& AssetManager::getResources(bool required) const
{
    const ResTable* rt = getResTable(required);
    return *rt;
}
复制代码

这里咱们看到调用了getResTable方法来获取ResTable对象的,咱们再进getResTable方法:

const ResTable* AssetManager::getResTable(bool required) const
{
    ResTable* rt = mResources;
    if (rt) {
        return rt;
    }

    // Iterate through all asset packages, collecting resources from each.

    AutoMutex _l(mLock);

    if (mResources != NULL) {
        return mResources;
    }

    if (required) {
        LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");
    }

    mResources = new ResTable();
    updateResourceParamsLocked();

    bool onlyEmptyResources = true;
    const size_t N = mAssetPaths.size();
    for (size_t i=0; i<N; i++) {
        bool empty = appendPathToResTable(mAssetPaths.itemAt(i));
        onlyEmptyResources = onlyEmptyResources && empty;
    }

    if (required && onlyEmptyResources) {
        ALOGW("Unable to find resources file resources.arsc");
        delete mResources;
        mResources = NULL;
    }

    return mResources;
}
复制代码

这里咱们看到首先建立了一个ResTable对象rt,指向了mResources,若是不为空则直接返回,这就说明该资源包里面的资源索引表resources.arsc已经被解析过了,若是为空,那么就会去建立一个自动的互斥锁,在Android的native源代码里你会常常看到AutoMutex _l(mLock);,这是什么意思呢?AutoMutex其实就是Thread的一种自动的互斥锁,在函数代码中使用 AutoMutex 就能够锁定对象,而代码执行完AutoMutex所在的代码域以后,就自动释放锁。这里用互斥锁锁上的目的是为了防止其余线程也去解析当前资源包里的resources.arsc资源索引表,因此接下来会再次进行判断,是否为空的状况。 若是仍是为空的话,那么接下来就会去解析该资源包里的resources.arsc了。 在上一篇将资源文件打包到apk的文章中,咱们已经知道一个apk不只要去访问本身的资源文件,还要去访问系统的资源文件,因此一个应用程序要使用的资源包通常是2个,一个是系统资源包,一个是本身的apk文件,固然这里也有特殊状况,阿里sophix在资源替换上的方案是建立了一个新的资源包,后面咱们会分析到,这里先不提。而这些资源包的路径都会保存在AssetManager的成员变量mAssetPaths里。 这里对mAssetPaths里的每一个资源包循环调用appendPathToResTable方法获取一个布尔型的empty值,接下来咱们就进appendPathToResTable方法看一下:

bool AssetManager::appendPathToResTable(const asset_path& ap, bool appAsLib) const {
    // skip those ap's that correspond to system overlays if (ap.isSystemOverlay) { return true; } Asset* ass = NULL; ResTable* sharedRes = NULL; bool shared = true; bool onlyEmptyResources = true; ATRACE_NAME(ap.path.string()); Asset* idmap = openIdmapLocked(ap); size_t nextEntryIdx = mResources->getTableCount(); ALOGV("Looking for resource asset in '%s'\n", ap.path.string()); if (ap.type != kFileTypeDirectory) { if (nextEntryIdx == 0) { // The first item is typically the framework resources, // which we want to avoid parsing every time. sharedRes = const_cast<AssetManager*>(this)-> mZipSet.getZipResourceTable(ap.path); if (sharedRes != NULL) { // skip ahead the number of system overlay packages preloaded nextEntryIdx = sharedRes->getTableCount(); } } if (sharedRes == NULL) { ass = const_cast<AssetManager*>(this)-> mZipSet.getZipResourceTableAsset(ap.path); if (ass == NULL) { ALOGV("loading resource table %s\n", ap.path.string()); ass = const_cast<AssetManager*>(this)-> openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, ap); if (ass != NULL && ass != kExcludedAsset) { ass = const_cast<AssetManager*>(this)-> mZipSet.setZipResourceTableAsset(ap.path, ass); } } if (nextEntryIdx == 0 && ass != NULL) { // If this is the first resource table in the asset // manager, then we are going to cache it so that we // can quickly copy it out for others. ALOGV("Creating shared resources for %s", ap.path.string()); sharedRes = new ResTable(); sharedRes->add(ass, idmap, nextEntryIdx + 1, false); #ifdef __ANDROID__ const char* data = getenv("ANDROID_DATA"); LOG_ALWAYS_FATAL_IF(data == NULL, "ANDROID_DATA not set"); String8 overlaysListPath(data); overlaysListPath.appendPath(kResourceCache); overlaysListPath.appendPath("overlays.list"); addSystemOverlays(overlaysListPath.string(), ap.path, sharedRes, nextEntryIdx); #endif sharedRes = const_cast<AssetManager*>(this)-> mZipSet.setZipResourceTable(ap.path, sharedRes); } } } else { ALOGV("loading resource table %s\n", ap.path.string()); ass = const_cast<AssetManager*>(this)-> openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, ap); shared = false; } if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) { ALOGV("Installing resource asset %p in to table %p\n", ass, mResources); if (sharedRes != NULL) { ALOGV("Copying existing resources for %s", ap.path.string()); mResources->add(sharedRes, ap.isSystemAsset); } else { ALOGV("Parsing resources for %s", ap.path.string()); mResources->add(ass, idmap, nextEntryIdx + 1, !shared, appAsLib, ap.isSystemAsset); } onlyEmptyResources = false; if (!shared) { delete ass; } } else { ALOGV("Installing empty resources in to table %p\n", mResources); mResources->addEmpty(nextEntryIdx + 1); } if (idmap != NULL) { delete idmap; } return onlyEmptyResources; } 复制代码

这个方法比较长,让咱们一点点来分析,前面都是一些初始化的操做,先判断是不是系统资源包,若是是系统资源包,则直接调用AssetManager对象的成员变量mZipSet的方法getZipResourceTable方法直接获取对应的ResTable对象,为了不重复加载解析系统资源包的状况。 若是不是系统资源包的话,则会先去调用AssetManager对象的成员变量mZipSet的方法getZipResourceTableAsset先获取到一个Asset对象,固然这是针对已经资源包下的resources.arsc已经提取出来的状况,若是没有提取过的话那么ass就为空,就会去加载该资源包路径下的resources.arsc,经过AssetManager对象的openNonAssetInPathLocked方法读取到rsources.arsc保存到Asset对象ass中。 以后的if判断又回到系统资源包,若是是系统资源包的话,也就是nextEntryIndx等于0的状况下,则会进行一系列的缓存操做,以便可以快速地将其复制出来供其余地方使用。这里须要注意系统资源包有一个overlay机制,也就是资源覆盖机制,手机厂商能够利用这个机制来自定义的系统资源以覆盖系统默认的系统资源,达到个性化系统界面的目的。这里会经过addSystemOverlays方法来更新系统资源包,这里就不详述了。 这以后就是把asset对象加载到ResTable对象mResources,这里若是sharedRes不等于null的话,也就是这是一个系统资源包,会经过复制的方式调用add方法把asset对象加载到ResTable的mResources对象中,若是不是的话则会经过解析的方式add进去。接下来咱们跟下面的add方法进去看一看,代码在ResourceTypes.cpp里:

status_t ResTable::add(
        Asset* asset, Asset* idmapAsset, const int32_t cookie, bool copyData,
        bool appAsLib, bool isSystemAsset) {
    const void* data = asset->getBuffer(true);
    if (data == NULL) {
        ALOGW("Unable to get buffer of resource asset file");
        return UNKNOWN_ERROR;
    }

    size_t idmapSize = 0;
    const void* idmapData = NULL;
    if (idmapAsset != NULL) {
        idmapData = idmapAsset->getBuffer(true);
        if (idmapData == NULL) {
            ALOGW("Unable to get buffer of idmap asset file");
            return UNKNOWN_ERROR;
        }
        idmapSize = static_cast<size_t>(idmapAsset->getLength());
    }

    return addInternal(data, static_cast<size_t>(asset->getLength()),
            idmapData, idmapSize, appAsLib, cookie, copyData, isSystemAsset);
}
复制代码
status_t ResTable::addInternal(const void* data, size_t dataSize, const void* idmapData, size_t idmapDataSize,
        bool appAsLib, const int32_t cookie, bool copyData, bool isSystemAsset)
{
    if (!data) {
        return NO_ERROR;
    }

    if (dataSize < sizeof(ResTable_header)) {
        ALOGE("Invalid data. Size(%d) is smaller than a ResTable_header(%d).",
                (int) dataSize, (int) sizeof(ResTable_header));
        return UNKNOWN_ERROR;
    }

    Header* header = new Header(this);
    header->index = mHeaders.size();
    header->cookie = cookie;
    if (idmapData != NULL) {
        header->resourceIDMap = (uint32_t*) malloc(idmapDataSize);
        if (header->resourceIDMap == NULL) {
            delete header;
            return (mError = NO_MEMORY);
        }
        memcpy(header->resourceIDMap, idmapData, idmapDataSize);
        header->resourceIDMapSize = idmapDataSize;
    }
    mHeaders.add(header);

    const bool notDeviceEndian = htods(0xf0) != 0xf0;

    if (kDebugLoadTableNoisy) {
        ALOGV("Adding resources to ResTable: data=%p, size=%zu, cookie=%d, copy=%d "
                "idmap=%p\n", data, dataSize, cookie, copyData, idmapData);
    }

    if (copyData || notDeviceEndian) {
        header->ownedData = malloc(dataSize);
        if (header->ownedData == NULL) {
            return (mError=NO_MEMORY);
        }
        memcpy(header->ownedData, data, dataSize);
        data = header->ownedData;
    }

    header->header = (const ResTable_header*)data;
    header->size = dtohl(header->header->header.size);
    if (kDebugLoadTableSuperNoisy) {
        ALOGI("Got size %zu, again size 0x%x, raw size 0x%x\n", header->size,
                dtohl(header->header->header.size), header->header->header.size);
    }
    if (kDebugLoadTableNoisy) {
        ALOGV("Loading ResTable @%p:\n", header->header);
    }
    if (dtohs(header->header->header.headerSize) > header->size
            || header->size > dataSize) {
        ALOGW("Bad resource table: header size 0x%x or total size 0x%x is larger than data size 0x%x\n",
             (int)dtohs(header->header->header.headerSize),
             (int)header->size, (int)dataSize);
        return (mError=BAD_TYPE);
    }
    if (((dtohs(header->header->header.headerSize)|header->size)&0x3) != 0) {
        ALOGW("Bad resource table: header size 0x%x or total size 0x%x is not on an integer boundary\n",
             (int)dtohs(header->header->header.headerSize),
             (int)header->size);
        return (mError=BAD_TYPE);
    }
    header->dataEnd = ((const uint8_t*)header->header) + header->size;

    // Iterate through all chunks.
    size_t curPackage = 0;

    const ResChunk_header* chunk =
        (const ResChunk_header*)(((const uint8_t*)header->header)
                                 + dtohs(header->header->header.headerSize));
    while (((const uint8_t*)chunk) <= (header->dataEnd-sizeof(ResChunk_header)) &&
           ((const uint8_t*)chunk) <= (header->dataEnd-dtohl(chunk->size))) {
        status_t err = validate_chunk(chunk, sizeof(ResChunk_header), header->dataEnd, "ResTable");
        if (err != NO_ERROR) {
            return (mError=err);
        }
        if (kDebugTableNoisy) {
            ALOGV("Chunk: type=0x%x, headerSize=0x%x, size=0x%x, pos=%p\n",
                    dtohs(chunk->type), dtohs(chunk->headerSize), dtohl(chunk->size),
                    (void*)(((const uint8_t*)chunk) - ((const uint8_t*)header->header)));
        }
        const size_t csize = dtohl(chunk->size);
        const uint16_t ctype = dtohs(chunk->type);
        if (ctype == RES_STRING_POOL_TYPE) {
            if (header->values.getError() != NO_ERROR) {
                // Only use the first string chunk; ignore any others that
                // may appear.
                status_t err = header->values.setTo(chunk, csize);
                if (err != NO_ERROR) {
                    return (mError=err);
                }
            } else {
                ALOGW("Multiple string chunks found in resource table.");
            }
        } else if (ctype == RES_TABLE_PACKAGE_TYPE) {
            if (curPackage >= dtohl(header->header->packageCount)) {
                ALOGW("More package chunks were found than the %d declared in the header.",
                     dtohl(header->header->packageCount));
                return (mError=BAD_TYPE);
            }

            if (parsePackage(
                    (ResTable_package*)chunk, header, appAsLib, isSystemAsset) != NO_ERROR) {
                return mError;
            }
            curPackage++;
        } else {
            ALOGW("Unknown chunk type 0x%x in table at %p.\n",
                 ctype,
                 (void*)(((const uint8_t*)chunk) - ((const uint8_t*)header->header)));
        }
        chunk = (const ResChunk_header*)
            (((const uint8_t*)chunk) + csize);
    }

    if (curPackage < dtohl(header->header->packageCount)) {
        ALOGW("Fewer package chunks (%d) were found than the %d declared in the header.",
             (int)curPackage, dtohl(header->header->packageCount));
        return (mError=BAD_TYPE);
    }
    mError = header->values.getError();
    if (mError != NO_ERROR) {
        ALOGW("No string values found in resource table!");
    }

    if (kDebugTableNoisy) {
        ALOGV("Returning from add with mError=%d\n", mError);
    }
    return mError;
}
复制代码

这里咱们能够看到add方法进去之后职责基本上就是Asset对象所描述的resources.arsc文件的内容进行解析,解析最后会获得一个系列的Package信息。每个Package又包含了一个资源类型字符串资源池和一个资源项名称字符串资源池,以及一系列的资源类型规范数据块和一系列的资源项数据块。解析过程这里就不详述了,整个resources.arsc的解析过程在上一篇Android资源编译和打包过程分析已经有详述了,不清楚的朋友能够先去看看上一篇。 ok这里咱们就已经把上面的AssetManager的getResource方法弄通了,获得了一个资源表ResTable对象。咱们回上去,回到android_content_AssetManager_loadResourceValue方法,咱们看到这一步以后建立了Res_value对象value,ResTable_config对象config,uint32_t类型typeSpecFlags,调用刚获取到的RestTable的对象res的getResource方法获取相对应id的资源选项值以及它的配置信息,并保存下来。 咱们进res的getResource方法继续探究:

ssize_t ResTable::getResource(uint32_t resID, Res_value* outValue, bool mayBeBag, uint16_t density,
        uint32_t* outSpecFlags, ResTable_config* outConfig) const
{
    if (mError != NO_ERROR) {
        return mError;
    }

    const ssize_t p = getResourcePackageIndex(resID);
    const int t = Res_GETTYPE(resID);
    const int e = Res_GETENTRY(resID);

    if (p < 0) {
        if (Res_GETPACKAGE(resID)+1 == 0) {
            ALOGW("No package identifier when getting value for resource number 0x%08x", resID);
        } else {
            ALOGW("No known package when getting value for resource number 0x%08x", resID);
        }
        return BAD_INDEX;
    }
    if (t < 0) {
        ALOGW("No type identifier when getting value for resource number 0x%08x", resID);
        return BAD_INDEX;
    }

    const PackageGroup* const grp = mPackageGroups[p];
    if (grp == NULL) {
        ALOGW("Bad identifier when getting value for resource number 0x%08x", resID);
        return BAD_INDEX;
    }

    // Allow overriding density
    ResTable_config desiredConfig = mParams;
    if (density > 0) {
        desiredConfig.density = density;
    }

    Entry entry;
    status_t err = getEntry(grp, t, e, &desiredConfig, &entry);
    if (err != NO_ERROR) {
        // Only log the failure when we're not running on the host as // part of a tool. The caller will do its own logging. #ifndef STATIC_ANDROIDFW_FOR_TOOLS ALOGW("Failure getting entry for 0x%08x (t=%d e=%d) (error %d)\n", resID, t, e, err); #endif return err; } if ((dtohs(entry.entry->flags) & ResTable_entry::FLAG_COMPLEX) != 0) { if (!mayBeBag) { ALOGW("Requesting resource 0x%08x failed because it is complex\n", resID); } return BAD_VALUE; } const Res_value* value = reinterpret_cast<const Res_value*>( reinterpret_cast<const uint8_t*>(entry.entry) + entry.entry->size); outValue->size = dtohs(value->size); outValue->res0 = value->res0; outValue->dataType = value->dataType; outValue->data = dtohl(value->data); // The reference may be pointing to a resource in a shared library. These // references have build-time generated package IDs. These ids may not match // the actual package IDs of the corresponding packages in this ResTable. // We need to fix the package ID based on a mapping. if (grp->dynamicRefTable.lookupResourceValue(outValue) != NO_ERROR) { ALOGW("Failed to resolve referenced package: 0x%08x", outValue->data); return BAD_VALUE; } if (kDebugTableNoisy) { size_t len; printf("Found value: pkg=%zu, type=%d, str=%s, int=%d\n", entry.package->header->index, outValue->dataType, outValue->dataType == Res_value::TYPE_STRING ? String8(entry.package->header->values.stringAt(outValue->data, &len)).string() : "", outValue->data); } if (outSpecFlags != NULL) { *outSpecFlags = entry.specFlags; } if (outConfig != NULL) { *outConfig = entry.config; } return entry.package->header->index; } 复制代码

先来看传进来的参数,resId是要去寻找资源的资源id,uint32_t* outSpecFlags, ResTable_config* outConfig都是以前新建的两个对象,density往上看过去应该是getDrawableForDensity方法里传进来的0。 首先分别调用getResourcePackageIndex方法,Res_GETTYPE方法,Res_GETENTRY方法获取到该资源id的packageId,typeId和entryId。而后根据这个packageid获取到了一个packageGroup,而后拿这些数据去调用getEntry方法,接下来咱们来深究下getEntry方法:

status_t ResTable::getEntry(
        const PackageGroup* packageGroup, int typeIndex, int entryIndex,
        const ResTable_config* config,
        Entry* outEntry) const
{
    const TypeList& typeList = packageGroup->types[typeIndex];
    if (typeList.isEmpty()) {
        ALOGV("Skipping entry type index 0x%02x because type is NULL!\n", typeIndex);
        return BAD_TYPE;
    }

    const ResTable_type* bestType = NULL;
    uint32_t bestOffset = ResTable_type::NO_ENTRY;
    const Package* bestPackage = NULL;
    uint32_t specFlags = 0;
    uint8_t actualTypeIndex = typeIndex;
    ResTable_config bestConfig;
    memset(&bestConfig, 0, sizeof(bestConfig));

    // Iterate over the Types of each package.
    const size_t typeCount = typeList.size();
    for (size_t i = 0; i < typeCount; i++) {
        const Type* const typeSpec = typeList[i];

        int realEntryIndex = entryIndex;
        int realTypeIndex = typeIndex;
        bool currentTypeIsOverlay = false;

        // Runtime overlay packages provide a mapping of app resource
        // ID to package resource ID.
        if (typeSpec->idmapEntries.hasEntries()) {
            uint16_t overlayEntryIndex;
            if (typeSpec->idmapEntries.lookup(entryIndex, &overlayEntryIndex) != NO_ERROR) {
                // No such mapping exists
                continue;
            }
            realEntryIndex = overlayEntryIndex;
            realTypeIndex = typeSpec->idmapEntries.overlayTypeId() - 1;
            currentTypeIsOverlay = true;
        }

        // Check that the entry idx is within range of the declared entry count (ResTable_typeSpec).
        // Particular types (ResTable_type) may be encoded with sparse entries, and so their
        // entryCount do not need to match.
        if (static_cast<size_t>(realEntryIndex) >= typeSpec->entryCount) {
            ALOGW("For resource 0x%08x, entry index(%d) is beyond type entryCount(%d)",
                    Res_MAKEID(packageGroup->id - 1, typeIndex, entryIndex),
                    entryIndex, static_cast<int>(typeSpec->entryCount));
            // We should normally abort here, but some legacy apps declare
            // resources in the 'android' package (old bug in AAPT).
            continue;
        }

        // Aggregate all the flags for each package that defines this entry.
        if (typeSpec->typeSpecFlags != NULL) {
            specFlags |= dtohl(typeSpec->typeSpecFlags[realEntryIndex]);
        } else {
            specFlags = -1;
        }

        const Vector<const ResTable_type*>* candidateConfigs = &typeSpec->configs;

        std::shared_ptr<Vector<const ResTable_type*>> filteredConfigs;
        if (config && memcmp(&mParams, config, sizeof(mParams)) == 0) {
            // Grab the lock first so we can safely get the current filtered list.
            AutoMutex _lock(mFilteredConfigLock);

            // This configuration is equal to the one we have previously cached for,
            // so use the filtered configs.

            const TypeCacheEntry& cacheEntry = packageGroup->typeCacheEntries[typeIndex];
            if (i < cacheEntry.filteredConfigs.size()) {
                if (cacheEntry.filteredConfigs[i]) {
                    // Grab a reference to the shared_ptr so it doesn't get destroyed while // going through this list. filteredConfigs = cacheEntry.filteredConfigs[i]; // Use this filtered list. candidateConfigs = filteredConfigs.get(); } } } const size_t numConfigs = candidateConfigs->size(); for (size_t c = 0; c < numConfigs; c++) { const ResTable_type* const thisType = candidateConfigs->itemAt(c); if (thisType == NULL) { continue; } ResTable_config thisConfig; thisConfig.copyFromDtoH(thisType->config); // Check to make sure this one is valid for the current parameters. if (config != NULL && !thisConfig.match(*config)) { continue; } const uint32_t* const eindex = reinterpret_cast<const uint32_t*>( reinterpret_cast<const uint8_t*>(thisType) + dtohs(thisType->header.headerSize)); uint32_t thisOffset; // Check if there is the desired entry in this type. if (thisType->flags & ResTable_type::FLAG_SPARSE) { // This is encoded as a sparse map, so perform a binary search. const ResTable_sparseTypeEntry* sparseIndices = reinterpret_cast<const ResTable_sparseTypeEntry*>(eindex); const ResTable_sparseTypeEntry* result = std::lower_bound( sparseIndices, sparseIndices + dtohl(thisType->entryCount), realEntryIndex, keyCompare); if (result == sparseIndices + dtohl(thisType->entryCount) || dtohs(result->idx) != realEntryIndex) { // No entry found. continue; } // Extract the offset from the entry. Each offset must be a multiple of 4 // so we store it as the real offset divided by 4. thisOffset = dtohs(result->offset) * 4u; } else { if (static_cast<uint32_t>(realEntryIndex) >= dtohl(thisType->entryCount)) { // Entry does not exist. continue; } thisOffset = dtohl(eindex[realEntryIndex]); } if (thisOffset == ResTable_type::NO_ENTRY) { // There is no entry for this index and configuration. continue; } if (bestType != NULL) { // Check if this one is less specific than the last found. If so, // we will skip it. We check starting with things we most care // about to those we least care about. if (!thisConfig.isBetterThan(bestConfig, config)) { if (!currentTypeIsOverlay || thisConfig.compare(bestConfig) != 0) { continue; } } } bestType = thisType; bestOffset = thisOffset; bestConfig = thisConfig; bestPackage = typeSpec->package; actualTypeIndex = realTypeIndex; // If no config was specified, any type will do, so skip if (config == NULL) { break; } } } if (bestType == NULL) { return BAD_INDEX; } bestOffset += dtohl(bestType->entriesStart); if (bestOffset > (dtohl(bestType->header.size)-sizeof(ResTable_entry))) { ALOGW("ResTable_entry at 0x%x is beyond type chunk data 0x%x", bestOffset, dtohl(bestType->header.size)); return BAD_TYPE; } if ((bestOffset & 0x3) != 0) { ALOGW("ResTable_entry at 0x%x is not on an integer boundary", bestOffset); return BAD_TYPE; } const ResTable_entry* const entry = reinterpret_cast<const ResTable_entry*>( reinterpret_cast<const uint8_t*>(bestType) + bestOffset); if (dtohs(entry->size) < sizeof(*entry)) { ALOGW("ResTable_entry size 0x%x is too small", dtohs(entry->size)); return BAD_TYPE; } if (outEntry != NULL) { outEntry->entry = entry; outEntry->config = bestConfig; outEntry->type = bestType; outEntry->specFlags = specFlags; outEntry->package = bestPackage; outEntry->typeStr = StringPoolRef(&bestPackage->typeStrings, actualTypeIndex - bestPackage->typeIdOffset); outEntry->keyStr = StringPoolRef(&bestPackage->keyStrings, dtohl(entry->key.index)); } return NO_ERROR; } 复制代码

getEntry方法很长,简而言之就是为了从一个packageGroup里找到一个指定typeId,entryId和最符合当前配置信息的资源项。 首先从packageGroup中获取到对应的typeList,由于上面获取到的packgroup可能含有多个package,因此这里有一个for循环在每个package中查找最合适的资源选项。 接着来看这个for循环的内容。for循环获取到每一个package的type,由于在一个packageGroup中,第一个永远都是一开始的基package,后面的都是overlay package,若是这个资源类型规范type指向的是idmapEntries的话,就会把realEntryntryIndex,realTypeIndex指向overlay package下提供的index,在运行的时候overlay package是提供应用程序资源ID到包资源ID的映射的。 在上一篇中咱们知道ResTable_type结构体中会有一个类型为unit32_t的偏移数组。而这个偏移数组的第entryIndex个元素的值就表示Entry ID等于entryIndex的资源项的具体内容相对于ResTable_type结构体的偏移值。因此这里咱们根据realEntryIndex就能够得到这个资源项的具体内容相对于ResTable_type结构体的偏移值。 以后就会去比较ResTable_type里的配置信息和config参数里配置的信息哪一个更匹配一点,以此去拿到那个最合适的资源,而后保存在bestType,bestOffset,bsetConfig,bestPackage字段中,并返回给上一层。这样就经过getEntry方法获取到了最匹配现有配置信息的资源了。 这以后,咱们再回上去,回到android_content_AssetManager_loadResourceValue方法,这以后就是调用resolveReference方法来解析上面获取到的资源选项值和配置信息了,那让咱们来看resolveReference方法:

ssize_t ResTable::resolveReference(Res_value* value, ssize_t blockIndex,
        uint32_t* outLastRef, uint32_t* inoutTypeSpecFlags,
        ResTable_config* outConfig) const
{
    int count=0;
    while (blockIndex >= 0 && value->dataType == Res_value::TYPE_REFERENCE
            && value->data != 0 && count < 20) {
        if (outLastRef) *outLastRef = value->data;
        uint32_t newFlags = 0;
        const ssize_t newIndex = getResource(value->data, value, true, 0, &newFlags,
                outConfig);
        if (newIndex == BAD_INDEX) {
            return BAD_INDEX;
        }
        if (kDebugTableTheme) {
            ALOGI("Resolving reference 0x%x: newIndex=%d, type=0x%x, data=0x%x\n",
                    value->data, (int)newIndex, (int)value->dataType, value->data);
        }
        //printf("Getting reference 0x%08x: newIndex=%d\n", value->data, newIndex);
        if (inoutTypeSpecFlags != NULL) *inoutTypeSpecFlags |= newFlags;
        if (newIndex < 0) {
            // This can fail if the resource being referenced is a style...
            // in this case, just return the reference, and expect the
            // caller to deal with.
            return blockIndex;
        }
        blockIndex = newIndex;
        count++;
    }
    return blockIndex;
}
复制代码

这里咱们看到咱们把获取到的Res_value对象不断的循环嗲用ResTable的getResource方法来进行解析,getResource方法干吗的上面咱们已经讲过了。不断循环的条件是若是这个value的dataType值等于TYPE_REFERENCE的话,这个意思就是这个value它只是一个引用。咱们也看到了这里限制了循环20次,防止无限循环。

经过resolveReference方法获取到了blockIndex值后,会进行断定,若是blockIndex 大于等于0的话,则会调用copyValue方法返回最终的资源内容:

jint copyValue(JNIEnv* env, jobject outValue, const ResTable* table,
               const Res_value& value, uint32_t ref, ssize_t block,
               uint32_t typeSpecFlags, ResTable_config* config)
{
    env->SetIntField(outValue, gTypedValueOffsets.mType, value.dataType);
    env->SetIntField(outValue, gTypedValueOffsets.mAssetCookie,
                     static_cast<jint>(table->getTableCookie(block)));
    env->SetIntField(outValue, gTypedValueOffsets.mData, value.data);
    env->SetObjectField(outValue, gTypedValueOffsets.mString, NULL);
    env->SetIntField(outValue, gTypedValueOffsets.mResourceId, ref);
    env->SetIntField(outValue, gTypedValueOffsets.mChangingConfigurations,
            typeSpecFlags);
    if (config != NULL) {
        env->SetIntField(outValue, gTypedValueOffsets.mDensity, config->density);
    }
    return block;
}
复制代码

接下来咱们能够回到getDrawableForDensity方法了,在分析完ResourcesImpl的getValueForDensity方法的整个流程之后,return调用的是ResourcesImpl对象的loadDrawable方法去得到Drawable对象,根据咱们获取到的资源内容,去根据他的type去获取对应的drawable。

ok,到这里咱们总算把这个过程所有理完了,上面的问题咱们也基本理通了。。。。 在理清了资源文件打包的时候是如何打包到apk里和apk运行的时候是如何读取资源文件的过程之后,咱们就能够作以后的探索了,根据咱们理的整个过程,咱们怎么样操做下才能够实现资源线上热修复的功能。。。

本系列目录: Android热修复原理简要介绍和学习计划

我的微信公共帐号已上线,欢迎关注:

在这里插入图片描述
相关文章
相关标签/搜索