Resources原理

1,249 阅读3分钟

Resources简介

Resources是FrameWork层提供给用户访问应用资源的类。这个类是在应用的AssetManager之上,并且提供了高层Api让用户能够从资源中获取各类型数据。

Android 资源系统会一直追踪所有跟Application关联的无代码资源。你可以通过android.content.Context#getResources getResources()获取Application 相关的资源Resources,来使用我们的Application资源。

下面我们将了解下Resources的加载流程,首先看下他们的关系图

Resources 加载流程

ContextImpl的创建

static ContextImpl createActivityContext(ActivityThread mainThread,
        LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
        Configuration overrideConfiguration) {
    
		//1.传教ContextImpl 实例
    ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName,
            activityToken, null, 0, classLoader);

    // Clamp display ID to DEFAULT_DISPLAY if it is INVALID_DISPLAY.
    displayId = (displayId != Display.INVALID_DISPLAY) ? displayId : Display.DEFAULT_DISPLAY;

    final CompatibilityInfo compatInfo = (displayId == Display.DEFAULT_DISPLAY)
            ? packageInfo.getCompatibilityInfo()
            : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;

  	//2.获取ResourcesManager 实例,用来创建Resources
    final ResourcesManager resourcesManager = ResourcesManager.getInstance();

    // Create the base resources for which all configuration contexts for this Activity
    // will be rebased upon.
  	//3.创建resources并设置给给当前Activity的context
    context.setResources(resourcesManager.createBaseActivityResources(activityToken,
            packageInfo.getResDir(),
            splitDirs,
            packageInfo.getOverlayDirs(),
            packageInfo.getApplicationInfo().sharedLibraryFiles,
            displayId,
            overrideConfiguration,
            compatInfo,
            classLoader));
    context.mDisplay = resourcesManager.getAdjustedDisplay(displayId,
            context.getResources());
    return context;
}

在ContextImpl类中,静态方法创建ContextImpl实例时会给ContextImpl实例设置Resources,而创建Resources的工作就交给我们的ResourcesManager来完成

ResourcesManager

在ResourcesManager中有几个成员变量很重要,所以这里先讲下

ResourcesManager成员变量
//每个相同tokn的Activity都有一个基本Config对应多个资源Resources,而资源对象又可能指定了它们自己的config。
private final WeakHashMap<IBinder, ActivityResources> mActivityResourceReferences =
  	new WeakHashMap<>();
//存放ResourcesImpl实例,因为ResourcesImpl是一个很有重量级的对象,所以需要重复使用
private final ArrayMap<ResourcesKey, WeakReference<ResourcesImpl>> mResourceImpls =
  	new ArrayMap<>();
//Resources 资源列表,存放跟activity 无关联的资源,例如Application的Resources
private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>();
ActivityResources

可以从数据类型上看出来,Activity和Resources关系是一对多,每个相同token的Activity可能有不同的Config所以对应多个Resourcess

private static class ActivityResources {
    public final Configuration overrideConfig = new Configuration();
    public final ArrayList<WeakReference<Resources>> activityResources = new ArrayList<>();
}
ResourcesKey

用于ResourcesImpl实例一一对应的key,ResourcesKey比对的是资源路径,Configuration 等参数

public ResourcesKey(@Nullable String resDir,
                    @Nullable String[] splitResDirs,
                    @Nullable String[] overlayDirs,
                    @Nullable String[] libDirs,
                    int displayId,
                    @Nullable Configuration overrideConfig,
                    @Nullable CompatibilityInfo compatInfo) {
    mResDir = resDir;
    mSplitResDirs = splitResDirs;
    mOverlayDirs = overlayDirs;
    mLibDirs = libDirs;
    mDisplayId = displayId;
    mOverrideConfiguration = new Configuration(overrideConfig != null
            ? overrideConfig : Configuration.EMPTY);
    mCompatInfo = compatInfo != null ? compatInfo : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;

    int hash = 17;
    hash = 31 * hash + Objects.hashCode(mResDir);
    hash = 31 * hash + Arrays.hashCode(mSplitResDirs);
    hash = 31 * hash + Arrays.hashCode(mOverlayDirs);
    hash = 31 * hash + Arrays.hashCode(mLibDirs);
    hash = 31 * hash + mDisplayId;
    hash = 31 * hash + Objects.hashCode(mOverrideConfiguration);
    hash = 31 * hash + Objects.hashCode(mCompatInfo);
    mHash = hash;
}
ResourcesImpl

