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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章