android—Resouce源码解析

前言

在android开发过程中的Resouce是我们经常使用的,但是我们大多只是用它的getColor和getDrawable方法获取资源文件中的颜色和图片资源,其实在我们看不到的地方整个android系统的资源获取都是使用的它,最近出现的一些屏幕适配和动态换肤使用到了它,不知道你有没有这些疑问,Application的Resouce和Activity的有什么区别?每个不同Acitivty的Resouce有什么区别?这篇文章就分析一下Resouce从创建到绑定到Activity的过程。

准备

在开始分析代码之前需要了解一些东西,Resouce类可以说成是一个中介类,他并没做什么实质性的东西,大部分的操作被他转到了ResourcesImpl中,ResourcesImpl中管理着AssetManager、DisplayMetrics、Configuration这三个对象,这三个对象我们应该或多或少的都了解过吧,由于篇幅有限这里就不讲他们的作用了,被Resouce转到ResourcesImpl的操作又根据具体类型被转到这三个对象中了。 我们通常通过Resouce去获取颜色、字符串、图片等资源文件最终其实都是调用AssetManager的方法执行的。

代码分析

源码是根据android9.0也就是API28进行分析。
首先查看AppCompatActivity的getResources方法(这里没有直接选择Activity,是想看一下AppCompatActivity做了哪些事)

    @Override
    public Resources getResources() {
        if (mResources == null && VectorEnabledTintResources.shouldBeUsed()) {
            mResources = new VectorEnabledTintResources(this, super.getResources());
        }
        return mResources == null ? super.getResources() : mResources;
    }

这里有一个VectorEnabledTintResources.shouldBeUsed()的判断,简单看了一下这个类,这应该是一个兼容矢量图的方法,只有在API20以下才会使用,我们暂时不考虑这个,那么就会调用super.getResources(),也就是ContextThemeWrapper的getResources方法:

    @Override
    public Resources getResources() {
        return getResourcesInternal();
    }

    private Resources getResourcesInternal() {
        if (mResources == null) {
            if (mOverrideConfiguration == null) {
                mResources = super.getResources();
            } else {
                final Context resContext = createConfigurationContext(mOverrideConfiguration);
                mResources = resContext.getResources();
            }
        }
        return mResources;
    }

这里如果mOverrideConfiguration不为空就会调用createConfigurationContext()方法使用mOverrideConfiguration作为参数创建Resouce,这个mOverrideConfiguration是我们通过applyOverrideConfiguration()方法设置的,也就是说我们可以让系统使用我们自定义的Configuration:

    public void applyOverrideConfiguration(Configuration overrideConfiguration) {
        if (mResources != null) {
            throw new IllegalStateException(
                    "getResources() or getAssets() has already been called");
        }
        if (mOverrideConfiguration != null) {
            throw new IllegalStateException("Override configuration has already been set");
        }
        mOverrideConfiguration = new Configuration(overrideConfiguration);
    }

如果mOverrideConfiguration为空就会调用ContextWrapper的getResources方法:

    @Override
    public Resources getResources() {
        return mBase.getResources();
    }

这是一个Context的代理类,mBase就是Context,所以最后会走到Context的getResources方法,如果我们直接从Activity开始看就会直接到这里,Context是一个抽象类他的实现类是ContextImpl,我们看一下ContextImpl的getResources方法:

    @Override
    public Resources getResources() {
        return mResources;
    }
    
    void setResources(Resources r) {
        if (r instanceof CompatResources) {
            ((CompatResources) r).setContext(this);
        }
        mResources = r;
    }

getResources返回的是在setResources中设置的Resouce实例,setResources方法只ContextImpl中调用,其他地方并没有调用,我们把调用的方法列举出来:

