1.Resources的分类
Resources可以分为Activity类型的和WindowContext类型两种类型的Resources,(ps:Application类型的Resources还待研究),谷歌源码的解释如下:
298 /** 299 * Contains a resource derived from an {@link Activity} or {@link WindowContext} and information 300 * about how this resource expects its configuration to differ from the token's. 301 * 302 * @see ActivityResources 303 */ 304 // TODO: Ideally this class should be called something token related, like TokenBasedResource. 305 private static class ActivityResource { 306 /** 307 * The override configuration applied on top of the token's override config for this 308 * resource. 309 */ 310 public final Configuration overrideConfig = new Configuration(); 311 312 /** 313 * If non-null this resource expects its configuration to override the display from the 314 * token's configuration. 315 * 316 * @see #applyDisplayMetricsToConfiguration(DisplayMetrics, Configuration) 317 */ 318 @Nullable 319 public Integer overrideDisplayId; 320 321 @Nullable 322 public WeakReference<Resources> resources; 323 324 private ActivityResource() {} 325 }
2 Activity类型的Resources创建过程:
2.1 在Resources创建之前,先理解下Activity和Resources之间的关系。
应用开发是通过Context.getResources()方法获取Resources对象,接下来我们看下Context对象是如何获取到Resources对象的。
一般情况下系统的Context对象有Activity、Application、Service类型的Context对象,其最终实现类是ContextImpl,因为在Activity的内部在attach的时候会传入一个Context对象,这个Context对象的实现类就是ContextImpl。具体的堆栈信息如下:
performLaunchActivity(
因为ContextImpl类中持有Resources对象mResources,其调用setResources方法设置进来的,调用堆栈如下:
所以我们就清楚了Activity、ContextImpl、Resources、ResourcesImpl之间的关系如下:
2.2 Resources创建过程:
Activity类型的Resources是通过ResourcesManager的createResourcesForActivityLocked方法创建的:
869 @NonNull 870 private Resources createResourcesForActivityLocked(@NonNull IBinder activityToken, 871 @NonNull Configuration initialOverrideConfig, @Nullable Integer overrideDisplayId, 872 @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl, 873 @NonNull CompatibilityInfo compatInfo) { 874 final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked( 875 activityToken); 876 cleanupReferences(activityResources.activityResources, 877 activityResources.activityResourcesQueue, 878 (r) -> r.resources); 879 880 Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader) 881 : new Resources(classLoader); 882 resources.setImpl(impl); 883 resources.setCallbacks(mUpdateCallbacks); 884 885 ActivityResource activityResource = new ActivityResource(); 886 activityResource.resources = new WeakReference<>(resources, 887 activityResources.activityResourcesQueue); 888 activityResource.overrideConfig.setTo(initialOverrideConfig); 889 activityResource.overrideDisplayId = overrideDisplayId; 890 activityResources.activityResources.add(activityResource); 891 if (DEBUG) { 892 Slog.d(TAG, "- creating new ref=" + resources); 893 Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl); 894 } 895 return resources; 896 }
在创建Resources对象的时候创建了ActivityResources对象,ActivityResources对象是个什么,它的作用是什么:
256 /** 257 * Class containing the base configuration override and set of resources associated with an 258 * {@link Activity} or a {@link WindowContext}. 259 */ 260 private static class ActivityResources { 261 /** 262 * Override config to apply to all resources associated with the token this instance is 263 * based on. 264 * 265 * @see #activityResources 266 * @see #getResources(IBinder, String, String[], String[], String[], String[], Integer, 267 * Configuration, CompatibilityInfo, ClassLoader, List) 268 */ 269 public final Configuration overrideConfig = new Configuration(); 270 271 /** 272 * The display to apply to all resources associated with the token this instance is based 273 * on. 274 */ 275 public int overrideDisplayId; 276 277 /** List of {@link ActivityResource} associated with the token this instance is based on. */ 278 public final ArrayList<ActivityResource> activityResources = new ArrayList<>(); 279 280 public final ReferenceQueue<Resources> activityResourcesQueue = new ReferenceQueue<>(); 281 282 @UnsupportedAppUsage 283 private ActivityResources() {} 284 285 /** Returns the number of live resource references within {@code activityResources}. */ 286 public int countLiveReferences() { 287 int count = 0; 288 for (int i = 0; i < activityResources.size(); i++) { 289 WeakReference<Resources> resources = activityResources.get(i).resources; 290 if (resources != null && resources.get() != null) { 291 count++; 292 } 293 } 294 return count; 295 } 296 }
我们通过注释可以看出ActivityResources就是一个它是一个包含了一个overrideConfig的一组Activity类型/WindowContext类型的集合,其成员activityResources集合包含了一组的ActivityResource,那么ActivityResource又是个什么:
298 /** 299 * Contains a resource derived from an {@link Activity} or {@link WindowContext} and information 300 * about how this resource expects its configuration to differ from the token's. 301 * 302 * @see ActivityResources 303 */ 304 // TODO: Ideally this class should be called something token related, like TokenBasedResource. 305 private static class ActivityResource { 306 /** 307 * The override configuration applied on top of the token's override config for this 308 * resource. 309 */ 310 public final Configuration overrideConfig = new Configuration(); 311 312 /** 313 * If non-null this resource expects its configuration to override the display from the 314 * token's configuration. 315 * 316 * @see #applyDisplayMetricsToConfiguration(DisplayMetrics, Configuration) 317 */ 318 @Nullable 319 public Integer overrideDisplayId; 320 321 @Nullable 322 public WeakReference<Resources> resources; 323 324 private ActivityResource() {} 325 }
ActivityResource通过成员变量resources来管理Resources的,下面整理一下Activity、ActivityClientRecord、ActivityResources、ActivityResource之间的关系:
ResourcesManager中的mActivityResourceReferences成员存入的key是IBinder对象,其实就是一个activityToken对象,其是在LaunchActivityItem时由system_server的ATMS生成的,可以理解成通行证,用于标记Activity的身份,在ATMS侧可以找到ActivityRecord,在APP侧可以找到对应的ActivityClientRecord。从上图的对应关系我们可以知道,一个Activity可能会对应多个Resources对象,这是为什么呢,这就和Resources的缓存机制有关了,假如说我们的系统语言为中文,然后我们从中文切换到了英语,这个时候系统就会为我们创建一个和此次Configuration有关的新的Resources与之对应,然后我们将英语又切回到了中文,这个时候系统就会复用第一次创建的和中文有关的Resources对象。
3.Resources更新过程
一个Activity在初始化的时候会调用attach方法绑定一个ContextImpl对象,这个ContextImpl对象会一直到该页面销毁,一直保持不变,上面我们知道一个ContextImpl对象中会持有一个Resources对象,当我们切换语言的时候页面的时候ContextImpl是没有变的,要保证app资源的正确性,这个时候就要替换掉ContextImpl对象里面的Resources对象,这也就是一个Activity可能有多个Resources的原因。系统会把我们之前切换掉的Resources保存在ResourceManager中防止下一次又切换到相同的Configuration的时候重新创建Resources对象。ResourceManger主要就是通过下面这两个集合进行Resources复用的:
108 /** 109 * A mapping of ResourceImpls and their configurations. These are heavy weight objects 110 * which should be reused as much as possible. 111 */ 112 @UnsupportedAppUsage 113 private final ArrayMap<ResourcesKey, WeakReference<ResourcesImpl>> mResourceImpls = 114 new ArrayMap<>(); 115 116 /** 117 * A list of Resource references that can be reused. 118 */ 119 @UnsupportedAppUsage 120 private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>();
从上面的注释可以看出ResourcesManager保存了和Configuration有关的ResourcesKey和与之对应的ResourcesImpl对象。当我们下次再切换到相同的语言,系统创建的ResourceKey就和上一次切换的ResourceKey两个不同的对象能保证他们的mHash值保持一致,也就能顺利的从mResourceImpls当中取出缓存对象。下面摘录了ResourcesKey的mHash的计算方法:
31 public final class ResourcesKey {
68 public ResourcesKey(@Nullable String resDir, 69 @Nullable String[] splitResDirs, 70 @Nullable String[] overlayPaths, 71 @Nullable String[] libDirs, 72 int overrideDisplayId, 73 @Nullable Configuration overrideConfig, 74 @Nullable CompatibilityInfo compatInfo, 75 @Nullable ResourcesLoader[] loader) { 76 mResDir = resDir; 77 mSplitResDirs = splitResDirs; 78 mOverlayPaths = overlayPaths; 79 mLibDirs = libDirs; 80 mLoaders = (loader != null && loader.length == 0) ? null : loader; 81 mDisplayId = overrideDisplayId; 82 mOverrideConfiguration = new Configuration(overrideConfig != null 83 ? overrideConfig : Configuration.EMPTY); 84 mCompatInfo = compatInfo != null ? compatInfo : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; 85 86 int hash = 17; 87 hash = 31 * hash + Objects.hashCode(mResDir); 88 hash = 31 * hash + Arrays.hashCode(mSplitResDirs); 89 hash = 31 * hash + Arrays.hashCode(mOverlayPaths); 90 hash = 31 * hash + Arrays.hashCode(mLibDirs); 91 hash = 31 * hash + Objects.hashCode(mDisplayId); 92 hash = 31 * hash + Objects.hashCode(mOverrideConfiguration); 93 hash = 31 * hash + Objects.hashCode(mCompatInfo); 94 hash = 31 * hash + Arrays.hashCode(mLoaders); 95 mHash = hash; 96 }
139 @Override 140 public boolean equals(@Nullable Object obj) { 141 if (!(obj instanceof ResourcesKey)) { 142 return false; 143 } 144 145 ResourcesKey peer = (ResourcesKey) obj; 146 if (mHash != peer.mHash) { 147 // If the hashes don't match, the objects can't match. 148 return false; 149 } 150 151 if (!Objects.equals(mResDir, peer.mResDir)) { 152 return false; 153 } 154 if (!Arrays.equals(mSplitResDirs, peer.mSplitResDirs)) { 155 return false; 156 } 157 if (!Arrays.equals(mOverlayPaths, peer.mOverlayPaths)) { 158 return false; 159 } 160 if (!Arrays.equals(mLibDirs, peer.mLibDirs)) { 161 return false; 162 } 163 if (mDisplayId != peer.mDisplayId) { 164 return false; 165 } 166 if (!Objects.equals(mOverrideConfiguration, peer.mOverrideConfiguration)) { 167 return false; 168 } 169 if (!Objects.equals(mCompatInfo, peer.mCompatInfo)) { 170 return false; 171 } 172 if (!Arrays.equals(mLoaders, peer.mLoaders)) { 173 return false; 174 } 175 return true; 176 }
209 }