FrameWork源碼解析(6)-AssetManager加載資源過程

主目錄見:Android高級(jí)進(jìn)階知識(shí)(這是總目錄索引)

?之前一段時(shí)間項(xiàng)目比較忙所以一直沒有更新,接下來準(zhǔn)備把插件化系列的文章寫完,今天我們就先跳過ContentProvider源碼解析來講資源加載相關(guān)的知識(shí),資源加載可以說是插件化非常重要的一環(huán),我們很有必要來了解它。當(dāng)然看這篇文章之前可以看下性能優(yōu)化(6)-減小APK體積加深下對(duì)資源目錄的了解。

一.目標(biāo)

今天的文章內(nèi)容是為了插件化框架講解做準(zhǔn)備的知識(shí)的,我們的今天要達(dá)到的目標(biāo)是:
1.能明白AssetManager是怎么加載資源的,apk內(nèi)部或者外部的;
2.同時(shí)加深一下對(duì)資源的認(rèn)識(shí)。

二.AssetManager

?前面我們已經(jīng)知道因?yàn)?code>Activity,ContextWrapper,ContextImpl的關(guān)系,這個(gè)部分用的是裝飾模式來實(shí)現(xiàn)的,所以在Activity中調(diào)用的大部分方法都將最終調(diào)用到ContextImpl中,所以訪問資源的兩個(gè)方法getResources()getAssets()最終都將調(diào)用ContextImpl中的相應(yīng)方法。
?ContextImpl#getResources()方法返回Resources對(duì)象,這個(gè)對(duì)象是根據(jù)資源的id來訪問相應(yīng)的資源的,除了assets目錄不會(huì)在R文件中生成相應(yīng)的id外,其他都是可以的。ContextImpl#getAssets()返回的是AssetManager對(duì)象,這個(gè)對(duì)象可以根據(jù)文件名來返回被編譯過或者未編譯過的資源。其實(shí)Resources對(duì)象最終也是通過AssetManager對(duì)象來獲取資源的,不過會(huì)先通過資源id查找到資源文件名。
這里我們首先從ContextImpl#getResources()方法入手:

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

我們看到這里直接返回了ContextImpl中的mResources變量,這個(gè)變量是在哪里被初始化的呢?我們可以在ContextImpl的構(gòu)造函數(shù)看到:

 private ContextImpl(ContextImpl container, ActivityThread mainThread,
            LoadedApk packageInfo, IBinder activityToken, UserHandle user, int flags,
            Display display, Configuration overrideConfiguration, int createDisplayWithId) {
.....
   Resources resources = packageInfo.getResources(mainThread);
....
  mResources = resources;
....
}

這里的packageInfoLoadedApk對(duì)象,LoadedApk描述的是當(dāng)前apk的一些信息,我們看到這個(gè)方法首先是調(diào)用了LoadedApk#getResources方法,傳進(jìn)去的參數(shù)是mainThreadmainThread對(duì)應(yīng)的是ActivityThread對(duì)象,也就是當(dāng)前正在運(yùn)行的應(yīng)用程序進(jìn)程。所以我們接下來看LoadedApk#getResources方法:

 public Resources getResources(ActivityThread mainThread) {
        if (mResources == null) {
            mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs,
                    mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, this);
        }
        return mResources;
    }

這個(gè)方法首先會(huì)判斷mResources是否為空,如果為空才去調(diào)用ActivityThread中的方法getTopLevelResources(),這個(gè)方法會(huì)返回一個(gè)Resources對(duì)象,其中第一個(gè)參數(shù)mResDir是資源文件的路徑,這個(gè)路徑就是保存在LoadedApk的變量mResDir中的,第二個(gè)參數(shù)mSplitResDirs是針對(duì)有可能一個(gè)app由多個(gè)apk組成,每個(gè)子apk的資源路徑。 mOverlayDirs是目錄主題包base.apk的路徑,如果沒有的話那就為空,mApplicationInfo.sharedLibraryFiles是apk依賴的共享庫的文件。接著我們看ActivityThread#getTopLevelResources方法:

 Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs,
            String[] libDirs, int displayId, LoadedApk pkgInfo) {
        return mResourcesManager.getResources(null, resDir, splitResDirs, overlayDirs, libDirs,
                displayId, null, pkgInfo.getCompatibilityInfo(), pkgInfo.getClassLoader());
    }

我們看到這個(gè)方法直接調(diào)用ResourcesManager對(duì)象的getResources()方法:

    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);
        }
    }

這個(gè)方法會(huì)以apk各種資源文件路徑為參數(shù)創(chuàng)建ResourcesKey對(duì)象,接著會(huì)講類加載器賦值給ResourcesManager的變量classLoader,最后會(huì)調(diào)用getOrCreateResources()方法:

 private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
            @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
        synchronized (this) {
.......
            if (activityToken != null) {
.......
                //根據(jù)ResourcesKey來查找
                ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
                if (resourcesImpl != null) {
                    if (DEBUG) {
                        Slog.d(TAG, "- using existing impl=" + resourcesImpl);
                    }
                    //如果 resourcesImpl 有 那么根據(jù)resourcesImpl 和classLoader 從緩存
                    //ActivityResources中的activityResources找找 Resource
                    return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
                            resourcesImpl);
                }

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

            } else {
.......
                  }

        // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
      //這個(gè)方法等會(huì)會(huì)重點(diǎn)來看,這里是因?yàn)榍懊嫖凑业胶线m的ResourcesImpl ,所以這里
      //會(huì)用ResourceKey來創(chuàng)建
        ResourcesImpl resourcesImpl = createResourcesImpl(key);
        if (resourcesImpl == null) {
            return null;
        }

        synchronized (this) {
            ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key);
            if (existingResourcesImpl != null) {
                if (DEBUG) {
                    Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl
                            + " new impl=" + resourcesImpl);
                }
                resourcesImpl.getAssets().close();
                resourcesImpl = existingResourcesImpl;
            } else {
                // Add this ResourcesImpl to the cache.
                mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
            }

            final Resources resources;
            if (activityToken != null) {
                //根據(jù)classloader和resourcesImpl來獲取Resources對(duì)象
                resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
                        resourcesImpl);
            } else {
                resources = getOrCreateResourcesLocked(classLoader, resourcesImpl);
            }
            return resources;
        }
    }

