【Android 12】Resources的创建和更新

2,149 阅读23分钟

本文主要的重点放在Configuration的更新。

1 Resources的创建

根据一个Resources对象在创建的时候是否与一个Activity有关联,可以将Resources分为Activity类型Resources和非Activity类型Resources。

1.1 Activity类型Resources的创建

创建Activity类型Resources的地方在ResourcesManager#createResourcesForActivityLocked:

    @NonNull
    private Resources createResourcesForActivityLocked(@NonNull IBinder activityToken,
            @NonNull Configuration initialOverrideConfig, @Nullable Integer overrideDisplayId,
            @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl,
            @NonNull CompatibilityInfo compatInfo) {
        final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
                activityToken);
        cleanupReferences(activityResources.activityResources,
                activityResources.activityResourcesQueue,
                (r) -> r.resources);
        Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
                : new Resources(classLoader);
        resources.setImpl(impl);
        resources.setCallbacks(mUpdateCallbacks);
        ActivityResource activityResource = new ActivityResource();
        activityResource.resources = new WeakReference<>(resources,
                activityResources.activityResourcesQueue);
        activityResource.overrideConfig.setTo(initialOverrideConfig);
        activityResource.overrideDisplayId = overrideDisplayId;
        activityResources.activityResources.add(activityResource);
        if (DEBUG) {
            Slog.d(TAG, "- creating new ref=" + resources);
            Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
        }
        return resources;
    }

首先这里传入了一个IBinder类型的activityToken,这个是ActivityClientRecord的token对象,代表了当前Activity。

1.1.1 ActivityResources类的概念

        final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
                activityToken);

接着通过ResourcesManager#getOrCreateActivityResourcesStructLocked获取一个ActivityResources对象,碰到了第一个新的类,ActivityResources。

ActivityResources类定义在ResourcesManager中:

    /**
     * Class containing the base configuration override and set of resources associated with an
     * {@link Activity} or a {@link WindowContext}.
     */
    private static class ActivityResources {
        /**
         * Override config to apply to all resources associated with the token this instance is
         * based on.
         *
         * @see #activityResources
         * @see #getResources(IBinder, String, String[], String[], String[], String[], Integer,
         * Configuration, CompatibilityInfo, ClassLoader, List)
         */
        public final Configuration overrideConfig = new Configuration();
        /**
         * The display to apply to all resources associated with the token this instance is based
         * on.
         */
        public int overrideDisplayId;
        /** List of {@link ActivityResource} associated with the token this instance is based on. */
        public final ArrayList<ActivityResource> activityResources = new ArrayList<>();
        ......
    }

包含了与一个Activity或者WindowContext相关联的基础Configuration复写和Resources集合。

这也就是说,每一个Activity或者WindowContext,都可能对应多个Resources。在创建一个Activity或者WindowContext的时候,系统都会为其创建一个ActivityResources对象,这个ActivityResources的作用之一就是为Activity或者WindowContext保存与之联系的所有Resources对象,通过ActivityResources的成员变量activityResources。在下面会看到,这些Resources对象在创建的时候,会被封装为一个ActivityResource对象,然后添加到ActivityResources.activityResources中。

另外,ActivityResources还有一个overrideConfig成员变量,它会被应用到ActivityResources.activityResources队列中的所有Resources的Configuration,后面会看到它的具体作用。

1.1.2 ActivityResource类的概念

ActivityResource类也定义在ResourcesManager中:

    /**
     * Contains a resource derived from an {@link Activity} or {@link WindowContext} and information
     * about how this resource expects its configuration to differ from the token's.
     *
     * @see ActivityResources
     */
    // TODO: Ideally this class should be called something token related, like TokenBasedResource.
    private static class ActivityResource {
        /**
         * The override configuration applied on top of the token's override config for this
         * resource.
         */
        public final Configuration overrideConfig = new Configuration();
        /**
         * If non-null this resource expects its configuration to override the display from the
         * token's configuration.
         *
         * @see #applyDisplayMetricsToConfiguration(DisplayMetrics, Configuration)
         */
        @Nullable
        public Integer overrideDisplayId;
        @Nullable
        public WeakReference<Resources> resources;
        private ActivityResource() {}
    }

ActivityResources就是对Resources的一层封装,这里有两个值得关注的成员变量:

1)、resources,即当前ActivityResource对象保存的那个Resources对象的弱饮用。

2)、overrideConfig,保存了一个Activity或者WindowContext对象创建的时候的初始Configuration,这里我们看到ResourcesManager#createResourcesForActivityLocked方法还有一个参数:

@NonNull Configuration initialOverrideConfig

也就是说在创建Activity或者WindowContext的时候,也可以指定一个初始Configuration,就保存在ActivityResource.overrideConfig中。这个初始Configuration保存的是Activity或者WindowContext在创建的同时伴随的那一份独特的信息。后面会看到,这个Configuration是很特殊的,在Configuration更新的时候不会被新传入的Configuration覆盖掉。

1.1.3 ResourcesManager#getOrCreateActivityResourcesStructLocked

有了一些基础知识后再去看ResourcesManager#getOrCreateActivityResourcesStructLocked方法:

    private ActivityResources getOrCreateActivityResourcesStructLocked(
            @NonNull IBinder activityToken) {
        ActivityResources activityResources = mActivityResourceReferences.get(activityToken);
        if (activityResources == null) {
            activityResources = new ActivityResources();
            mActivityResourceReferences.put(activityToken, activityResources);
        }
        return activityResources;
    }

ResourcesManager#getOrCreateActivityResourcesStructLocked尝试从ResourcesManager的成员变量mActivityResourceReferences中根据activityToken去取相应的ActivityResources对象,如果找不到,就说明这是第一次为这个Activity创建相关联的Resources,那么新创建一个ActivityResources对象,并且将键值对<activityToken, activityResources>保存在ResourcesManager的成员变量mActivityResourceReferences中,mActivityResourceReferences定义是:

    /**
     * Each Activity or WindowToken may has a base override configuration that is applied to each
     * Resources object, which in turn may have their own override configuration specified.
     */
    @UnsupportedAppUsage
    private final WeakHashMap<IBinder, ActivityResources> mActivityResourceReferences =
            new WeakHashMap<>();

可以看到,ResourcesManager通过mActivityResourceReferences记录了每一个Activity对应的所有ActivityResources对象。

接下来其实就是上面所讲内容的具体实践:

        Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
                : new Resources(classLoader);
        resources.setImpl(impl);
        resources.setCallbacks(mUpdateCallbacks);
        ActivityResource activityResource = new ActivityResource();
        activityResource.resources = new WeakReference<>(resources,
                activityResources.activityResourcesQueue);
        activityResource.overrideConfig.setTo(initialOverrideConfig);
        activityResource.overrideDisplayId = overrideDisplayId;
        activityResources.activityResources.add(activityResource);

1)、创建了一个Resources对象。

2)、调用Resources#setImpl将传入的ResourcesImpl对象设置为这个新创建的Resources对象的mImpl成员变量。

3)、每一次为Activity创建Resources,都需要同时封装一层ActivityResource对象,并且将ActivityResource对象添加到activityResources.activityResources队列中。另外,这里还将ActivityResource对象的成员overrideConfig赋值为传入的initialOverrideConfig,这是ActivityResource.overrideConfig唯一赋值的地方。

1.2 非Activity类型Resources的创建

    private @NonNull Resources createResourcesLocked(@NonNull ClassLoader classLoader,
            @NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo) {
        cleanupReferences(mResourceReferences, mResourcesReferencesQueue);
        Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
                : new Resources(classLoader);
        resources.setImpl(impl);
        resources.setCallbacks(mUpdateCallbacks);
        mResourceReferences.add(new WeakReference<>(resources, mResourcesReferencesQueue));
        if (DEBUG) {
            Slog.d(TAG, "- creating new ref=" + resources);
            Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
        }
        return resources;
    }

相较于Activity类型Resources创建的过程,非Activity类型Resources的创建就显得比较简单,这里看到ResourcesManager会把新创建的非Activity类型Resources添加到成员变量mResourceReferences:

    /**
     * A list of Resource references that can be reused.
     */
    @UnsupportedAppUsage
    private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>();

就像把mActivityResourceReferences记录的是Activity类型Resources一样,mResourceReferences记录的是所有非Activity类型Resources。

1.3 小结

1)、Activity类型Resources通过ResourcesManager#createResourcesForActivityLocked创建,非Activity类型Resources通过ResourcesManager#createResourcesLocked创建。

Activity类型Resources创建流程有两个,非Activity类型Resources创建流程只有一个。

调用堆栈总结为:

Activity类型Resources创建流程之一:
...
    -> ContextImpl#createActivityContext
    或
    -> ContextImpl#createWindowContextResources
        -> ResourcesManager#createBaseTokenResources
            -> ResourcesManager#createResourcesForActivity
                -> ResourcesManager#createResourcesForActivityLocked
            
Activity类型Resources和非Activity类型Resources共同的创建流程:
...
    -> ResourcesManager#getResources
    (根据此处传入的activityToken是否为null来决定是调用ResourcesManager#createResourcesForActivity还是ResourcesManager#createResources)
        -> ResourcesManager#createResourcesForActivity(Activity类型Resources)
            -> ResourcesManager#createResourcesForActivityLocked
        -> ResourcesManager#createResources(非Activity类型Resources)
            -> ResourcesManager#createResourcesLocked

2)、ResourcesManager为每一个Activity都创建了一个ActivityResources对象,用来保存所有和这个Activity有联系的Resources,将每一个Resource封装为了ActivityResource对象。

3)、ActivityResource的成员变量overrideConfig保存了每一个Activity类型Resource在创建时期的初始Configuration,它代表了每一个Activity=类型Resource的独特部分,在Activity类型Resources更新的时候有着重要作用。

4)、ResourcesManager成员变量mActivityResourceReferences保存的是所有Activity类型的Resources,成员变量mResourceReferences保存了所有非Activity类型Resources。

2 Resources的更新

2.1 所有Resources的更新

所有Resources的更新在ResourcesManager#applyConfigurationToResources。

调用堆栈第一处:

ResourcesManager#applyConfigurationToResources调用堆栈1.png

这一处涉及到了ConfigurationChangeItem,ConfigurationChangeItem使用的地方在WindowProcessController:

ActivityTaskManagerService#updateGlobalConfigurationLocked 
    -> WindowProcessController#onConfigurationChanged 
        -> WindowProcessController#updateConfiguration 
            -> WindowProcessController#dispatchConfiguration

ActivityRecord#onConfigurationChanged
	-> WindowProcessController#dispatchConfiguration

第二处:

ResourcesManager#applyConfigurationToResources调用堆栈2.png

主要的还是第一处。

