Since Android 7.0, Google has made a major adjustment to Resources.
Today, let's look at the source code of Android 9.0
First look at Resources.java
Instead of maintaining Assert Manager in Android 6.0 Resources, Assert Manager is encapsulated with other caches as a Resource Impl.
Look at the source code
public class Resources { static final String TAG = "Resources"; static Resources mSystem = null; private ResourcesImpl mResourcesImpl; private TypedValue mTmpValue = new TypedValue(); final ClassLoader mClassLoader; }
public class ResourcesImpl { private static final LongSparseArray<Drawable.ConstantState>[] sPreloadedDrawables; private static final LongSparseArray<Drawable.ConstantState> sPreloadedColorDrawables = new LongSparseArray<>(); private static final LongSparseArray<android.content.res.ConstantState<ComplexColor>> sPreloadedComplexColors = new LongSparseArray<>(); // These are protected by mAccessLock. private final Configuration mTmpConfig = new Configuration(); private final DrawableCache mDrawableCache = new DrawableCache(); private final DrawableCache mColorDrawableCache = new DrawableCache(); private final ConfigurationBoundResourceCache<ComplexColor> mComplexColorCache = new ConfigurationBoundResourceCache<>(); private final ConfigurationBoundResourceCache<Animator> mAnimatorCache = new ConfigurationBoundResourceCache<>(); private final ConfigurationBoundResourceCache<StateListAnimator> mStateListAnimatorCache = new ConfigurationBoundResourceCache<>(); final AssetManager mAssets; private final DisplayMetrics mMetrics = new DisplayMetrics(); private final DisplayAdjustments mDisplayAdjustments; private PluralRules mPluralRule; private final Configuration mConfiguration = new Configuration(); }
ResourcesImpl is responsible for wrapping AssertManager and maintaining data caching in older versions of Resources.
The code for Resources is also simpler, and its method calls are ultimately handed over to ResourcesImpl for implementation.
The same thing is whether the management of Resources should be handed over to the Resource Manager, which is a singleton model like Android 6.0.
So 9.0 Resource Manager is different from 6.0 Resource Manager?
Starting with application startup, or familiar with 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; }
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(); 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; }
The difference between the methods in ResourceManager that are ultimately called by the Resource that generates the Application or Activity is that one calls ResourcesManager.getInstance().getResources and the other calls resourcesManager.createBaseActivityResources.
OK. Let's look at the source code for Resource Manager.
Let's look at the various attributes it provides first, and let's pick the most important ones, so as not to make things too messy and messy.
/** * ResourceImpls Mapping of its configuration. These are data that occupy a lot of memory. * It should be reused as much as possible. All ResourcesImpl s generated by ResourcesManager are cached in this map */ private final ArrayMap<ResourcesKey, WeakReference<ResourcesImpl>> mResourceImpls = new ArrayMap<>(); /** *A list of Resource references that can be reused. Note that this list does not store Activity's Resources cache. As I understand it, all non-Activcity Resources are cached here, such as Application's Resources. */ private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>(); /** * Each Activity has a basic coverage configuration that applies to each Resource object, which in turn can specify its own coverage configuration. This cache holds the cache of Actrivity Resource. ActivityResources is an object that contains a Configuration owned by an Activity and all Resources that might have been owned, such as an Activity. In some cases, his ResourcesImpl has changed, and then it is Activity. YResources may hold multiple resource references */ private final WeakHashMap<IBinder, ActivityResources> mActivityResourceReferences = new WeakHashMap<>(); /** * Cached ApkAssets, which can be ignored first */ private final LruCache<ApkKey, ApkAssets> mLoadedApkAssets = new LruCache<>(3); /** * This is also a cache for ApkAssets, which can also be ignored first. */ private final ArrayMap<ApkKey, WeakReference<ApkAssets>> mCachedApkAssets = new ArrayMap<>(); private static class ApkKey { public final String path; public final boolean sharedLib; public final boolean overlay; } /** * Resource and basic configuration coverage associated with Activity. */ private static class ActivityResources { public final Configuration overrideConfig = new Configuration(); //As a rule, an Activity has only one Resource, but a list is used to store it. This is to consider that if the Activity changes and the Resource is regenerated, the list will contain all the Resources used in the Activity history. Of course, if no one holds these Resources, it will. Will be recycled public final ArrayList<WeakReference<Resources>> activityResources = new ArrayList<>(); }
With these important attributes in mind, let's take a look at the many methods that ResourceManager provides.
ResourceManager provides the following to write public methods for invocation
Look first at getResources and createBaseActivityResources, both of which ultimately use a ResourcesKey to call getOrCreateResources
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 { final ResourcesKey key = new ResourcesKey(resDir, splitResDirs, overlayDirs, libDirs, displayId, overrideConfig != null ? new Configuration(overrideConfig) : null,compatInfo); classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader(); return getOrCreateResources(activityToken, key, classLoader); } finally { } }
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 { final ResourcesKey key = new ResourcesKey(resDir, splitResDirs, overlayDirs, libDirs, displayId, overrideConfig != null ? new Configuration(overrideConfig) : null, compatInfo); classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader(); synchronized (this) { // Force the creation of ActivityResources objects and put them in the cache getOrCreateActivityResourcesStructLocked(activityToken); } // Update any existing Activity Resources references. updateResourcesForActivity(activityToken, overrideConfig, displayId, false /* movedToDifferentDisplay */); // Now request an actual Resource object. return getOrCreateResources(activityToken, key, classLoader); } finally { } }
getOrCreateResources I have written comments in every line of code. You should pay attention to the comments in the code. Some of the comments are translations of the quoted comments in the code.
private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken, @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) { synchronized (this) { if (activityToken != null) { final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(activityToken); // Clean up caches that have been recycled ArrayUtils.unstableRemoveIf(activityResources.activityResources, sEmptyReferencePredicate); // Rebase the key's override config on top of the Activity's base override. if (key.hasOverrideConfiguration() && !activityResources.overrideConfig.equals(Configuration.EMPTY)) { final Configuration temp = new Configuration(activityResources.overrideConfig); temp.updateFrom(key.mOverrideConfiguration); key.mOverrideConfiguration.setTo(temp); } //Getting a ResourcesImpl based on the corresponding key may be new or cached ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key); if (resourcesImpl != null) { //Use Resources Impl to generate a Resource return getOrCreateResourcesForActivityLocked(activityToken, classLoader, resourcesImpl, key.mCompatInfo); } // We will create the ResourcesImpl object outside of holding this lock. } else { // Clean up because the mResourceReferences contain weak references. To determine whether these weak references have been released, remove them from Array if released. ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate); // Instead of relying on Activity, find shared resources with the correct ResourcesImpl, which is based on key to find in the cache of mResourceImpls ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key); if (resourcesImpl != null) { //If you find resourcesImpl, look at mResourceReferences to see if there are any available resources. If the class loader is the same as ResourcesImpl, get the existing resources object, otherwise a new resources object will be created. return getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo); } // We will create ResourcesImpl objects outside of holding this lock. } // If we got here, we couldn't find the right ResourcesImpl to use, so now create one. ResourcesImpl resourcesImpl = createResourcesImpl(key); if (resourcesImpl == null) { return null; } // Add this ResourcesImpl to the cache. mResourceImpls.put(key, new WeakReference<>(resourcesImpl)); final Resources resources; if (activityToken != null) { //Look in mActivity Resource References to see if there are suitable Resources available. If not, build a resource soldier and add it to mActivity Resource References. resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader, resourcesImpl, key.mCompatInfo); } else { //Use the created ResourcesImpl to match a Resource, whether to fetch (if any) from the cache mResourceReferences or create a new one is determined by the following method resources = getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo); } return resources; } }
Draw a flowchart and look at it.
After looking at the graph, the logic is basically clear.
Let's use the following caches of the code hook system ResourcesManger to see what objects are included in an App boot and open an Activity
try { System.out.println("Application = " + getApplicationContext().getResources() + " hold " + Reflector.with(getApplicationContext().getResources()).method("getImpl").call()); System.out.println("Activity = " + getResources() + " hold " + Reflector.with(getResources()).method("getImpl").call()); System.out.println("System = " + Resources.getSystem() + " hold " + Reflector.with(Resources.getSystem()).method("getImpl").call()); ResourcesManager resourcesManager = ResourcesManager.getInstance(); System.out.println("--------------------------------mResourceImpls----------------------------------------------"); ArrayMap<ResourcesKey, WeakReference<ResourcesImpl>> mResourceImpls = Reflector.with(resourcesManager).field("mResourceImpls").get(); Iterator<ResourcesKey> resourcesKeyIterator = mResourceImpls.keySet().iterator(); while (resourcesKeyIterator.hasNext()) { ResourcesKey key = resourcesKeyIterator.next(); WeakReference<ResourcesImpl> value = mResourceImpls.get(key); System.out.println("key = " + key); System.out.println("value = " + value.get()); } System.out.println("-----------------------------------mResourceReferences-------------------------------------------"); ArrayList<WeakReference<Resources>> mResourceReferences = Reflector.with(resourcesManager).field("mResourceReferences").get(); for (WeakReference<Resources> weakReference : mResourceReferences) { Resources resources = weakReference.get(); if (resources != null) { System.out.println(resources + " hold " + Reflector.with(resources).method("getImpl").call()); } } System.out.println("-------------------------------------mActivityResourceReferences-----------------------------------------"); WeakHashMap<IBinder, Object> mActivityResourceReferences = Reflector.with(resourcesManager).field("mActivityResourceReferences").get(); Iterator<IBinder> iBinderIterator = mActivityResourceReferences.keySet().iterator(); while (iBinderIterator.hasNext()) { IBinder key = iBinderIterator.next(); Object value = mActivityResourceReferences.get(key); System.out.println("key = " + key); System.out.println("value = " + value); Object overrideConfig = Reflector.with(value).field("overrideConfig").get(); System.out.println("overrideConfig = " + overrideConfig); Object activityResources = Reflector.with(value).field("activityResources").get(); try { ArrayList<WeakReference<Resources>> list = (ArrayList<WeakReference<Resources>>) activityResources; for (WeakReference<Resources> weakReference : list) { Resources resources = weakReference.get(); System.out.println("activityResources = " + resources + " hold " + Reflector.with(resources).method("getImpl").call()); } } catch (Reflector.ReflectedException e) { e.printStackTrace(); } } } catch (Exception e) { e.printStackTrace(); }
The printed results are as follows