public Context createApplicationContext(ApplicationInfo application, int flags)
public Context createContextForSplit(String splitName)
public Context createConfigurationContext(Configuration overrideConfiguration)
public Context createDisplayContext(Display display)
static ContextImpl createSystemContext(ActivityThread mainThread)
static ContextImpl createSystemUiContext(ContextImpl systemContext)
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo)
static ContextImpl createActivityContext(ActivityThread mainThread,
            LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
            Configuration overrideConfiguration)

这里主要是createApplicationContext和createActivityContext两个方法,看名字就能知道他们创建的分别是Application和Activity的Context,我们就主要分析这两个方法,其他的都是大同小异。

先来看看createApplicationContext方法:

    @Override
    public Context createApplicationContext(ApplicationInfo application, int flags)
            throws NameNotFoundException {
        LoadedApk pi = mMainThread.getPackageInfo(application, mResources.getCompatibilityInfo(),
                flags | CONTEXT_REGISTER_PACKAGE);
        if (pi != null) {
            ContextImpl c = new ContextImpl(this, mMainThread, pi, null, mActivityToken,
                    new UserHandle(UserHandle.getUserId(application.uid)), flags, null);

            final int displayId = mDisplay != null
                    ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;

            c.setResources(createResources(mActivityToken, pi, null, displayId, null,
                    getDisplayAdjustments(displayId).getCompatibilityInfo()));
            if (c.mResources != null) {
                return c;
            }
        }

        throw new PackageManager.NameNotFoundException(
                "Application package " + application.packageName + " not found");
    }

这里会去调用createResources(mActivityToken, pi, null, displayId, null,
getDisplayAdjustments(displayId).getCompatibilityInfo())创建Resouce实例:

    private static Resources createResources(IBinder activityToken, LoadedApk pi, String splitName,
            int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo) {
            
        ....................
        
        return ResourcesManager.getInstance().getResources(activityToken,
                pi.getResDir(),
                splitResDirs,
                pi.getOverlayDirs(),
                pi.getApplicationInfo().sharedLibraryFiles,
                displayId,
                overrideConfig,
                compatInfo,
                classLoader);
    }

这里就转到了ResourcesManager中的getResouce()方法,ResourcesManager是一个对Resouce进行管理的类,实现了对Resouce的创建、缓存和获取。我们看一下ResourcesManager的getResouce()方法:

    public @Nullable Resources getResources(@Nullable IBinder activityToken,
            @Nullable String resDir,
            @Nullable String[] splitResDirs,
            @Nullable String[] overlayDirs,
            @Nullable String[] libDirs,
            int displayId,
            @Nullable Configuration overrideConfig,
            @NonNull CompatibilityInfo compatInfo,
            @Nullable ClassLoader classLoader) {
        try {
            Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
            final ResourcesKey key = new ResourcesKey(
                    resDir,
                    splitResDirs,
                    overlayDirs,
                    libDirs,
                    displayId,
                    overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
                    compatInfo);
            classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
            return getOrCreateResources(activityToken, key, classLoader);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
        }
    }

这里首先创建了一个ResourcesKey,这是一个很重要的类,ResouceImpl的缓存是使用它做的key,之后会去调用getOrCreateResources()方法,这里我们先搁一下,回头去看看createActivityContext方法:

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

      .................................
      
        final ResourcesManager resourcesManager = ResourcesManager.getInstance();
        // Create the base resources for which all configuration contexts for this Activity
        // will be rebased upon.
        context.setResources(resourcesManager.createBaseActivityResources(activityToken,
                packageInfo.getResDir(),
                splitDirs,
                packageInfo.getOverlayDirs(),
                packageInfo.getApplicationInfo().sharedLibraryFiles,
                displayId,
                overrideConfiguration,
                compatInfo,
                classLoader));
        context.mDisplay = resourcesManager.getAdjustedDisplay(displayId,
                context.getResources());
        return context;
    }