接下来分析具体代码。

    /** Applies the global configuration to the managed resources. */
    public final boolean applyConfigurationToResources(@NonNull Configuration config,
            @Nullable CompatibilityInfo compat, @Nullable DisplayAdjustments adjustments) {
        synchronized (mLock) {
            try {
                Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
                        "ResourcesManager#applyConfigurationToResources");
                if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) {
                    if (DEBUG || DEBUG_CONFIGURATION) {
                        Slog.v(TAG, "Skipping new config: curSeq="
                                + mResConfiguration.seq + ", newSeq=" + config.seq);
                    }
                    return false;
                }
                int changes = mResConfiguration.updateFrom(config);
                // ......
                Configuration tmpConfig = new Configuration();
                for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
                    ResourcesKey key = mResourceImpls.keyAt(i);
                    WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
                    ResourcesImpl r = weakImplRef != null ? weakImplRef.get() : null;
                    if (r != null) {
                        applyConfigurationToResourcesLocked(config, compat, tmpConfig, key, r);
                    } else {
                        mResourceImpls.removeAt(i);
                    }
                }
                return changes != 0;
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
            }
        }
    }

将全局configuration的更新应用到受ResourcesManager管理的Resources对象上。

挑几处重要的地方分析。

2.1.1 更新ResourcesManager.mResConfiguration

                if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) {
                    if (DEBUG || DEBUG_CONFIGURATION) {
                        Slog.v(TAG, "Skipping new config: curSeq="
                                + mResConfiguration.seq + ", newSeq=" + config.seq);
                    }
                    return false;
                }
                int changes = mResConfiguration.updateFrom(config);

这里ResourcesManager.mResConfiguration储存了每一次传入的Configuration的值,如果这一次传入的Configuration并不比ResourcesManager.mResConfiguration新,那么跳过这次更新。ResourcesManager.mResConfiguration的定义是:

    /**
     * The global configuration upon which all Resources are based. Multi-window Resources
     * apply their overrides to this configuration.
     */
    @UnsupportedAppUsage
    private final Configuration mResConfiguration = new Configuration();

ResourcesManager.mResConfiguration作为全局Configuration来使用,所有Resources对象基于它来生成。这个具体怎么理解,可以看后续的分析。

另外这也是ResourcesManager.mResConfiguration更新的唯一地方。

2.1.2 ResourcesImpl类和ResourcesManager成员变量mResourceImpls

                for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
                    ResourcesKey key = mResourceImpls.keyAt(i);
                    WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
                    ResourcesImpl r = weakImplRef != null ? weakImplRef.get() : null;
                    if (r != null) {
                        applyConfigurationToResourcesLocked(config, compat, tmpConfig, key, r);
                    } else {
                        mResourceImpls.removeAt(i);
                    }
                }

接下来的内容就是遍历ResourcesManager.mResourceImpls中的每一个ResourcesImpl对象进行更新操作。

在分析applyConfigurationToResourcesLocked方法是如何更新ResourcesManager.mResourceImpls中的每一个ResourcesImpl对象之前,需要对ResourcesImpl类和ResourcesManager的成员变量mResourceImpls有一定的了解。

mResourceImpls,定义是:

    /**
     * A mapping of ResourceImpls and their configurations. These are heavy weight objects
     * which should be reused as much as possible.
     */
    @UnsupportedAppUsage
    private final ArrayMap<ResourcesKey, WeakReference<ResourcesImpl>> mResourceImpls =
            new ArrayMap<>();

注释说,这是ResourcesImpl对象到他们的Configuration的映射。

ResourcesKey对象从名字上来看,就是为了作为Map的主键存在的。ResourcesKey对象中有一个成员变量,mOverrdieConfiguration,也是保存了一个ResourcesKey独有的Configuration。

作为value的ResourcesImpl类,从名字上也可以看出,是Resources的实现类,真正持有Configuration和其他信息那个角色。

2.1.3 ResourcesManager.mResourceImpls内容的填充

跟踪一下ResourcesImpl是如何添加到ResourcesManager.mResourceImpls中的。

ResourcesManager.mResourceImpls添加数据是在ResourcesManager#findOrCreateResourcesImplForKeyLocked方法中:

    /**
     * Variant of {@link #findOrCreateResourcesImplForKeyLocked(ResourcesKey)} that attempts to
     * load ApkAssets from a {@link ApkAssetsSupplier} when creating a new ResourcesImpl.
     */
    private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked(
            @NonNull ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier) {
        ResourcesImpl impl = findResourcesImplForKeyLocked(key);
        if (impl == null) {
            impl = createResourcesImpl(key, apkSupplier);
            if (impl != null) {
                mResourceImpls.put(key, new WeakReference<>(impl));
            }
        }
        return impl;
    }

每次想获得一个ResourcesImpl对象的时候,先从ResourcesManager.mResourceImpls中去寻找与传入的ResourcesKey对象对应的那个ResourcesImpl对象,如果找不到,那么就会调用ResourcesImpl#createResourcesImpl去为当前ResourcesKey对象创建一个ResourcesImpl对象,然后把这个键值对添加到ResourcesManager.mResourceImpls中。这里也说明了,ResourcesKey和ResourcesImpl是一一对应的。

在ResourcesManager#findOrCreateResourcesImplForKeyLocked方法的调用情况的时候,看到以下两种调用地点:

1)、ResourcesManager#createResourcesForActivity

    @Nullable
    private Resources createResourcesForActivity(@NonNull IBinder activityToken,
            @NonNull ResourcesKey key, @NonNull Configuration initialOverrideConfig,
            @Nullable Integer overrideDisplayId, @NonNull ClassLoader classLoader,
            @Nullable ApkAssetsSupplier apkSupplier) {
        synchronized (mLock) {
            if (DEBUG) {
                Throwable here = new Throwable();
                here.fillInStackTrace();
                Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here);
            }

            ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key, apkSupplier);
            if (resourcesImpl == null) {
                return null;
            }

            return createResourcesForActivityLocked(activityToken, initialOverrideConfig,
                    overrideDisplayId, classLoader, resourcesImpl, key.mCompatInfo);
        }
    }

这个正好对应的Activity类型Resources的创建流程,提前通过ResourcesManager#findOrCreateResourcesImplForKeyLocked方法获取一个ResourcesImpl对象,然后在ResourcesManager#createResourcesForActivityLocked方法中,再通过Resources#setImpl让新创建的Activity类型Resources保存这个ResourcesImpl对象。

2)、ResourcesManager#createResources

    /**
     * Creates a Resources object set with a ResourcesImpl object matching the given key.
     *
     * @param key The key describing the parameters of the ResourcesImpl object.
     * @param classLoader The classloader to use for the Resources object.
     *                    If null, {@link ClassLoader#getSystemClassLoader()} is used.
     * @return A Resources object that gets updated when
     *         {@link #applyConfigurationToResources(Configuration, CompatibilityInfo)}
     *         is called.
     */
    @Nullable
    private Resources createResources(@NonNull ResourcesKey key, @NonNull ClassLoader classLoader,
            @Nullable ApkAssetsSupplier apkSupplier) {
        synchronized (mLock) {
            if (DEBUG) {
                Throwable here = new Throwable();
                here.fillInStackTrace();
                Slog.w(TAG, "!! Create resources for key=" + key, here);
            }

            ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key, apkSupplier);
            if (resourcesImpl == null) {
                return null;
            }

            return createResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
        }
    }

这个正好对应的非Activity类型Resources的创建流程,提前通过ResourcesManager#findOrCreateResourcesImplForKeyLocked方法获取一个ResourcesImpl对象,然后在ResourcesManager#createResourcesLocked方法中,再通过Resources#setImpl让新创建的非Activity类型Resources保存这个ResourcesImpl对象。

从以上内容可以获取几点重要信息:

  • ResourcesImpl和Resources是一对多的关系,多个Resources内部的mResourcesImpl成员变量可能是同一个。
  • 不管是Activity类型Resources,还是非Activity类型Resources,它们内部的mResourcesImpl成员变量都被记录在ResourcesManager的成员变量mResourceImpls中。那么更新mResourceImpls表中记录的所有ResourcesImpl对象中保存的Configuration,其实就是更新所有Resources中保存的Configuration。

2.1.4 ResourcesImpl对象的创建

再看下创建ResourcesImpl对象的createResourcesImpl方法:

    private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key,
            @Nullable ApkAssetsSupplier apkSupplier) {
        final AssetManager assets = createAssetManager(key, apkSupplier);
        if (assets == null) {
            return null;
        }
        final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
        daj.setCompatibilityInfo(key.mCompatInfo);
        final Configuration config = generateConfig(key);
        final DisplayMetrics displayMetrics = getDisplayMetrics(generateDisplayId(key), daj);
        final ResourcesImpl impl = new ResourcesImpl(assets, displayMetrics, config, daj);
        if (DEBUG) {
            Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
        }
        return impl;
    }

这里有一点需要注意的是,这里创建ResourcesImpl用的Configuration是通过ResourcesManager#generateConfig生成的,上面提到的ResourcesManager的成员变量mResConfiguration的作用就是在这里,mResConfiguration可以通过ResourcesManager#getConfiguration方法得到:

    public Configuration getConfiguration() {
        synchronized (mLock) {
            return mResConfiguration;
        }
    }

通过上面的分析可知,ResourcesManager每一次创建Resources对象时,都需要提前从mResourceImpls中获取一个ResourcesImpl对象,而mResourceImpls中的所有ResourcesImpl对象都是通过ResourcesManager#createResourcesImpl添加的。ResourcesManager#createResourcesImpl方法在创建ResourcesImpl对象的时候,需要向ResourcesImpl的构造方法中传入一个Configuration,这个Configuration对象是通过ResourcesManager#generateConfig方法生成的,而ResourcesManager#generateConfig返回的Configuration对象,则是基于ResourcesManager.mResConfiguration创建的:

    private Configuration generateConfig(@NonNull ResourcesKey key) {
        Configuration config;
        final boolean hasOverrideConfig = key.hasOverrideConfiguration();
        if (hasOverrideConfig) {
            config = new Configuration(getConfiguration());
            config.updateFrom(key.mOverrideConfiguration);
            if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration);
        } else {
            config = getConfiguration();
        }
        return config;
    }

以ResourcesManager.mResConfiguration为base,update上ResourcesKey.mOverrideConfiguration(如果Resourceskey不为null的话)。

这段逻辑展示了mResConfiguration的作用,就如mResConfiguration的注释所说的那样,所有的Resources的创建都是基于ResourcesManager.mResConfiguration的。

2.1.5 更新ResourcesImpl对象

可以继续往下分析了:

                for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
                    ResourcesKey key = mResourceImpls.keyAt(i);
                    WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
                    ResourcesImpl r = weakImplRef != null ? weakImplRef.get() : null;
                    if (r != null) {
                        applyConfigurationToResourcesLocked(config, compat, tmpConfig, key, r);
                    } else {
                        mResourceImpls.removeAt(i);
                    }
                }