存放在ResourcesManager中的mResourceImpls局部变量中,这是一个重量级的对象,所以必须要高复用,他跟ResourcesKey相关联

Resources 创建流程

ContextImpl创建时会调用ResourcesManager中的createBaseActivityResources创建Resources

public @Nullable Resources createBaseActivityResources(@NonNull IBinder activityToken,
        @Nullable String resDir,
        @Nullable String[] splitResDirs,
        @Nullable String[] overlayDirs,
        @Nullable String[] libDirs,
        int displayId,
        @Nullable Configuration overrideConfig,
        @NonNull CompatibilityInfo compatInfo,
        @Nullable ClassLoader classLoader) {
    try {
        Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
                "ResourcesManager#createBaseActivityResources");
      	//1.根据资源路径等相关属性参数创建 ResourcesKey
        final ResourcesKey key = new ResourcesKey(
                resDir,
                splitResDirs,
                overlayDirs,
                libDirs,
                displayId,
                overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
                compatInfo);
        classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();


        synchronized (this) {
            // Force the creation of an ActivityResourcesStruct.
          	//2.根据activityToken 创建对应ActivityResources(如果没有创建过)
            getOrCreateActivityResourcesStructLocked(activityToken);
        }

        // Update any existing Activity Resources references.
     		//3.更新AcitivityResources资源
        updateResourcesForActivity(activityToken, overrideConfig, displayId,
                false /* movedToDifferentDisplay */);

        // Now request an actual Resources object.
      	//4.获取或创建资源
        return getOrCreateResources(activityToken, key, classLoader);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
    }
}
getOrCreateActivityResourcesStructLocked

非常简单的逻辑,从mActivityResourceReferences成员变量中取缓存,如果没有就创建新的实例放到缓存中