我們看到這個(gè)方法會(huì)先根據(jù)ResourcesKey查找ResourcesImpl對(duì)象,如果能找到的話,那么就會(huì)根據(jù)ResourcesImpl對(duì)象和classloader來獲取Resource對(duì)象。如果沒找到ResourcesImpl對(duì)象的話,那么會(huì)調(diào)用createResourcesImpl()方法創(chuàng)建。最后依然根據(jù)ResourcesImpl對(duì)象和classloader來獲取Resource對(duì)象。我們這里再來看看createResourcesImpl()方法:

 private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
        final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
        daj.setCompatibilityInfo(key.mCompatInfo);
       //創(chuàng)建 AssetManager 對(duì)象
        final AssetManager assets = createAssetManager(key);
        if (assets == null) {
            return null;
        }

        final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
        final Configuration config = generateConfig(key, dm);
        //根據(jù)AssetManager 創(chuàng)建一個(gè)ResourcesImpl。所以查找資源的話就會(huì)查找到Resources
        //然后ResourcesImpl,最后就是到AssetManager
        final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);
        if (DEBUG) {
            Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
        }
        return impl;
    }

這個(gè)方法里面有一個(gè)很重要的方法那就是createAssetManager()方法,這個(gè)方法會(huì)返回AssetManager對(duì)象,我們來看下這個(gè)方法:

  @VisibleForTesting
    protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
        AssetManager assets = new AssetManager();

        // resDir can be null if the 'android' package is creating a new Resources object.
        // This is fine, since each AssetManager automatically loads the 'android' package
        // already.
        if (key.mResDir != null) {
            if (assets.addAssetPath(key.mResDir) == 0) {
                Log.e(TAG, "failed to add asset path " + key.mResDir);
                return null;
            }
        }

        if (key.mSplitResDirs != null) {
            for (final String splitResDir : key.mSplitResDirs) {
                if (assets.addAssetPath(splitResDir) == 0) {
                    Log.e(TAG, "failed to add split asset path " + splitResDir);
                    return null;
                }
            }
        }

        if (key.mOverlayDirs != null) {
            for (final String idmapPath : key.mOverlayDirs) {
                assets.addOverlayPath(idmapPath);
            }
        }

        if (key.mLibDirs != null) {
            for (final String libDir : key.mLibDirs) {
                if (libDir.endsWith(".apk")) {
                    // Avoid opening files we know do not have resources,
                    // like code-only .jar files.
                    if (assets.addAssetPathAsSharedLibrary(libDir) == 0) {
                        Log.w(TAG, "Asset path '" + libDir +
                                "' does not exist or contains no resources.");
                    }
                }
            }
        }
        return assets;
    }

上面的方法我們可以很清楚看到這個(gè)方法分別調(diào)用了AssetManager對(duì)象的addAssetPath,addOverlayPath,addAssetPathAsSharedLibrary方法,這些方法都是native的方法,我們不去細(xì)看,這幾個(gè)方法就是分別加載相應(yīng)資源文件路徑的資源。其中addAssetPathAsSharedLibrary方法調(diào)用之前還會(huì)判斷是不是以apk開頭的,是因?yàn)閖ar文件中不能包含資源文件。我們最后看下獲取了ResourcesImpl對(duì)象之后,由于activityToken 為null,所以最終會(huì)調(diào)用getOrCreateResourcesLocked方法:

  private @NonNull Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader,
            @NonNull ResourcesImpl impl) {
        // Find an existing Resources that has this ResourcesImpl set.
        final int refCount = mResourceReferences.size();
        for (int i = 0; i < refCount; i++) {
          //在Resources的弱引用中查找
            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.
        // 創(chuàng)建一個(gè)Resources ,Resource有好幾個(gè)構(gòu)造方法,每個(gè)版本之間有稍微的差別 
        // 有的版本是用的這一個(gè)構(gòu)造方法 Resources(assets, dm, config, compatInfo)
        Resources resources = 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;
    }

我們看到創(chuàng)建好ResourcesImpl之后會(huì)再去緩存中找Resource如果沒有,那么則會(huì)創(chuàng)建Resource并將其緩存。到這里我們的加載資源部分已經(jīng)完畢,我們可以看到我們?nèi)绻虞d外部的資源文件,我們可以反射調(diào)用AssetManager#addAssetPath方法來加載我們自己的資源文件。這個(gè)地方到時(shí)會(huì)詳細(xì)講解,現(xiàn)在只要有個(gè)意識(shí)就可以。

總結(jié):到這里資源的加載就已經(jīng)講完了,我們下一篇要寫資源的查找過程,大家就當(dāng)擴(kuò)展一下知識(shí),希望大家在這個(gè)系列的文章中能有所收獲,不正確的歡迎指出,虛心接受。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容