这里遍历ResourcesManager.mResourceImpls,然后对于每一个ResourcesImpl对象,调用applyConfigurationToResourcesLocked去更新。

    private void applyConfigurationToResourcesLocked(@NonNull Configuration config,
            @Nullable CompatibilityInfo compat, Configuration tmpConfig,
            ResourcesKey key, ResourcesImpl resourcesImpl) {
        if (DEBUG || DEBUG_CONFIGURATION) {
            Slog.v(TAG, "Changing resources "
                    + resourcesImpl + " config to: " + config);
        }

        tmpConfig.setTo(config);
        if (key.hasOverrideConfiguration()) {
            tmpConfig.updateFrom(key.mOverrideConfiguration);
        }

        // Get new DisplayMetrics based on the DisplayAdjustments given to the ResourcesImpl. Update
        // a copy if the CompatibilityInfo changed, because the ResourcesImpl object will handle the
        // update internally.
        DisplayAdjustments daj = resourcesImpl.getDisplayAdjustments();
        if (compat != null) {
            daj = new DisplayAdjustments(daj);
            daj.setCompatibilityInfo(compat);
        }
        daj.setConfiguration(tmpConfig);
        DisplayMetrics dm = getDisplayMetrics(generateDisplayId(key), daj);

        resourcesImpl.updateConfiguration(tmpConfig, dm, compat);
    }

重点看下Configuration是怎么更新的。

将本次传入的Configuration作为base,然后再根据ResourcesKey.mOverrideConfiguration进行update,得到最终的Configuration,然后再去调用ResourcesImpl#updateConfiguration去更新ResourcesImpl,最终在ResourcesImpl#calConfigChanges中,ResourcesImpl.mConfiguraton得到了更新。

这里有一个重要的点,如果当前ResourcesImp对象对应的ResourcesKey中保存的Configuration不是Configuration.EMPTY,那么最终得到的tmpConfig是经过ResourcesKey.mOverrideConfiguration的update的,不仅仅只是本次更新Configuration的传参config的值。

想象这样一种情况,比如这里传入的config.screenWidthDp为500dp,但是key.mOverrideConfiguration.screenWidthDp为600dp,那么最终tmpConfig中的screenWidthDp还是600dp,导致ResourcesImpl.mConfiguration.screenWidthDp还是600dp,没有更新为传入的500dp。也就是说,如果当前ResourcesImpl对应了一个ResourcesKey.mOverrideConfiguration不为Configuration.EMPTY的ResourcesKey,那么很可能经过这一次ResourcesManager.applyConfigurationToResourcesLocked,当前ResourcesImpl最终没有得到更新。

2.1.6 小结

经过ResourcesManager#applyConfigurationToResources,最终的结果是,所有ResourcesImpl对象中保存的Configuration对象得到更新,当然由于ResourcesKey中保存的Configuration不为Configuration.EMPTY,最终某些ResourcesImpl的Configuration也可能没有得到更新。

2.2 Activity类型Resources的更新

Activity Resources的更新在ResourcesManager#updateResourcesForActivity。

调用堆栈是:

updateResourcesForActivity堆栈调用.png

看到是通过ActivityConfigurationChangeItem传递信息的,ActivityConfigurationChangeItem在服务端用的地方在ActivityRecord#scheduleConfigurationChanged:

    private void scheduleConfigurationChanged(Configuration config) {
        if (!attachedToProcess()) {
            ProtoLog.w(WM_DEBUG_CONFIGURATION, "Can't report activity configuration "
                    + "update - client not running, activityRecord=%s", this);
            return;
        }
        try {
            ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending new config to %s, "
                    + "config: %s", this, config);
            mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
                    ActivityConfigurationChangeItem.obtain(config));
        } catch (RemoteException e) {
            // If process died, whatever.
        }
    }

接下来看具体代码。

    /**
     * Updates an Activity's Resources object with overrideConfig. The Resources object
     * that was previously returned by {@link #getResources(IBinder, String, String[], String[],
     * String[], String[], Integer, Configuration, CompatibilityInfo, ClassLoader, List)} is still
     * valid and will have the updated configuration.
     *
     * @param activityToken The Activity token.
     * @param overrideConfig The configuration override to update.
     * @param displayId Id of the display where activity currently resides.
     */
    public void updateResourcesForActivity(@NonNull IBinder activityToken,
            @Nullable Configuration overrideConfig, int displayId) {
        try {
            // ......
            synchronized (mLock) {
                final ActivityResources activityResources =
                        getOrCreateActivityResourcesStructLocked(activityToken);

                boolean movedToDifferentDisplay = activityResources.overrideDisplayId != displayId;
                if (Objects.equals(activityResources.overrideConfig, overrideConfig)
                        && !movedToDifferentDisplay) {
                    // They are the same and no change of display id, no work to do.
                    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);

                // Update the Activity's base override.
                if (overrideConfig != null) {
                    activityResources.overrideConfig.setTo(overrideConfig);
                } else {
                    activityResources.overrideConfig.unset();
                }

                // ......

                // Rebase each Resources associated with this Activity.
                final int refCount = activityResources.activityResources.size();
                for (int i = 0; i < refCount; i++) {
                    final ActivityResource activityResource =
                            activityResources.activityResources.get(i);

                    final Resources resources = activityResource.resources.get();
                    if (resources == null) {
                        continue;
                    }

                    final ResourcesKey newKey = rebaseActivityOverrideConfig(activityResource,
                            overrideConfig, displayId);
                    if (newKey == null) {
                        continue;
                    }

                    // TODO(b/173090263): Improve the performance of AssetManager & ResourcesImpl
                    // constructions.
                    final ResourcesImpl resourcesImpl =
                            findOrCreateResourcesImplForKeyLocked(newKey);
                    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);
        }
    }

挑几点重要的分析。

2.2.1 遍历前的准备

                final ActivityResources activityResources =
                        getOrCreateActivityResourcesStructLocked(activityToken);

                boolean movedToDifferentDisplay = activityResources.overrideDisplayId != displayId;
                if (Objects.equals(activityResources.overrideConfig, overrideConfig)
                        && !movedToDifferentDisplay) {
                    // They are the same and no change of display id, no work to do.
                    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);

                // Update the Activity's base override.
                if (overrideConfig != null) {
                    activityResources.overrideConfig.setTo(overrideConfig);
                } else {
                    activityResources.overrideConfig.unset();
                }

1)、判断本次ResourcesManager#updateResourcesForActivity传入的新的Configuration和上一次传入的是否一致,如果一致那本次就不用更新了。这里是ActivityResources.overideConfig唯一更新的地方。

2)、获取到Activity的activityResources.overrideConfig的一份拷贝,因为现在我们要将activityResources.overrideConfig更新为传入的overrideConfig,但是后续还要用到更新前的activityResources.overrideConfig。

3)、将activityResources.overrideConfig更新为传入的overrideConfig。

2.2.2 遍历当前Activity对应的ActivityResources对象的activityResources成员

                // Rebase each Resources associated with this Activity.
                final int refCount = activityResources.activityResources.size();
                for (int i = 0; i < refCount; i++) {
                    final ActivityResource activityResource =
                            activityResources.activityResources.get(i);
                    final Resources resources = activityResource.resources.get();
                    if (resources == null) {
                        continue;
                    }
                    final ResourcesKey newKey = rebaseActivityOverrideConfig(activityResource,
                            overrideConfig, displayId);
                    if (newKey == null) {
                        continue;
                    }
                    // TODO(b/173090263): Improve the performance of AssetManager & ResourcesImpl
                    // constructions.
                    final ResourcesImpl resourcesImpl =
                            findOrCreateResourcesImplForKeyLocked(newKey);
                    if (resourcesImpl != null && resourcesImpl != resources.getImpl()) {
                        // Set the ResourcesImpl, updating it for all users of this Resources
                        // object.
                        resources.setImpl(resourcesImpl);
                    }
                }

这里可以看到和ResourcesManager#applyConfigurationToResources的差别,ResourcesManager#applyConfigurationToResources是遍历ResourcesManager.mResourceImpls,而这里只遍历和当前Activity相关联的Resources对象,也就是保存在ActivityResources.activityResources队列中的所有Resources。

这里先通过ResourcesManager#rebaseActivityOverrideConfig生成一个新ResourcesKey,这个ResourcesKey.mOverrideConfiguration就是基于传入的新Configuration和其他因素综合生成的最终的Configuration,然后再跟据这个ResourcesKey从ResourcesManager.mResourceImpls寻找一个对应的ResourcesImpl对象,找不到就创建一个,然后把新的ResourcesImpl对象通过Resources#setImpl赋值给Resources.mResourcesImpl成员变量,从而完成对Resources对象的更新。

重点看一下ResourcesManager#rebaseActivityOverrideConfig方法。

2.2.3 ResourcesManager#rebaseActivityOverrideConfig

    /**
     * Rebases an updated override config over any old override config and returns the new one
     * that an Activity's Resources should be set to.
     */
    @Nullable
    private ResourcesKey rebaseActivityOverrideConfig(@NonNull ActivityResource activityResource,
            @Nullable Configuration newOverrideConfig, int displayId) {
        final Resources resources = activityResource.resources.get();
        if (resources == null) {
            return null;
        }

        // Extract the ResourcesKey that was last used to create the Resources for this
        // activity.
        final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
        if (oldKey == null) {
            Slog.e(TAG, "can't find ResourcesKey for resources impl="
                    + resources.getImpl());
            return null;
        }

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

        // ......

        final boolean hasOverrideConfig =
                !activityResource.overrideConfig.equals(Configuration.EMPTY);
        if (hasOverrideConfig) {
            rebasedOverrideConfig.updateFrom(activityResource.overrideConfig);
        }

        // ......

        // Create the new ResourcesKey with the rebased override config.
        final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir,
                oldKey.mSplitResDirs, oldKey.mOverlayPaths, oldKey.mLibDirs,
                displayId, rebasedOverrideConfig, oldKey.mCompatInfo, oldKey.mLoaders);

        if (DEBUG) {
            Slog.d(TAG, "rebasing ref=" + resources + " from oldKey=" + oldKey
                    + " to newKey=" + newKey + ", displayId=" + displayId);
        }

        return newKey;
    }

1)、首先调用ResourcesManager#findKeyForResourceImplLocked在ResourcesManager.mResourcesImpl中查找当前ResourcesImpl有没有对应的ResourcesKey,没有直接返回。

2)、接下来为此次的要创建的新的ResourcesKey生成新的Configuration:rebasedOverrideConfig,它把传入的newOverrideConfig作为base,然后update上ActivityResource.overrideConfig得到终值。

3)、最后再根据最终的rebasedOverrideConfig去创建新的ResourcesKey。