这里的Resouce实例是通过resourcesManager.createBaseActivityResources()方法获取的,这样就直接转到了ResourcesManager中:

    public @Nullable Resources createBaseActivityResources(@NonNull IBinder activityToken,
            @Nullable String resDir,
            @Nullable String[] splitResDirs,
            @Nullable String[] overlayDirs,
            @Nullable String[] libDirs,
            int displayId,
            @Nullable Configuration overrideConfig,
            @NonNull CompatibilityInfo compatInfo,
            @Nullable ClassLoader classLoader) {
        try {
            Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
                    "ResourcesManager#createBaseActivityResources");
            final ResourcesKey key = new ResourcesKey(
                    resDir,
                    splitResDirs,
                    overlayDirs,
                    libDirs,
                    displayId,
                    overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
                    compatInfo);
            classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();

            if (DEBUG) {
                Slog.d(TAG, "createBaseActivityResources activity=" + activityToken
                        + " with key=" + key);
            }

            synchronized (this) {
                // Force the creation of an ActivityResourcesStruct.
                getOrCreateActivityResourcesStructLocked(activityToken);
            }

            // Update any existing Activity Resources references.
            updateResourcesForActivity(activityToken, overrideConfig, displayId,
                    false /* movedToDifferentDisplay */);

            // Now request an actual Resources object.
            return getOrCreateResources(activityToken, key, classLoader);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
        }
    }

这里也是会先去创建一个ResourcesKey,然后也是去调用getOrCreateResources()方法,这时createActivityContext和createApplicationContext就走到了一条路上了。
在看getOrCreateResources()方法之前我们先来熟悉一下ResourcesManager的缓存。

  /**
     * ResourceImpls及其配置的映射。 这些都是占用较大内存的数据
     * 应该尽可能重用。所有的由ResourcesManager生成的ResourcesImpl都会被缓存在这个map中
     */
    private final ArrayMap<ResourcesKey, WeakReference<ResourcesImpl>> mResourceImpls = new ArrayMap<>();
 
    /**
     *可以重用的资源引用列表。注意一下 这个list里面存储的并不是Activity的Resources缓存,按照我的理解,所有非Activcity的Resource都会被缓存在此处,比如Application的Resource
     */
    private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>();
 
    /**
     * 每个Activity都有一个基本覆盖配置,该配置应用于每个Resources对象,而这些对象又可以指定自己的覆盖配置。
        这个缓存里面保存的都是Actrivity的Resource的缓存,ActivityResources是一个对象,里面包含了一个Activity所拥有的Configuration和所有可能拥有过的Resources,比如一个Activity,在某些情况下他的ResourcesImpl发生了变化,那么这个时候就ActivityResources就可能会持有多个Resource引用
     */
    private final WeakHashMap<IBinder, ActivityResources> mActivityResourceReferences = new WeakHashMap<>();
 
    /**
     * 缓存的ApkAssets,这个可以先不看
     */
    private final LruCache<ApkKey, ApkAssets> mLoadedApkAssets = new LruCache<>(3);
 
    /**
     * 这也是ApkAssets的一个缓存 这个也可以先不看
     */
    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;
    }
 
    /**
     * 与Activity关联的资源和基本配置覆盖。
     */
    private static class ActivityResources {
        public final Configuration overrideConfig = new Configuration();
//按照常规的理解 一个Activity只有一个Resources 但是这里却使用了一个list来存储,这是考虑如果Activity发生变化,重新生成了Resource,这个列表就会将Activity历史使用过的Resources都存在里面,当然,如果没有人再持有这些Resources,就会被回收
        public final ArrayList<WeakReference<Resources>> activityResources = new ArrayList<>();
    }

