在“外销项目的小语种bug热修复方案”一文中,我们使用了替换mResource的方法来实现资源及字符串的热修复
之前在写这篇文章的时候 也一直在想一些问题
比如:
1:
为什么Application的getResource以及各个Activity的getResource获取的mResource对象不是同一个对象
2:
为什么 Activity的getResources().getAssert() 都是同一个Assert对象
而Appication的getResources().getAssert() 与Activity的Assert对象 又不是同一个
先看Application 的 Resource 的创建流程
// 以下代码来自ContextImpl
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,
null);
context.setResources(packageInfo.getResources());
return context;
}
继续往下看
以下代码来自LoadedApk
public Resources getResources() {
if (mResources == null) {
final String[] splitPaths;
try {
splitPaths = getSplitPaths(null);
} catch (NameNotFoundException e) {
// This should never fail.
throw new AssertionError("null split not found");
}
mResources = ResourcesManager.getInstance().getResources(null, mResDir,
splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles,
Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(),
getClassLoader());
}
return mResources;
}
以下代码来自ResourcesMananger
public @Nullable Resources getResources(@Nullable 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#getResources");
final ResourcesKey key = new ResourcesKey(
resDir,
splitResDirs,
overlayDirs,
libDirs,
displayId,
overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
compatInfo);
classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
//最终调用getOrCreateResources
return getOrCreateResources(activityToken, key, classLoader);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
@NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
。。。。。。。。。。。。。。。。。。。。。。。。。。
。。。。。。。。。。。。。。。。。。。。。。。。。。
synchronized (this) {
。。。。。。。。。。。。。。。。。。。。。。。
//这边最终是拿到了一个resourcesImpl 生成了Resources
final Resources resources;
//这边我的理解 如果是Application启动 activityToken 就是空的 如有错误请大神指正
if (activityToken != null) {
resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl, key.mCompatInfo);
} else {
resources = getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
}
return resources;
}
}
看下最后两个生成Resource的方法
private @NonNull Resources getOrCreateResourcesForActivityLocked(@NonNull IBinder activityToken,
@NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl,
@NonNull CompatibilityInfo compatInfo) {
。。。。。。。。。。。。。。。。。。。。。。
。。。。。。。。。。。。。。。。。。。。。。
//这边最终是实例化了一个新的对象Resources
Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
: new Resources(classLoader);
resources.setImpl(impl);
activityResources.activityResources.add(new WeakReference<>(resources));
if (DEBUG) {
Slog.d(TAG, "- creating new ref=" + resources);
Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
}
return resources;
}
private @NonNull Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader,
@NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo) {
。。。。。。。。。。。。。。。。。。。。。。。。
// Create a new Resources reference and use the existing ResourcesImpl object.
//跟刚刚分析的那个方法是一样的操作 实例化新的Resources
Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
: new Resources(classLoader);
resources.setImpl(impl);
mResourceReferences.add(new WeakReference<>(resources));
if (DEBUG) {
Slog.d(TAG, "- creating new ref=" + resources);
Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
}
return resources;
}
再来看下Activity的Resources生成过程
static ContextImpl createActivityContext(ActivityThread mainThread,
LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
Configuration overrideConfiguration) {
。。。。。。。。。。。。。。。。。。。。。
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName,
activityToken, null, 0, classLoader);
。。。。。。。。。。。。。。。。。。。。。。。
final ResourcesManager resourcesManager = ResourcesManager.getInstance();
// Create the base resources for which all configuration contexts for this Activity
// will be rebased upon.
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;
}
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) {
.........................................
// Update any existing Activity Resources references.
//这里是重点
updateResourcesForActivity(activityToken, overrideConfig, displayId,
false /* movedToDifferentDisplay */);
// Now request an actual Resources object.
return getOrCreateResources(activityToken, key, classLoader);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
public void updateResourcesForActivity(@NonNull IBinder activityToken,
@Nullable Configuration overrideConfig, int displayId,
boolean movedToDifferentDisplay) {
try {
。。。。。。。。。。。。。。。
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);
// 更新缓存
if (overrideConfig != null) {
activityResources.overrideConfig.setTo(overrideConfig);
} else {
activityResources.overrideConfig.unset();
}
final boolean activityHasOverrideConfig =
!activityResources.overrideConfig.equals(Configuration.EMPTY);
// 重新定位与此活动关联的每个资源。
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;
}
// 提取上次用于为此活动创建资源的ResourcesKey。
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);
}
// 根据数据创建了一个新的key
final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir,
oldKey.mSplitResDirs,
oldKey.mOverlayDirs, oldKey.mLibDirs, displayId,
rebasedOverrideConfig, oldKey.mCompatInfo);
//根据新的key从mResourceImpls里面去找
//这边有可能找不到 也有可能找到 因为ResourcesKey的equals和hashcode方法被改写了,部分数据匹配依然能找到
ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(newKey);
if (resourcesImpl == null) {
resourcesImpl = createResourcesImpl(newKey);
if (resourcesImpl != null) {
mResourceImpls.put(newKey, new WeakReference<>(resourcesImpl));
}
}
//找到了 就缓存起来了
if (resourcesImpl != null && resourcesImpl != resources.getImpl()) {
// Set the ResourcesImpl, updating it for all users of this Resources
// object.
resources.setImpl(resourcesImpl);
}
}
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
看下ResourcesKey的hash生成规则
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;
现在我们可以回答之前提到的两个问题
一:为啥Resources对象都不相同
因为每次都是new出来的
二:为啥getAssert Activity的都相同
因为ResurcesImpl 是复用的 (一个Assert 对应一个ResurcesImpl )
复用逻辑设计ResourcesKey的比对,ResourcesKey比对的是资源路径,Configuration 等参数
ActivityToken 作为ActivityResources的缓存key 使用ActivityToken记录相同Token下的Activity使用的Resources
相同ActivityToken的Activity 也有可能因为config不同 导致Resources不同 因此ActivityResources里面记录了一个装满Resources的list
缓存设计逻辑:
1:先使用ActivityToken取相同token的ActivityResources
2:使用找到的ActivityResources取里面缓存的Resources 如果存在就使用它加上新的参数生成新的ResourcesKey
3:使用ResourcesKey去ResourcesImps这个缓存下面再去找资源
4:找不到就重新创建
三:为啥Application的Assert和Activity的不一样
因为Application在创建Resources的时候传入的configuration 是null 生成的ResourcesKey与Activity不一致
所以没有被Activity复用
以上分析 完全是根据源代码进行分析的,小弟不才,有些地方看的也是迷迷糊糊,如有说错的地方 麻烦帮忙指正