这里和ResourcesManager#applyConfigurationForResources中更新Resources的思想是一样的,ActivityResource.overrideConfig唯一赋值的时间点是在Activity类型Resources创建的时候,那么ActivityResource.overrideConfig就保存了当前Activity类型Resouces创建的时候的初始Configuration,抽象点说就是这个Configuration是这个Resources与生俱来的,是这个Resources不同于其他Resources的重要特点,任何时候都不应该被丢弃,因此需要放在最后的时候再update上去,这样通过这个Resources获取的Configuration总是会包含这个Resources的特点部分。

2.2.4 小结

经过ResourcesManager#updateResourcesForActivity,最终的结果为,所有的Activity类型的Resources的mResourcesImpl成员变量被替换,这个过程中可能伴随着新的ResourcesImpl对象的创建,但是ResourcesImpl对象中保存的Configuration没有更新。

3 Log分析

这里我们通过Demo App的log来验证一下之前的纸上谈兵谈的正确不正确。

3.1 在Launcher界面点击Demo App图标启动App

12-17 11:26:35.375  7133  7133 I ukynho_res: ContextImpl#constructor ---- context = android.app.ContextImpl@8a62ae
12-17 11:26:35.384  7133  7133 I ukynho_res: ContextImpl#constructor ---- context = android.app.ContextImpl@a9dfbba
12-17 11:26:35.558  7133  7133 I ukynho_res: ContextImpl#constructor ---- context = android.app.ContextImpl@2c79e0
12-17 11:26:35.587  7133  7133 I ukynho_res: ContextImpl#constructor ---- context = android.app.ContextImpl@256d33f
12-17 11:26:35.646  7133  7133 I ukynho_res: ContextImpl#constructor ---- context = android.app.ContextImpl@763a4f8
12-17 11:26:35.693  7133  7133 I ukynho_res: ContextImpl#constructor ---- context = android.app.ContextImpl@df10a2f
12-17 11:26:35.957  7133  7133 I ukynho_res: ContextImpl#constructor ---- context = android.app.ContextImpl@d7e7b3
12-17 11:26:35.535  7133  7133 I ukynho_res: Resources#constructor ---- this = android.content.res.Resources@82d2a74
12-17 11:26:35.672  7133  7133 I ukynho_res: Resources#constructor ---- this = android.content.res.Resources@c3ad9c2
12-17 11:26:35.735  7133  7133 I ukynho_res: Resources#constructor ---- this = android.content.res.Resources@ca21027
12-17 11:26:35.840  7133  7133 I ukynho_res: Resources#constructor ---- this = android.content.res.Resources@19dc86c
12-17 11:26:35.967  7133  7133 I ukynho_res: Resources#constructor ---- this = android.content.res.Resources@8df4c0f
    
12-17 11:26:35.523  7133  7133 I ukynho_res: ResourcesImpl#constructor ---- resourcesImpl = android.content.res.ResourcesImpl@765ba61
12-17 11:26:35.658  7133  7133 I ukynho_res: ResourcesImpl#constructor ---- resourcesImpl = android.content.res.ResourcesImpl@ab5eb37
12-17 11:26:35.724  7133  7133 I ukynho_res: ResourcesImpl#constructor ---- resourcesImpl = android.content.res.ResourcesImpl@7863c28

首先我们看到总共创建了7个Context,但是只创建了5个Resources对象,ResourcesImpl更少,只有3个。

对应关系为:

12-17 11:26:35.377  7133  7133 I ukynho_res: ContextImpl#setResources ---- context = android.app.ContextImpl@8a62ae  resources = android.content.res.Resources@dfa5116
12-17 11:26:35.540  7133  7133 I ukynho_res: ContextImpl#setResources ---- context = android.app.ContextImpl@a9dfbba     resources = android.content.res.Resources@82d2a74
12-17 11:26:35.556  7133  7133 I ukynho_res: ContextImpl#setResources ---- context = android.app.ContextImpl@2c79e0  resources = android.content.res.Resources@82d2a74
12-17 11:26:35.589  7133  7133 I ukynho_res: ContextImpl#setResources ---- context = android.app.ContextImpl@256d33f     resources = android.content.res.Resources@82d2a74
12-17 11:26:35.675  7133  7133 I ukynho_res: ContextImpl#setResources ---- context = android.app.ContextImpl@763a4f8     resources = android.content.res.Resources@c3ad9c2
12-17 11:26:35.742  7133  7133 I ukynho_res: ContextImpl#setResources ---- context = android.app.ContextImpl@df10a2f     resources = android.content.res.Resources@ca21027
12-17 11:26:35.955  7133  7133 I ukynho_res: ContextImpl#setResources ---- context = android.app.ContextImpl@d7e7b3  resources = android.content.res.Resources@82d2a74
12-17 11:26:35.971  7133  7133 I ukynho_res: ContextImpl#setResources ---- context = android.app.ContextImpl@d7e7b3  resources = android.content.res.Resources@8df4c0f
12-17 11:26:35.537  7133  7133 I ukynho_res: Resources#setImpl ---- resources = android.content.res.Resources@82d2a74    mResourcesImpl = null   impl = android.content.res.ResourcesImpl@765ba61
12-17 11:26:35.673  7133  7133 I ukynho_res: Resources#setImpl ---- resources = android.content.res.Resources@c3ad9c2    mResourcesImpl = null   impl = android.content.res.ResourcesImpl@ab5eb37
12-17 11:26:35.737  7133  7133 I ukynho_res: Resources#setImpl ---- resources = android.content.res.Resources@ca21027    mResourcesImpl = null   impl = android.content.res.ResourcesImpl@7863c28
12-17 11:26:35.842  7133  7133 I ukynho_res: Resources#setImpl ---- resources = android.content.res.Resources@19dc86c    mResourcesImpl = null   impl = android.content.res.ResourcesImpl@765ba61
12-17 11:26:35.969  7133  7133 I ukynho_res: Resources#setImpl ---- resources = android.content.res.Resources@8df4c0f    mResourcesImpl = null   impl = android.content.res.ResourcesImpl@765ba61
12-17 11:26:35.515  7133  7133 I ukynho_res: ResourcesManager#findOrCreateResourcesImplForKeyLocked ---- key = ResourcesKey{ mHash=593d1817 mOverrideConfig={0.0 ?mcc?mnc ?localeList ?layoutDir ?swdp ?wdp ?hdp ?density ?lsize ?long ?ldr ?wideColorGamut ?orien ?uimode ?night ?touch ?keyb/?/? ?nav/? winConfig={ mBounds=Rect(0, 0 - 0, 0) mAppBounds=null mMaxBounds=Rect(0, 0 - 0, 0) mWindowingMode=undefined mDisplayWindowingMode=undefined mActivityType=undefined mAlwaysOnTop=undefined mRotation=undefined} ?fontWeightAdjustment?ecid}}
12-17 11:26:35.515  7133  7133 I ukynho_res: ResourcesManager#findOrCreateResourcesImplForKeyLocked ---- existing resourcesIml = null
12-17 11:26:35.533  7133  7133 I ukynho_res: ResourcesManager#findOrCreateResourcesImplForKeyLocked ---- new resourcesIml = android.content.res.ResourcesImpl@765ba61
12-17 11:26:35.653  7133  7133 I ukynho_res: ResourcesManager#findOrCreateResourcesImplForKeyLocked ---- key = ResourcesKey{ mHash=2a5c3093 mOverrideConfig={0.0 ?mcc?mnc ?localeList ?layoutDir sw360dp w360dp h722dp 480dpi nrml long ?ldr ?wideColorGamut port ?uimode ?night -touch ?keyb/?/? ?nav/? winConfig={ mBounds=Rect(0, 0 - 0, 0) mAppBounds=null mMaxBounds=Rect(0, 0 - 0, 0) mWindowingMode=undefined mDisplayWindowingMode=undefined mActivityType=undefined mAlwaysOnTop=undefined mRotation=undefined} ?fontWeightAdjustment?ecid}}
12-17 11:26:35.653  7133  7133 I ukynho_res: ResourcesManager#findOrCreateResourcesImplForKeyLocked ---- existing resourcesIml = null
12-17 11:26:35.670  7133  7133 I ukynho_res: ResourcesManager#findOrCreateResourcesImplForKeyLocked ---- new resourcesIml = android.content.res.ResourcesImpl@ab5eb37
12-17 11:26:35.710  7133  7133 I ukynho_res: ResourcesManager#findOrCreateResourcesImplForKeyLocked ---- key = ResourcesKey{ mHash=7d7a14ea mOverrideConfig={0.93 ?mcc?mnc [en_US] ldltr sw360dp w360dp h722dp 480dpi nrml long port finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1080, 2400) mAppBounds=Rect(0, 90 - 1080, 2256) mMaxBounds=Rect(0, 0 - 1080, 2400) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_0} s.1 fontWeightAdjustment=0?ecid}}
12-17 11:26:35.710  7133  7133 I ukynho_res: ResourcesManager#findOrCreateResourcesImplForKeyLocked ---- existing resourcesIml = null
12-17 11:26:35.732  7133  7133 I ukynho_res: ResourcesManager#findOrCreateResourcesImplForKeyLocked ---- new resourcesIml = android.content.res.ResourcesImpl@7863c28
12-17 11:26:35.838  7133  7133 I ukynho_res: ResourcesManager#findOrCreateResourcesImplForKeyLocked ---- key = ResourcesKey{ mHash=593d1817 mOverrideConfig={0.0 ?mcc?mnc ?localeList ?layoutDir ?swdp ?wdp ?hdp ?density ?lsize ?long ?ldr ?wideColorGamut ?orien ?uimode ?night ?touch ?keyb/?/? ?nav/? winConfig={ mBounds=Rect(0, 0 - 0, 0) mAppBounds=null mMaxBounds=Rect(0, 0 - 0, 0) mWindowingMode=undefined mDisplayWindowingMode=undefined mActivityType=undefined mAlwaysOnTop=undefined mRotation=undefined} ?fontWeightAdjustment?ecid}}
12-17 11:26:35.839  7133  7133 I ukynho_res: ResourcesManager#findOrCreateResourcesImplForKeyLocked ---- existing resourcesIml = android.content.res.ResourcesImpl@765ba61
12-17 11:26:35.963  7133  7133 I ukynho_res: ResourcesManager#findOrCreateResourcesImplForKeyLocked ---- key = ResourcesKey{ mHash=593d1817 mOverrideConfig={0.0 ?mcc?mnc ?localeList ?layoutDir ?swdp ?wdp ?hdp ?density ?lsize ?long ?ldr ?wideColorGamut ?orien ?uimode ?night ?touch ?keyb/?/? ?nav/? winConfig={ mBounds=Rect(0, 0 - 0, 0) mAppBounds=null mMaxBounds=Rect(0, 0 - 0, 0) mWindowingMode=undefined mDisplayWindowingMode=undefined mActivityType=undefined mAlwaysOnTop=undefined mRotation=undefined} ?fontWeightAdjustment?ecid}}
12-17 11:26:35.964  7133  7133 I ukynho_res: ResourcesManager#findOrCreateResourcesImplForKeyLocked ---- existing resourcesIml = android.content.res.ResourcesImpl@765ba61