private ActivityResources getOrCreateActivityResourcesStructLocked(
        @NonNull IBinder activityToken) {
    ActivityResources activityResources = mActivityResourceReferences.get(activityToken);
    if (activityResources == null) {
        activityResources = new ActivityResources();
        mActivityResourceReferences.put(activityToken, activityResources);
    }
    return activityResources;
}
updateResourcesForActivity
public void updateResourcesForActivity(@NonNull IBinder activityToken,
        @Nullable Configuration overrideConfig, int displayId,
        boolean movedToDifferentDisplay) {
    try {
        Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
                "ResourcesManager#updateResourcesForActivity");
        synchronized (this) {
          	//获取缓存的activityResources
            final ActivityResources activityResources =
                    getOrCreateActivityResourcesStructLocked(activityToken);

            if (Objects.equals(activityResources.overrideConfig, overrideConfig)
                    && !movedToDifferentDisplay) {
              	//它们是相同的,没有显示ID的变化,没有工作要做。
                return;
            }

            // Grab a copy of the old configuration so we can create the delta's of each
            // Resources object associated with this Activity.
            final Configuration oldConfig = new Configuration(activityResources.overrideConfig);

            // 更新activityResources 的 base Config
            if (overrideConfig != null) {
                activityResources.overrideConfig.setTo(overrideConfig);
            } else {
                activityResources.overrideConfig.unset();
            }

            final boolean activityHasOverrideConfig =
                    !activityResources.overrideConfig.equals(Configuration.EMPTY);

            // Rebase each Resources associated with this Activity.
          	//重定这个activity 相关联的所有资源
            final int refCount = activityResources.activityResources.size();
            for (int i = 0; i < refCount; i++) {
                WeakReference<Resources> weakResRef = activityResources.activityResources.get(
                        i);
                Resources resources = weakResRef.get();
                if (resources == null) {
                    continue;
                }

                //从mResourceImpls中获取对应ResourceImpl的key
                final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
                if (oldKey == null) {
                    Slog.e(TAG, "can't find ResourcesKey for resources impl="
                            + resources.getImpl());
                    continue;
                }

                // Build the new override configuration for this ResourcesKey.
                final Configuration rebasedOverrideConfig = new Configuration();
                if (overrideConfig != null) {
                    rebasedOverrideConfig.setTo(overrideConfig);
                }

                if (activityHasOverrideConfig && oldKey.hasOverrideConfiguration()) {
                    // Generate a delta between the old base Activity override configuration and
                    // the actual final override configuration that was used to figure out the
                    // real delta this Resources object wanted.
                    Configuration overrideOverrideConfig = Configuration.generateDelta(
                            oldConfig, oldKey.mOverrideConfiguration);
                    rebasedOverrideConfig.updateFrom(overrideOverrideConfig);
                }

                //根据新的config displayId 等参数创建新的key
                final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir,
                        oldKey.mSplitResDirs,
                        oldKey.mOverlayDirs, oldKey.mLibDirs, displayId,
                        rebasedOverrideConfig, oldKey.mCompatInfo);

              	//根据新key从mResourceImpls中获取缓存ResourcesImpl
                ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(newKey);
                if (resourcesImpl == null) {
                    //5.根据新key创建新的ResourcesImpl
                    resourcesImpl = createResourcesImpl(newKey);
                    if (resourcesImpl != null) {
                        mResourceImpls.put(newKey, new WeakReference<>(resourcesImpl));
                    }
                }

                //将Resources和ResourcesImpl 关联上
                if (resourcesImpl != null && resourcesImpl != resources.getImpl()) {
                    resources.setImpl(resourcesImpl);
                }
            }
        }
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
    }
}

通过上面代码逻辑可以看出,同一个activityToken的ActivityResources会将里的所有resources关联上同一个ResourcesImpl实例。

getOrCreateResources
private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
        @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
    synchronized (this) {
        .....省略复用 ResourcesImpl 代码

        //如果执行到这一步,说明没有创建ResourcesImpl
        ResourcesImpl resourcesImpl = createResourcesImpl(key);
        if (resourcesImpl == null) {
            return null;
        }

        //resourcesImpl存入缓存
        mResourceImpls.put(key, new WeakReference<>(resourcesImpl));

        final Resources resources;
        if (activityToken != null) {
          	//从mActivityResourceReferences缓存取,没有就添加新的Resources到ActivityResource缓存中
            resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
                    resourcesImpl, key.mCompatInfo);
        } else {
          	//从 mResourceReferences 缓存取,没有就添加新的Resources到mResourceReferences缓存中
            resources = getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
        }
        return resources;
    }
}

AssetManager 创建过程

在我们开始讲解 AssetManager 创建的过程之前我们先看下 ResourcesManager 中 与 AssetManager相关的局部变量

ResourcesManager 成员变量
/**
 * The ApkAssets we are caching and intend to hold strong references to.
 * 这里使用LruCache缓存策略,缓存最近使用最频繁3个ApkAssets
 */
private final LruCache<ApkKey, ApkAssets> mLoadedApkAssets = new LruCache<>(3);

/**
 * The ApkAssets that are being referenced in the wild that we can reuse, even if they aren't
 * in our LRU cache. Bonus resources :)
 * 这个是对 mLoadedApkAssets 缓存的补充,当有超过3个 ApkAssets 需要缓存时这里会缓存超出限制的 ApkAssets
 */
private final ArrayMap<ApkKey, WeakReference<ApkAssets>> mCachedApkAssets = new ArrayMap<>();

ApkKey 用于ApkAssets实例一一对应的key。