现在来看getOrCreateResources()方法,这里才是创建Resouce真正的地方:

    private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
            @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
        synchronized (this) {

            ......
            //下边是分两种情况,当activityToken(IBinder)不为空就是创建Activity的Resouce,为空就是创建Application的
            if (activityToken != null) {
                ......
                //根据key获取与之对应的ResourcesImpl缓存
                ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
                if (resourcesImpl != null) {
                    ......
                    // 只要根据key获取到的ResourcesImpl不为null,就根据这个ResourcesImpl去获取缓存的
                    //Resources,如果又这个Resources缓存,就返回,没有就创建,具体看这个方法
                    return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
                            resourcesImpl, key.mCompatInfo);
                }

                // We will create the ResourcesImpl object outside of holding this lock.

            } else {
                ......
                ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
                if (resourcesImpl != null) {
                    ......
                    return getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
                }
            }
        
        //当程序走到这里的时候,说明ResourcesImpl没有找到,Resources也就没有得到,那么这里就是根据
        //key创建出一个ResourcesImpl来,程序第一次运行的时候肯定会首先走到这里,所以,上边的代码可以
        //不用太重点的去看,接下来我们看看ResourcesImpl是如何被创建出来的,见方法createResourcesImpl
        // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
        ResourcesImpl resourcesImpl = createResourcesImpl(key);
        if (resourcesImpl == null) {
            return null;
        }

        synchronized (this) {
            ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key);
            if (existingResourcesImpl != null) {
                //从缓存中获取
                ......
                resourcesImpl.getAssets().close();
                resourcesImpl = existingResourcesImpl;
            } else {
                // 将创建的ResourcesImpl缓存起来
                mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
            }

            final Resources resources;
            //在此针对activityToken是否为null分别处理,在getOrCreateResourcesForActivityLocked和getOrCreateResourcesLocked
            //这两个方法中,我们重点关注,Resources不存在缓存的情况,所以,最终会看到Resourses的创建,
            //见下边的方法
            if (activityToken != null) {
                resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
                        resourcesImpl, key.mCompatInfo);
            } else {
                resources = getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
            }
            return resources;
        }
    
        //Resources的创建,这里看到根据条件的不同有两种方式获取,一个是new CompatResources,一个是
        //new Resources,进入到CompatResources类中,我们看到这个构造最终也会调用Resources的一个构造
        //方法public Resources(@Nullable ClassLoader classLoader) 返回Resources,可见这个Resources是new
        //出来的
        Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
                : new Resources(classLoader);
        //给Resources设置ResourcesImpl
        resources.setImpl(impl);
        //加入缓存
        mResourceReferences.add(new WeakReference<>(resources));

这个方法非常重要,首先无论是Activity还是Application都会先 findResourcesImplForKeyLocked(key)方法去缓存中找有没有可用的ResourcesImpl,如果有的话Activity会去调用getOrCreateResourcesForActivityLocked(activityToken, classLoader,resourcesImpl, key.mCompatInfo)创建Resouce,Application会去调用getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo)方法创建Resouce。如果没有找到可用的ResourcesImpl,会走到下面的createResourcesImpl(key)创建ResourcesImpl,然后通过同样的方法创建Resouce。
我们先看createResourcesImpl(key)方法怎么创建ResourcesImpl了,毕竟这是Resouce里最重要的一个对象:

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

        final AssetManager assets = createAssetManager(key);
        if (assets == null) {
            return null;
        }

        final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
        final Configuration config = generateConfig(key, dm);
        final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);

        if (DEBUG) {
            Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
        }
        return impl;
    }

这里先是创建了AssetManager 、DisplayMetrics 、Configuration 三个对象的实例,然后通过这三个实例创建ResourcesImpl 对象,注意这里传过来的参数只有一个ResourcesKey,说明创建这三个对象所需的参数都在ResourcesKey里,我们就不详细研究具体的创建过程了,这里把ResourcesKey的构造函数贴出来:

    public ResourcesKey(@Nullable String resDir,
                        @Nullable String[] splitResDirs,
                        @Nullable String[] overlayDirs,
                        @Nullable String[] libDirs,
                        int displayId,
                        @Nullable Configuration overrideConfig,
                        @Nullable CompatibilityInfo compatInfo)