整理成表格:

ContextResourcesResourcesImplResourcesKey
ContextImpl@8a62aeResources@dfa5116
ContextImpl@a9dfbbaResources@82d2a74ResourcesImpl@765ba61ResourcesKey{ mHash=593d1817 ......}
ContextImpl@2c79e0Resources@82d2a74ResourcesImpl@765ba61ResourcesKey{ mHash=593d1817 ......}
ContextImpl@256d33fResources@82d2a74ResourcesImpl@765ba61ResourcesKey{ mHash=593d1817 ......}
ContextImpl@763a4f8Resources@c3ad9c2ResourcesImpl@ab5eb37ResourcesKey{ mHash=2a5c3093......}
ContextImpl@df10a2fResources@ca21027ResourcesImpl@7863c28ResourcesKey{ mHash=7d7a14ea......}
ContextImpl@d7e7b3Resources@8df4c0fResourcesImpl@765ba61ResourcesKey{ mHash=593d1817 ......}
Resources@19dc86cResourcesImpl@765ba61ResourcesKey{ mHash=593d1817 ......}

有一些Log可能没有打印出来,导致表格不是很全,但是无伤大雅。

3.1.1 Context和Resources的对应关系

Resources对于Context来说是一对多的关系,多个Context可能对应同一个Resources,在为Context创建Resources的时候,会先去寻找已经存在Resources中是否有合适的可以复用:

    @Nullable
    private Resources findResourcesForActivityLocked(@NonNull IBinder targetActivityToken,
            @NonNull ResourcesKey targetKey, @NonNull ClassLoader targetClassLoader) {
        ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
                targetActivityToken);
        final int size = activityResources.activityResources.size();
        for (int index = 0; index < size; index++) {
            ActivityResource activityResource = activityResources.activityResources.get(index);
            Resources resources = activityResource.resources.get();
            ResourcesKey key = resources == null ? null : findKeyForResourceImplLocked(
                    resources.getImpl());
            if (key != null
                    && Objects.equals(resources.getClassLoader(), targetClassLoader)
                    && Objects.equals(key, targetKey)) {
                return resources;
            }
        }
        return null;
    }

遍历当前Activity中所有的ResourcesImpl对象,看这些ResourcesImpl对应的ResourcesKey对象和传入的ResourcesKey对象targetKey是否一致。如果一致,说明此时对于当前传入的ResourcesKey对象,已经有一个ResourcesImp对象与其对应了,返回这个Resources对象即可。

3.1.2 Resources和ResourcesImpl的对应关系

ResourcesImpl对于Resources来说也是一对多的关系,多个Resources可能对应同一个ResourcesImpl,在为Resources创建ResourcesImpl的时候,会先去寻找已经存在ResourcesImpl中是否有合适的可以复用:

    /**
     * Variant of {@link #findOrCreateResourcesImplForKeyLocked(ResourcesKey)} that attempts to
     * load ApkAssets from a {@link ApkAssetsSupplier} when creating a new ResourcesImpl.
     */
    private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked(
            @NonNull ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier) {
        ResourcesImpl impl = findResourcesImplForKeyLocked(key);
        if (impl == null) {
            impl = createResourcesImpl(key, apkSupplier);
            if (impl != null) {
                mResourceImpls.put(key, new WeakReference<>(impl));
            }
        }
        return impl;
    }

如果已经有一个现有的ResourcesImpl对象与传入的ResourcesKey对应,那么就不需要再创建一个新的ResourcesImpl对象了。

3.1.3 比较的关键:ResourcesKey

这里看到,以上两种情况,决定是否复用的逻辑就是比较Resourceskey。

从之前的介绍中,我们知道创建Resources有两个入口,ResourcesManager#getResources和ResourcesManager#createBaseTokenResources,这两个方法有一个共同点:

    public @Nullable Resources createBaseTokenResources(@NonNull IBinder token,
            @Nullable String resDir,
            @Nullable String[] splitResDirs,
            @Nullable String[] legacyOverlayDirs,
            @Nullable String[] overlayPaths,
            @Nullable String[] libDirs,
            int displayId,
            @Nullable Configuration overrideConfig,
            @NonNull CompatibilityInfo compatInfo,
            @Nullable ClassLoader classLoader,
            @Nullable List<ResourcesLoader> loaders) {
        try {
            Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
                    "ResourcesManager#createBaseActivityResources");
            final ResourcesKey key = new ResourcesKey(
                    resDir,
                    splitResDirs,
                    combinedOverlayPaths(legacyOverlayDirs, overlayPaths),
                    libDirs,
                    displayId,
                    overrideConfig,
                    compatInfo,
                    loaders == null ? null : loaders.toArray(new ResourcesLoader[0]));
            ......
            }
        ......
    }

    public Resources getResources(
            @Nullable IBinder activityToken,
            @Nullable String resDir,
            @Nullable String[] splitResDirs,
            @Nullable String[] legacyOverlayDirs,
            @Nullable String[] overlayPaths,
            @Nullable String[] libDirs,
            @Nullable Integer overrideDisplayId,
            @Nullable Configuration overrideConfig,
            @NonNull CompatibilityInfo compatInfo,
            @Nullable ClassLoader classLoader,
            @Nullable List<ResourcesLoader> loaders) {
        try {
            Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
            final ResourcesKey key = new ResourcesKey(
                    resDir,
                    splitResDirs,
                    combinedOverlayPaths(legacyOverlayDirs, overlayPaths),
                    libDirs,
                    overrideDisplayId != null ? overrideDisplayId : INVALID_DISPLAY,
                    overrideConfig,
                    compatInfo,
                    loaders == null ? null : loaders.toArray(new ResourcesLoader[0]));
            ......
            }
        ......
    }

都传入了一个Configuration类型的overrideConfig对象,并且基于这个overrideConfig对象生成了一个ResourcesKey对象,上面说了两种情况,比较的也就是这个ResourcesKey对象和ResourcesManager.mResourceImpls中保存的ResourcesKey对象。这说明了创建Resources的时候,会基于传入的overrideConfig生成一个ResourcesKey,这个ResourcesKey有独特性,在一定程度上体现了这个Resources的特点,至于具体ResourcesKey是怎么比较的,先挖个坑。

3.1.4 重点关注的两个Resources和ResourcesKey

在Demo App启动的过程中,总共创建了3个ResourcesKey,其中有一个的mOverrideConfiguration成员是Configuration.EMPTY,剩下两个分别是:

ContextResourcesResourcesImplResourcesKey
ContextImpl@8a62aeResources@dfa5116
ContextImpl@a9dfbbaResources@82d2a74ResourcesImpl@765ba61ResourcesKey{ mHash=593d1817 ......}
ContextImpl@2c79e0Resources@82d2a74ResourcesImpl@765ba61ResourcesKey{ mHash=593d1817 ......}
ContextImpl@256d33fResources@82d2a74ResourcesImpl@765ba61ResourcesKey{ mHash=593d1817 ......}
ContextImpl@763a4f8Resources@c3ad9c2ResourcesImpl@ab5eb37ResourcesKey{ mHash=2a5c3093......}
ContextImpl@df10a2fResources@ca21027ResourcesImpl@7863c28ResourcesKey{ mHash=7d7a14ea......}
ContextImpl@d7e7b3Resources@8df4c0fResourcesImpl@765ba61ResourcesKey{ mHash=593d1817 ......}
Resources@19dc86cResourcesImpl@765ba61ResourcesKey{ mHash=593d1817 ......}

ResourcesKey{ mHash=2a5c3093 mOverrideConfig={0.0 ?mcc?mnc ?localeList ?layoutDir sw360dp w360dp h722dp 480dpi nrml long ?ldr ?wideColorGamut port ?uimode ?night -touch ?keyb/?/? ?nav/? winConfig={ mBounds=Rect(0, 0 - 0, 0) mAppBounds=null mMaxBounds=Rect(0, 0 - 0, 0) mWindowingMode=undefined mDisplayWindowingMode=undefined mActivityType=undefined mAlwaysOnTop=undefined mRotation=undefined} ?fontWeightAdjustment?ecid}}

对应的ContextImpl@763a4f8通过ContextImpl#createSystemUiContext创建,对应的Resources@c3ad9c2通过ResourcesManager#createResourcesLocked得到,这就是我个人定义的非Activity类型Resources。

ResourcesKey{ mHash=7d7a14ea mOverrideConfig={0.93 ?mcc?mnc [en_US] ldltr sw360dp w360dp h722dp 480dpi nrml long port finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1080, 2400) mAppBounds=Rect(0, 90 - 1080, 2256) mMaxBounds=Rect(0, 0 - 1080, 2400) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_0} s.1 fontWeightAdjustment=0?ecid}}

对应的ContextImpl@df10a2f通过ContextImpl#createActivityContext创建,对应的Resources@ca21027通过ResourcesManager#createResourcesForActivityLocked得到,这就是我个人定义的Activity类型Resources。

3.2 从竖屏转到横屏

分别有一次ResourcesManager#applyConfigurationToResources和ResourcesManager#updateResourcesForActivity。

3.2.1 ResourcesManager#applyConfigurationToResources

这里遍历的是ResourcesManager.mResourceImpls,此时ResourcesManager.mResourceImpls有3个,我们这里只关注orientation这一个属性。

1)、先看ResourcesImpl@7863c28,它对应了Activity类型Resources。

key = ResourcesKey{ mHash=7d7a14ea mOverrideConfig={0.93 ?mcc?mnc [en_US] ldltr sw360dp w360dp h722dp 480dpi nrml long port finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1080, 2400) mAppBounds=Rect(0, 90 - 1080, 2256) mMaxBounds=Rect(0, 0 - 1080, 2400) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_0} s.1 fontWeightAdjustment=0?ecid}} resourcesImpl = android.content.res.ResourcesImpl@7863c28

对应的ResourcesKey.mOverrideConfiguration不为Configuration.EMPTY。

12-17 11:27:22.179 7133 7133 I ukynho_res: ResourcesManager#applyConfigurationToResourcesLocked ---- newConfig = {0.93 ?mcc?mnc [en_US] ldltr sw360dp w722dp h330dp 480dpi nrml long land finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 2400, 1080) mAppBounds=Rect(144, 0 - 2310, 1080) mMaxBounds=Rect(0, 0 - 2400, 1080) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=undefined mAlwaysOnTop=undefined mRotation=ROTATION_270} s.206 fontWeightAdjustment=0?ecid}

这个是传入的newConfig,方向为横屏。

12-17 11:27:22.180 7133 7133 I ukynho_res: ResourcesManager#applyConfigurationToResourcesLocked ---- after update, final config = {0.93 ?mcc?mnc [en_US] ldltr sw360dp w360dp h722dp 480dpi nrml long port finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1080, 2400) mAppBounds=Rect(0, 90 - 1080, 2256) mMaxBounds=Rect(0, 0 - 1080, 2400) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_0} s.1 fontWeightAdjustment=0?ecid}

