21 Android资源装载机制

作者: 凤邪摩羯 | 来源:发表于2021-03-05 09:14 被阅读0次

    前言

    本篇要分析的是在Android中资源的装载机制,例如字符串资源,图片资源是如何被装载的。这里将从字符串和图片两种类型资源展开分析,同时对于后面所利用的资源装载的内容也会做简单的分析。

    Resources源码剖析

    image

    对于资源的装载机制,这里核心的几个类是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函数的调用。

    @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) {
                  ....
            }
        }
    }
    
    
    image

    图片资源的装载流程是首先将根据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<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,将新增的路径的资源表添加到其中。

    根据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<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对象,如果不存在,则创建一个。

    小结

    image

    对于资源的加载,大概可以通过上图进行概括,根据ID获取TypedValue,TypedValue的获取是在AssetManager添加资源路径的时候,通过对资源表的解析来构建的一个ResTable,通过该数据结构根据ID作为索引查找并构建TypedValue,然后再根据资源文件的类型,借助TypedValue内存储的关于资源的详细信息来获取资源,同时将加载的资源进行缓存。因此在插件化的方案中,通过创建新的Resource对象,为其添加新的Asset路径,从而构建出一个新的ResTable,实现通过ID进行非宿主App资源的装载。

    补充:一个AssetManager对应了一个ResTable,没为AssetTable设置一个资源路径之后,都会对资源路径进行解析,将其中的资源读出来,所有加载的资源都会放置到该表之中,因此我们就可以对其来获取TypeValue,根据TypeValue来进行相应的资源的查找了。

    相关文章

      网友评论

        本文标题:21 Android资源装载机制

        本文链接:https://www.haomeiwen.com/subject/jupuaktx.html