进入正题,创建了ResourcesImpl 之后就到了创建Resouce的时候了,先看Activity的getOrCreateResourcesForActivityLocked(activityToken, classLoader,resourcesImpl, key.mCompatInfo):

    private @NonNull Resources getOrCreateResourcesForActivityLocked(@NonNull IBinder activityToken,
            @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl,
            @NonNull CompatibilityInfo compatInfo) {
        final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
                activityToken);

        final int refCount = activityResources.activityResources.size();
        for (int i = 0; i < refCount; i++) {
            WeakReference<Resources> weakResourceRef = activityResources.activityResources.get(i);
            Resources resources = weakResourceRef.get();

            if (resources != null
                    && Objects.equals(resources.getClassLoader(), classLoader)
                    && resources.getImpl() == impl) {
                if (DEBUG) {
                    Slog.d(TAG, "- using existing ref=" + resources);
                }
                return resources;
            }
        }

        Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
                : new Resources(classLoader);
        resources.setImpl(impl);
        activityResources.activityResources.add(new WeakReference<>(resources));
        if (DEBUG) {
            Slog.d(TAG, "- creating new ref=" + resources);
            Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
        }
        return resources;
    }

这里回先通过activityToken去缓存中取,如果没有就会new一个Resouce,然后将ResourcesImpl设置进去,最后将new出来的Resouce添加进缓存。
接着看Application的getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo)方法:

    private @NonNull Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader,
            @NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo) {
        // Find an existing Resources that has this ResourcesImpl set.
        final int refCount = mResourceReferences.size();
        for (int i = 0; i < refCount; i++) {
            WeakReference<Resources> weakResourceRef = mResourceReferences.get(i);
            Resources resources = weakResourceRef.get();
            if (resources != null &&
                    Objects.equals(resources.getClassLoader(), classLoader) &&
                    resources.getImpl() == impl) {
                if (DEBUG) {
                    Slog.d(TAG, "- using existing ref=" + resources);
                }
                return resources;
            }
        }

        // Create a new Resources reference and use the existing ResourcesImpl object.
        Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
                : new Resources(classLoader);
        resources.setImpl(impl);
        mResourceReferences.add(new WeakReference<>(resources));
        if (DEBUG) {
            Slog.d(TAG, "- creating new ref=" + resources);
            Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
        }
        return resources;
    }

也是同样的逻辑,只是从不同的缓存中取而已。

到这里Resouce的创建过程已经讲完了,只是中间涉及到的很多东西没有讲,AssetManager 、DisplayMetrics 、Configuration 这三个对象的创建是重点,有兴趣的可以去搜一下。

最后盗一张图演示所有的流程
在这里插入图片描述

总结

1.mResourceImpls是存ResouresImpl缓存的一个Map,是由ResouresKey作为key的,每次去获取ResouresImpl时都是通过ResouresKey,所以除非ResouresKey发生改变,否则不会去创建ResouresImpl,缓存里也就只有一个ResouresImpl,一般一个app只有一个ResouresImpl。上面也贴出了ResouresKey的构造函数,只要其中的参数没有改变ResouresKey就不会变。

2.mResourceReferences是存储Application的Resoures的List数组,一般也只有一个。

3.mActivityResourceReferences是存储Activity的Resoures的Map,使用activityToken作为key,一个Activity可以有多个Resoures。从流程上看两个Activity会有不同的Resoures,但是他们的Resoures有着相同的ResouresImpl,也就有着相同的AssetManager 、DisplayMetrics 、Configuration,如果你在一个Acitivty中修改了其中一个对象,那么另一个Acitivty也会同步修改:

MainActivity:
getResources().getDisplayMetrics().densityDpi = 4
设置densityDpi的值为4

Main2Activity:
int densityDpi=getResources().getDisplayMetrics().densityDpi
densityDpi获取到的值为4

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值