但是由于ResourcesKey.mOverrideConfiguration不为Configuration.EMPTY,update上ResourcesKey.mOverrideConfiguration后方向又变为竖屏。

12-17 11:27:22.181 7133 7133 I ukynho_res: ResourcesImpl#updateConfiguration ---- resourcesImpl = android.content.res.ResourcesImpl@7863c28 newConfig = {0.93 ?mcc?mnc [en_US] ldltr sw360dp w360dp h722dp 480dpi nrml long port finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1080, 2400) mAppBounds=Rect(0, 90 - 1080, 2256) mMaxBounds=Rect(0, 0 - 1080, 2400) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_0} s.1 fontWeightAdjustment=0?ecid} oldConfig = {0.93 ?mcc?mnc [en_US] ldltr sw360dp w360dp h722dp 480dpi nrml long port finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1080, 2400) mAppBounds=Rect(0, 90 - 1080, 2256) mMaxBounds=Rect(0, 0 - 1080, 2400) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_0} s.1 fontWeightAdjustment=0?ecid}

最终传给ResourcesImpl#updateConfiguration的Configuration显示竖屏,说明由于ResourcesKey的存在,ResourcesImpl@7863c2在此次ResourcesManager#applyConfigurationToResources没有更新成功。

2)、i = 1, ResourcesImpl@765ba61。

12-17 11:27:22.183 7133 7133 I ukynho_res: ResourcesManager#applyConfigurationToResources ---- i = 1 key = ResourcesKey{ mHash=593d1817 mOverrideConfig={0.0 ?mcc?mnc ?localeList ?layoutDir ?swdp ?wdp ?hdp ?density ?lsize ?long ?ldr ?wideColorGamut ?orien ?uimode ?night ?touch ?keyb/?/? ?nav/? winConfig={ mBounds=Rect(0, 0 - 0, 0) mAppBounds=null mMaxBounds=Rect(0, 0 - 0, 0) mWindowingMode=undefined mDisplayWindowingMode=undefined mActivityType=undefined mAlwaysOnTop=undefined mRotation=undefined} ?fontWeightAdjustment?ecid}} resourcesImpl = android.content.res.ResourcesImpl@765ba61

对应的ResourcesKey.mOverrideConfiguration为Configuration.EMPTY。

12-17 11:27:22.184 7133 7133 I ukynho_res: ResourcesManager#applyConfigurationToResourcesLocked ---- newConfig = {0.93 ?mcc?mnc [en_US] ldltr sw360dp w722dp h330dp 480dpi nrml long land finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 2400, 1080) mAppBounds=Rect(144, 0 - 2310, 1080) mMaxBounds=Rect(0, 0 - 2400, 1080) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=undefined mAlwaysOnTop=undefined mRotation=ROTATION_270} s.206 fontWeightAdjustment=0?ecid}

这个是传入的newConfig,方向为横屏。

12-17 11:27:22.186 7133 7133 I ukynho_res: ResourcesImpl#updateConfiguration ---- resourcesImpl = android.content.res.ResourcesImpl@765ba61 newConfig = {0.93 ?mcc?mnc [en_US] ldltr sw360dp w722dp h330dp 480dpi nrml long land finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 2400, 1080) mAppBounds=Rect(144, 0 - 2310, 1080) mMaxBounds=Rect(0, 0 - 2400, 1080) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=undefined mAlwaysOnTop=undefined mRotation=ROTATION_270} s.206 fontWeightAdjustment=0?ecid} oldConfig = {0.93 ?mcc?mnc [en_US] ldltr sw360dp w360dp h722dp 480dpi nrml long port finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1080, 2400) mAppBounds=Rect(0, 90 - 1080, 2256) mMaxBounds=Rect(0, 0 - 1080, 2400) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=undefined mAlwaysOnTop=undefined mRotation=ROTATION_0} s.80 fontWeightAdjustment=0?ecid}

由于它的ResourcesKey的mOverrideConfiguration是Configuration.EMPTY,那么直接更新成功,看到最终传给ResourcesImpl#updateConfiguration的Configuration为横屏,更新成功。

3)、i = 0, ResourcesImpl@ab5eb37。

12-17 11:27:22.188 7133 7133 I ukynho_res: ResourcesManager#applyConfigurationToResources ---- i = 0 key = ResourcesKey{ mHash=2a5c3093 mOverrideConfig={0.0 ?mcc?mnc ?localeList ?layoutDir sw360dp w360dp h722dp 480dpi nrml long ?ldr ?wideColorGamut port ?uimode ?night -touch ?keyb/?/? ?nav/? winConfig={ mBounds=Rect(0, 0 - 0, 0) mAppBounds=null mMaxBounds=Rect(0, 0 - 0, 0) mWindowingMode=undefined mDisplayWindowingMode=undefined mActivityType=undefined mAlwaysOnTop=undefined mRotation=undefined} ?fontWeightAdjustment?ecid}} resourcesImpl = android.content.res.ResourcesImpl@ab5eb37

对应的ResourcesKey.mOverrideConfiguration不为Configuration.EMPTY。

12-17 11:27:22.189 7133 7133 I ukynho_res: ResourcesManager#applyConfigurationToResourcesLocked ---- newConfig = {0.93 ?mcc?mnc [en_US] ldltr sw360dp w722dp h330dp 480dpi nrml long land finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 2400, 1080) mAppBounds=Rect(144, 0 - 2310, 1080) mMaxBounds=Rect(0, 0 - 2400, 1080) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=undefined mAlwaysOnTop=undefined mRotation=ROTATION_270} s.206 fontWeightAdjustment=0?ecid}

这个是传入的newConfig,方向为横屏。

12-17 11:27:22.189 7133 7133 I ukynho_res: ResourcesManager#applyConfigurationToResourcesLocked ---- after update, final config = {0.93 ?mcc?mnc [en_US] ldltr sw360dp w360dp h722dp 480dpi nrml long port -touch -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 2400, 1080) mAppBounds=Rect(144, 0 - 2310, 1080) mMaxBounds=Rect(0, 0 - 2400, 1080) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=undefined mAlwaysOnTop=undefined mRotation=ROTATION_270} s.206 fontWeightAdjustment=0?ecid}

但是由于ResourcesKey.mOverrideConfiguration不为Configuration.EMPTY,update上ResourcesKey.mOverrideConfiguration后方向又变为竖屏。

12-17 11:27:22.191 7133 7133 I ukynho_res: ResourcesImpl#updateConfiguration ----

resourcesImpl = android.content.res.ResourcesImpl@ab5eb37 newConfig = {0.93 ?mcc?mnc [en_US] ldltr sw360dp w360dp h722dp 480dpi nrml long port -touch -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 2400, 1080) mAppBounds=Rect(144, 0 - 2310, 1080) mMaxBounds=Rect(0, 0 - 2400, 1080) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=undefined mAlwaysOnTop=undefined mRotation=ROTATION_270} s.206 fontWeightAdjustment=0?ecid} oldConfig = {0.93 ?mcc?mnc [en_US] ldltr sw360dp w360dp h722dp 480dpi nrml long port -touch -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1080, 2400) mAppBounds=Rect(0, 90 - 1080, 2256) mMaxBounds=Rect(0, 0 - 1080, 2400) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=undefined mAlwaysOnTop=undefined mRotation=ROTATION_0} s.80 fontWeightAdjustment=0?ecid}

最终传给ResourcesImpl#updateConfiguration的Configuration显示竖屏,说明由于ResourcesKey的存在,ResourcesImpl@ab5eb37在此次ResourcesManager#applyConfigurationToResources没有更新成功。

那么最终只有i = 1, ResourcesImpl@765ba61的情况更新成功。

3.2.2 ResourcesManager#updateResourcesForActivity

这里Activity类型Resources只有一个,即上一步中没有更新成功的ResourcesImpl@7863c28对应的Resources@ca21027。

12-17 11:27:22.212 7133 7133 I ukynho_res: ResourcesManager#updateResourcesForActivity ---- activity = android.os.BinderProxy@4f70d2d overrideConfig = {0.93 ?mcc?mnc [en_US] ldltr sw360dp w722dp h330dp 480dpi nrml long land finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 2400, 1080) mAppBounds=Rect(144, 0 - 2310, 1080) mMaxBounds=Rect(0, 0 - 2400, 1080) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_270} s.2 fontWeightAdjustment=0?ecid} activityResources = android.app.ResourcesManager$ActivityResources@b9789c5 activityResources.overrideConfig = {0.93 ?mcc?mnc [en_US] ldltr sw360dp w360dp h722dp 480dpi nrml long port finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1080, 2400) mAppBounds=Rect(0, 90 - 1080, 2256) mMaxBounds=Rect(0, 0 - 1080, 2400) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_0} s.1 fontWeightAdjustment=0?ecid}

12-17 11:27:22.213 7133 7133 I ukynho_res: ResourcesManager#updateResourcesForActivity ---- i = 0 resources = android.content.res.Resources@ca21027

此时需要更新Resources对象的只有一个,Resources@ca21027。

12-17 11:27:22.214 7133 7133 I ukynho_res: ResourcesManager#rebaseActivityOverrideConfig ---- resources = android.content.res.Resources@ca21027 newOverrideConfig = {0.93 ?mcc?mnc [en_US] ldltr sw360dp w722dp h330dp 480dpi nrml long land finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 2400, 1080) mAppBounds=Rect(144, 0 - 2310, 1080) mMaxBounds=Rect(0, 0 - 2400, 1080) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_270} s.2 fontWeightAdjustment=0?ecid} oldKey = ResourcesKey{ mHash=7d7a14ea mOverrideConfig={0.93 ?mcc?mnc [en_US] ldltr sw360dp w360dp h722dp 480dpi nrml long port finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1080, 2400) mAppBounds=Rect(0, 90 - 1080, 2256) mMaxBounds=Rect(0, 0 - 1080, 2400) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_0} s.1 fontWeightAdjustment=0?ecid}}

12-17 11:27:22.215 7133 7133 I ukynho_res: ResourcesManager#rebaseActivityOverrideConfig ---- activityResource.overrideConfig = {0.0 ?mcc?mnc ?localeList ?layoutDir ?swdp ?wdp ?hdp ?density ?lsize ?long ?ldr ?wideColorGamut ?orien ?uimode ?night ?touch ?keyb/?/? ?nav/? winConfig={ mBounds=Rect(0, 0 - 0, 0) mAppBounds=null mMaxBounds=Rect(0, 0 - 0, 0) mWindowingMode=undefined mDisplayWindowingMode=undefined mActivityType=undefined mAlwaysOnTop=undefined mRotation=undefined} ?fontWeightAdjustment?ecid} rebasedOverrideConfig = {0.93 ?mcc?mnc [en_US] ldltr sw360dp w722dp h330dp 480dpi nrml long land finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 2400, 1080) mAppBounds=Rect(144, 0 - 2310, 1080) mMaxBounds=Rect(0, 0 - 2400, 1080) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_270} s.2 fontWeightAdjustment=0?ecid}