ResourcesManager createResourcesImpl 创建ResourcesImpl
private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
    final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
    daj.setCompatibilityInfo(key.mCompatInfo);

  	//根据ResourcesKey创建AssetManager
    final AssetManager assets = createAssetManager(key);
    if (assets == null) {
        return null;
    }

    final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
    final Configuration config = generateConfig(key, dm);
  	//创建ResourcesImpl实例与
    final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);
    return impl;
}

从上面创建的代码中我们可以看出ResourcesImpl和AssetManager时一一对应的关系

createAssetManager 创建AssetManager
@VisibleForTesting
protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
    final AssetManager.Builder builder = new AssetManager.Builder();

    // resDir can be null if the 'android' package is creating a new Resources object.
    // This is fine, since each AssetManager automatically loads the 'android' package
    // already.
    if (key.mResDir != null) {
        try {
            builder.addApkAssets(loadApkAssets(key.mResDir, false /*sharedLib*/,
                    false /*overlay*/));
        } catch (IOException e) {
            return null;
        }
    }

    if (key.mSplitResDirs != null) {
        for (final String splitResDir : key.mSplitResDirs) {
            try {
                builder.addApkAssets(loadApkAssets(splitResDir, false /*sharedLib*/,
                        false /*overlay*/));
            } catch (IOException e) {
                return null;
            }
        }
    }

    if (key.mOverlayDirs != null) {
        for (final String idmapPath : key.mOverlayDirs) {
            try {
                builder.addApkAssets(loadApkAssets(idmapPath, false /*sharedLib*/,
                        true /*overlay*/));
            } catch (IOException e) {
            }
        }
    }

    if (key.mLibDirs != null) {
        for (final String libDir : key.mLibDirs) {
            if (libDir.endsWith(".apk")) {
                // Avoid opening files we know do not have resources,
                // like code-only .jar files.
                try {
                    builder.addApkAssets(loadApkAssets(libDir, true /*sharedLib*/,
                            false /*overlay*/));
                } catch (IOException e) {
                }
            }
        }
    }

    return builder.build();
}

通过AssetManager Build 设计模式,创建AssetManager实例。

loadApkAssets 加载ApkAssets
private @NonNull ApkAssets loadApkAssets(String path, boolean sharedLib, boolean overlay)
        throws IOException {
  	//生成ApkKey
    final ApkKey newKey = new ApkKey(path, sharedLib, overlay);
  	//从LRU mLoadedApkAssets缓存中取
    ApkAssets apkAssets = mLoadedApkAssets.get(newKey);
    if (apkAssets != null) {
        return apkAssets;
    }

    // Optimistically check if this ApkAssets exists somewhere else.
  	//从 mCachedApkAssets 取缓存
    final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(newKey);
    if (apkAssetsRef != null) {
        apkAssets = apkAssetsRef.get();
        if (apkAssets != null) {
            //刷新mLoadedApkAssets缓存
            mLoadedApkAssets.put(newKey, apkAssets);
            return apkAssets;
        } else {
            // Clean up the reference.
            // 不存在就清楚缓存
            mCachedApkAssets.remove(newKey);
        }
    }

    // We must load this from disk.
    // 缓存中都没取到 从磁盘中加载 ApkAssets
    if (overlay) {
        apkAssets = ApkAssets.loadOverlayFromPath(overlayPathToIdmapPath(path),
                false /*system*/);
    } else {
        apkAssets = ApkAssets.loadFromPath(path, false /*system*/, sharedLib);
    }
  	// 缓存ApkAssets
    mLoadedApkAssets.put(newKey, apkAssets);
    mCachedApkAssets.put(newKey, new WeakReference<>(apkAssets));
    return apkAssets;
}

小结

  • ResourcesManager 负责整个的加载逻辑控制,从Resources 到 AssetsManager创建流程。
  • app通过Resources获取资源,最终是通过 AssetsManager调用native方法来实现的。
  • AssetsManager是个重量级对象跟他相关联的对象ResourcesImpl 需要慎用,内存损耗较大