12-17 11:27:22.216 7133 7133 I ukynho_res: ResourcesManager#rebaseActivityOverrideConfig ---- newKey = ResourcesKey{ mHash=3f55d5d mOverrideConfig={0.93 ?mcc?mnc [en_US] ldltr sw360dp w722dp h330dp 480dpi nrml long land finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 2400, 1080) mAppBounds=Rect(144, 0 - 2310, 1080) mMaxBounds=Rect(0, 0 - 2400, 1080) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_270} s.2 fontWeightAdjustment=0?ecid}} rebasedOverrideConfig = {0.93 ?mcc?mnc [en_US] ldltr sw360dp w722dp h330dp 480dpi nrml long land finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 2400, 1080) mAppBounds=Rect(144, 0 - 2310, 1080) mMaxBounds=Rect(0, 0 - 2400, 1080) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_270} s.2 fontWeightAdjustment=0?ecid}

这里Resources@ca21027对应的ActivityResource.overrideConfig为Configuration.EMPTY,那么newKey的创建就完全使用了ResourcesManager#rebaseActivityOverrideConfig方法传入的newOverrideConfig。

12-17 11:27:22.217 7133 7133 I ukynho_res: ResourcesManager#findOrCreateResourcesImplForKeyLocked ---- key = ResourcesKey{ mHash=3f55d5d mOverrideConfig={0.93 ?mcc?mnc [en_US] ldltr sw360dp w722dp h330dp 480dpi nrml long land finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 2400, 1080) mAppBounds=Rect(144, 0 - 2310, 1080) mMaxBounds=Rect(0, 0 - 2400, 1080) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_270} s.2 fontWeightAdjustment=0?ecid}}

12-17 11:27:22.217 7133 7133 I ukynho_res: ResourcesManager#findOrCreateResourcesImplForKeyLocked ---- existing resourcesIml = null

12-17 11:27:22.221 7133 7133 I ukynho_res: ResourcesImpl#constructor ---- resourcesImpl = android.content.res.ResourcesImpl@ff5ba1 config = {0.93 ?mcc?mnc [en_US] ldltr sw360dp w722dp h330dp 480dpi nrml long land finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 2400, 1080) mAppBounds=Rect(144, 0 - 2310, 1080) mMaxBounds=Rect(0, 0 - 2400, 1080) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_270} s.2 fontWeightAdjustment=0?ecid}

12-17 11:27:22.223 7133 7133 I ukynho_res: ResourcesImpl#updateConfiguration ---- resourcesImpl = android.content.res.ResourcesImpl@ff5ba1 newConfig = {0.93 ?mcc?mnc [en_US] ldltr sw360dp w722dp h330dp 480dpi nrml long land finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 2400, 1080) mAppBounds=Rect(144, 0 - 2310, 1080) mMaxBounds=Rect(0, 0 - 2400, 1080) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_270} s.2 fontWeightAdjustment=0?ecid} oldConfig = {1.0 ?mcc?mnc ?localeList ?layoutDir ?swdp ?wdp ?hdp ?density ?lsize ?long ?ldr ?wideColorGamut ?orien ?uimode ?night ?touch ?keyb/?/? ?nav/? winConfig={ mBounds=Rect(0, 0 - 0, 0) mAppBounds=null mMaxBounds=Rect(0, 0 - 0, 0) mWindowingMode=undefined mDisplayWindowingMode=undefined mActivityType=undefined mAlwaysOnTop=undefined mRotation=undefined} ?fontWeightAdjustment?ecid}

12-17 11:27:22.226 7133 7133 D ResourcesManager: - creating impl=android.content.res.ResourcesImpl@ff5ba1 with key: ResourcesKey{ mHash=3f55d5d mOverrideConfig={0.93 ?mcc?mnc [en_US] ldltr sw360dp w722dp h330dp 480dpi nrml long land finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 2400, 1080) mAppBounds=Rect(144, 0 - 2310, 1080) mMaxBounds=Rect(0, 0 - 2400, 1080) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_270} s.2 fontWeightAdjustment=0?ecid}}

12-17 11:27:22.226 7133 7133 I ukynho_res: ResourcesManager#findOrCreateResourcesImplForKeyLocked ---- new resourcesIml = android.content.res.ResourcesImpl@ff5ba1

12-17 11:27:22.226 7133 7133 I ukynho_res: ResourcesManager#updateResourcesForActivity ---- i = 0 resourcesImpl = android.content.res.ResourcesImpl@ff5ba1 resources.getImpl() = android.content.res.ResourcesImpl@7863c28

12-17 11:27:22.227 7133 7133 I ukynho_res: Resources#setImpl ---- resources = android.content.res.Resources@ca21027 mResourcesImpl = android.content.res.ResourcesImpl@7863c28 impl = android.content.res.ResourcesImpl@ff5ba1

最终创建了一个新的ResourcesKey{ mHash=3f55d5d ......},并且创建了一个新的ResourcesImpl对象,ResouresImpl@ff5ba1,与之对应,最后通过Resources#setImpl方法将Resources@ca21027中的mResourcesImpl成员赋值为ResouresImpl@ff5ba1,完成了此次的Configuration更新。

虽然此时Resources@ca2102中的成员变量mResourcesImpl从ResourcesImpl@7863c28被替换为了ResouresImpl@ff5ba1,但是ResourcesImpl@7863c28仍然保存在ResourcesManager的mResourceImpls成员变量中,这为后续ResourcesImpl@7863c28的复用提供了支持。

ContextResourcesResourcesImplResourcesKey
ContextImpl@8a62aeResources@dfa5116
ContextImpl@a9dfbbaResources@82d2a74ResourcesImpl@765ba61ResourcesKey{ mHash=593d1817 ......}
ContextImpl@2c79e0Resources@82d2a74ResourcesImpl@765ba61ResourcesKey{ mHash=593d1817 ......}
ContextImpl@256d33fResources@82d2a74ResourcesImpl@765ba61ResourcesKey{ mHash=593d1817 ......}
ContextImpl@763a4f8Resources@c3ad9c2ResourcesImpl@ab5eb37ResourcesKey{ mHash=2a5c3093......}
ContextImpl@df10a2fResources@ca21027ResouresImpl@ff5ba1ResourcesKey{ mHash=3f55d5d ......}
ContextImpl@d7e7b3Resources@8df4c0fResourcesImpl@765ba61ResourcesKey{ mHash=593d1817 ......}
Resources@19dc86cResourcesImpl@765ba61ResourcesKey{ mHash=593d1817 ......}
ResourcesImpl@7863c28ResourcesKey{ mHash=7d7a14ea......}

3.3 从横屏转到竖屏

分别有一次ResourcesManager#applyConfigurationToResources和ResourcesManager#updateResourcesForActivity。

3.3.1 ResourcesManager#applyConfigurationToResources

这里遍历的是ResourcesManager.mResourceImpls,此时ResourcesManager.mResourceImpls有4个,因为上一步新增了一个ResourcesImpl#ff5ba1,我们只看这个ResourcesImpl对象如何更新,同样我们这里只关注方向这一个属性。

1)、i = 3,ResourcesImpl@7863c28。

2)、i = 2, ResourcesImpl@765ba61。

3)、i = 1, ResourcesImpl@ab5eb37。

4)、i = 0, ResourcesImpl@ff5ba1。

12-17 11:27:34.325 7133 7133 I ukynho_res: ResourcesManager#applyConfigurationToResources ---- i = 0 key = ResourcesKey{ mHash=3f55d5d mOverrideConfig={0.93 ?mcc?mnc [en_US] ldltr sw360dp w722dp h330dp 480dpi nrml long* *land* *finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 2400, 1080) mAppBounds=Rect(144, 0 - 2310, 1080) mMaxBounds=Rect(0, 0 - 2400, 1080) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_270} s.2 fontWeightAdjustment=0?ecid}} resourcesImpl = android.content.res.ResourcesImpl@ff5ba1

对应的ResourcesKey.mOverrideConfiguration不为Configuration.EMPTY。

12-17 11:27:34.326 7133 7133 I ukynho_res: ResourcesManager#applyConfigurationToResourcesLocked ---- newConfig = {0.93 ?mcc?mnc [en_US] ldltr sw360dp w360dp h722dp 480dpi nrml long port finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1080, 2400) mAppBounds=Rect(0, 90 - 1080, 2256) mMaxBounds=Rect(0, 0 - 1080, 2400) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=undefined mAlwaysOnTop=undefined mRotation=ROTATION_0} s.265 fontWeightAdjustment=0?ecid}

这个是传入的newConfig,方向为竖屏。

12-17 11:27:34.326 7133 7133 I ukynho_res: ResourcesManager#applyConfigurationToResourcesLocked ---- after update, final config = {0.93 ?mcc?mnc [en_US] ldltr sw360dp w722dp h330dp 480dpi nrml long land finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 2400, 1080) mAppBounds=Rect(144, 0 - 2310, 1080) mMaxBounds=Rect(0, 0 - 2400, 1080) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_270} s.2 fontWeightAdjustment=0?ecid}

但是由于ResourcesKey.mOverrideConfiguration不为Configuration.EMPTY,update上ResourcesKey.mOverrideConfiguration后方向又变为横屏。

12-17 11:27:34.328 7133 7133 I ukynho_res: ResourcesImpl#updateConfiguration ---- resourcesImpl = android.content.res.ResourcesImpl@ff5ba1 newConfig = {0.93 ?mcc?mnc [en_US] ldltr sw360dp w722dp h330dp 480dpi nrml long land finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 2400, 1080) mAppBounds=Rect(144, 0 - 2310, 1080) mMaxBounds=Rect(0, 0 - 2400, 1080) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_270} s.2 fontWeightAdjustment=0?ecid} oldConfig = {0.93 ?mcc?mnc [en_US] ldltr sw360dp w722dp h330dp 480dpi nrml long land finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 2400, 1080) mAppBounds=Rect(144, 0 - 2310, 1080) mMaxBounds=Rect(0, 0 - 2400, 1080) mWindowingMode=fullscreen

最终传给ResourcesImpl#updateConfiguration的Configuration显示横屏,说明由于ResourcesKey的存在,ResourcesImpl@7863c2在此次ResourcesManager#applyConfigurationToResources没有更新成功。

3.3.2 ResourcesManager#updateResourcesForActivity

这里Activity Resources只有一个,即上一步中没有更新成功的ResourcesImpl@ff5ba1对应的Resources@ca21027。

12-17 11:27:34.367 7133 7133 I ukynho_res: ResourcesManager#updateResourcesForActivity ---- activity = android.os.BinderProxy@4f70d2d overrideConfig = {0.93 ?mcc?mnc [en_US] ldltr sw360dp w360dp h722dp 480dpi nrml long port finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1080, 2400) mAppBounds=Rect(0, 90 - 1080, 2256) mMaxBounds=Rect(0, 0 - 1080, 2400) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_0} s.3 fontWeightAdjustment=0?ecid} activityResources = android.app.ResourcesManager$ActivityResources@b9789c5 activityResources.overrideConfig = {0.93 ?mcc?mnc [en_US] ldltr sw360dp w722dp h330dp 480dpi nrml long land finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 2400, 1080) mAppBounds=Rect(144, 0 - 2310, 1080) mMaxBounds=Rect(0, 0 - 2400, 1080) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_270} s.2 fontWeightAdjustment=0?ecid}

12-17 11:27:34.368 7133 7133 I ukynho_res: ResourcesManager#updateResourcesForActivity ---- i = 0 resources = android.content.res.Resources@ca21027

此时需要更新的只有Resources@ca21027。

12-17 11:27:34.369 7133 7133 I ukynho_res: ResourcesManager#rebaseActivityOverrideConfig ---- resources = android.content.res.Resources@ca21027 newOverrideConfig = {0.93 ?mcc?mnc [en_US] ldltr sw360dp w360dp h722dp 480dpi nrml long port finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1080, 2400) mAppBounds=Rect(0, 90 - 1080, 2256) mMaxBounds=Rect(0, 0 - 1080, 2400) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_0} s.3 fontWeightAdjustment=0?ecid} oldKey = ResourcesKey{ mHash=3f55d5d mOverrideConfig={0.93 ?mcc?mnc [en_US] ldltr sw360dp w722dp h330dp 480dpi nrml long land finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 2400, 1080) mAppBounds=Rect(144, 0 - 2310, 1080) mMaxBounds=Rect(0, 0 - 2400, 1080) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_270} s.2 fontWeightAdjustment=0?ecid}}

12-17 11:27:34.369 7133 7133 I ukynho_res: ResourcesManager#rebaseActivityOverrideConfig ---- activityResource.overrideConfig = {0.0 ?mcc?mnc ?localeList ?layoutDir ?swdp ?wdp ?hdp ?density ?lsize ?long ?ldr ?wideColorGamut ?orien ?uimode ?night ?touch ?keyb/?/? ?nav/? winConfig={ mBounds=Rect(0, 0 - 0, 0) mAppBounds=null mMaxBounds=Rect(0, 0 - 0, 0) mWindowingMode=undefined mDisplayWindowingMode=undefined mActivityType=undefined mAlwaysOnTop=undefined mRotation=undefined} ?fontWeightAdjustment?ecid} rebasedOverrideConfig = {0.93 ?mcc?mnc [en_US] ldltr sw360dp w360dp h722dp 480dpi nrml long port finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1080, 2400) mAppBounds=Rect(0, 90 - 1080, 2256) mMaxBounds=Rect(0, 0 - 1080, 2400) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_0} s.3 fontWeightAdjustment=0?ecid}

12-17 11:27:34.370 7133 7133 I ukynho_res: ResourcesManager#rebaseActivityOverrideConfig ---- newKey = ResourcesKey{ mHash=7d7a14ea mOverrideConfig={0.93 ?mcc?mnc [en_US] ldltr sw360dp w360dp h722dp 480dpi nrml long port finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1080, 2400) mAppBounds=Rect(0, 90 - 1080, 2256) mMaxBounds=Rect(0, 0 - 1080, 2400) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_0} s.3 fontWeightAdjustment=0?ecid}} rebasedOverrideConfig = {0.93 ?mcc?mnc [en_US] ldltr sw360dp w360dp h722dp 480dpi nrml long port finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1080, 2400) mAppBounds=Rect(0, 90 - 1080, 2256) mMaxBounds=Rect(0, 0 - 1080, 2400) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_0} s.3 fontWeightAdjustment=0?ecid}

这里Resources@ca21027对应的ActivityResource.overrideConfig为Configuration.EMPTY,那么newKey的创建就完全使用了ResourcesManager#rebaseActivityOverrideConfig方法传入的newOverrideConfig。

12-17 11:27:34.371 7133 7133 I ukynho_res: ResourcesManager#findOrCreateResourcesImplForKeyLocked ---- key = ResourcesKey{ mHash=7d7a14ea mOverrideConfig={0.93 ?mcc?mnc [en_US] ldltr sw360dp w360dp h722dp 480dpi nrml long port finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1080, 2400) mAppBounds=Rect(0, 90 - 1080, 2256) mMaxBounds=Rect(0, 0 - 1080, 2400) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_0} s.3 fontWeightAdjustment=0?ecid}}

12-17 11:27:34.372 7133 7133 I ukynho_res: ResourcesManager#findOrCreateResourcesImplForKeyLocked ---- existing resourcesIml = android.content.res.ResourcesImpl@7863c28

12-17 11:27:34.372 7133 7133 I ukynho_res: ResourcesManager#updateResourcesForActivity ---- i = 0 resourcesImpl = android.content.res.ResourcesImpl@7863c28 resources.getImpl() = android.content.res.ResourcesImpl@ff5ba1

12-17 11:27:34.373 7133 7133 I ukynho_res: Resources#setImpl ---- resources = android.content.res.Resources@ca21027 mResourcesImpl = android.content.res.ResourcesImpl@ff5ba1 impl = android.content.res.ResourcesImpl@7863c28

最终创建了一个新的ResourcesKey{ mHash=3f55d5d ......},但是这里在ResourcesImpl.mResourceImpls中可以找到一个对应的ResourcesImpl对象,ResouresImpl@7863c28,最后通过Resources#setImpl方法将Resources@ca21027中的mResourcesImpl成员赋值为ResouresImpl@7863c28,完成了此次的Configuration更新。

ContextResourcesResourcesImplResourcesKey
ContextImpl@8a62aeResources@dfa5116
ContextImpl@a9dfbbaResources@82d2a74ResourcesImpl@765ba61ResourcesKey{ mHash=593d1817 ......}
ContextImpl@2c79e0Resources@82d2a74ResourcesImpl@765ba61ResourcesKey{ mHash=593d1817 ......}
ContextImpl@256d33fResources@82d2a74ResourcesImpl@765ba61ResourcesKey{ mHash=593d1817 ......}
ContextImpl@763a4f8Resources@c3ad9c2ResourcesImpl@ab5eb37ResourcesKey{ mHash=2a5c3093......}
ContextImpl@df10a2fResources@ca21027ResouresImpl@7863c28ResourcesKey{ mHash=7d7a14ea......}
ContextImpl@d7e7b3Resources@8df4c0fResourcesImpl@765ba61ResourcesKey{ mHash=593d1817 ......}
Resources@19dc86cResourcesImpl@765ba61ResourcesKey{ mHash=593d1817 ......}
ResourcesImpl@ff5ba1ResourcesKey{ mHash=3f55d5d......}

4 总结

4.1 ResourcesManager#applyConfigurationToResource

ResourcesManager#applyConfigurationToResources中,更新的是所有ResourcesImpl(因为Resources只是对ResourcesImpl的一层封装,真正持有Configuration的是ResourcesImpl),通过遍历ResourcesImpl.mResourceImpls,来更新每一个ResourcesImpl对象。

finalConfig = newConfig.updateFrom(key,mOverrideConfiguration)。

其中newConfig为从系统服务进程传过来的的Configuration,key为当前ResourcesImpl对象对应的ResourcesKey对象,得到最终的finalConfig,通过ResourcesImpl#updateConfiguration方法,来更新ResourcesImpl中的mConfiguration对象。

如果一个ResourcesImpl对应的ResourcesKey不为Configuration.EMPTY,那么在一次Configuration的更新中,finalConfig的某些属性很有可能被key.mOverrideConfiguration覆盖掉。

4.2 ResourcesManager#updateResourcesForActivity

ResourcesManager#updateResourcesForActivity,更新的是当前Activity对应的所有Resources,那么就需要知道哪些Resources是对应当前Activity的。根据之前的分析,我们知道每一个Activity都对应一个ActivityResources对象,ActivityResources.activityResources队列中的每一个ActivityResource都对应一个Resources对象,这些Resources对象就是与当前Activity关联的Resources对象。那么我们就可以找到当前Activity对应的ActivityResources对象,通过遍历ActivityResoures.activityResources,更新每一个ActivityResource对象中保存的Resources对象。

更新每一个Resoures对象的具体内容是,以本次ResourcesManager#updateResourcesForActivity中从系统服务端传入的newConfig为baseConfig,再update上创建Resources时候传入的初始Configuration(已经保存在ActivityResource.overrideConfig中),生成一个finalConfig,然后基于这个finalConfig生成一个新的ResourcesKey。

finalConfig = newConfig.updateFrom(ActivityResoruce,overrideConfig)。

最终有两种情况:

1)、生成的这个ResourcesKey,在ResourcesManager.mResourcesImpl中找不到对应的ResourcesImpl对象,那么创建一个新的ResourcesImpl对象,并且将键值对<ResourcesKey, ResourcesImpl>加入到ResourcesManager.mResourcesImpl中,返回这个新创建的ResourcesImpl对象。

2)、生成的这个ResourcesKey,在ResourcesManager.mResourcesImpl中可以找到对应的ResourcesImpl对象,那么直接返回这个ResourcesImpl对象。

最终通过Resources#setImpl,通过替换Resources中的mResourcesImpl成员完成当前Resources的更新。

4.3 Log分析

从Log分析这一节中,可以看到ResourcesManager存在ResourcesImpl对象的复用。

我们只看和Resources@ca21027的部分:

1)、Activity创建的时候,生成了Resources@ca21027,以及<ResourcesKey{ mHash=7d7a14ea......}, ResouresImpl@7863c28>。

2)、从竖屏转为横屏,生成了新的键值对,<ResourcesKey{ mHash=3f55d5d......}, ResourcesImpl@ff5ba1 >,Resources@ca21027将成员变量mResourcesImpl从ResouresImpl@7863c28替换为ResourcesImpl@ff5ba1。

3)、从横屏转为竖屏,这个时候,没有创建新的键值对,而是复用了第一步的<ResourcesKey{ mHash=7d7a14ea......}, ResouresImpl@7863c28>,Resources@ca21027将成员变量mResourcesImpl从ResouresImpl@ff5ba1替换为ResourcesImpl@7863c28。

4)、同样,再从竖屏转为横屏,也不会创建新的键值对,会复用第二步生成的<ResourcesKey{ mHash=3f55d5d......}, ResourcesImpl@ff5ba1>,Resources@ca21027将成员变量mResourcesImpl从ResouresImpl@7863c28替换为ResourcesImpl@ff5ba1。