Android資源訪問機制—獲取Resources對象

我們知道在開發中,需要應用程序資源,如應用工程中assets和res目錄下的圖片,layout,values等,或者需要系統內置的資源。我們獲取這些資源的入口對象都是Resources對象,並博文將分析如何獲取Resources對象。

 

獲取Resources的過程:

(1)將framework/framework-res.apk和應用資源apk裝載爲Resources對象。

(2)獲取Resources對象

獲取Resources對象有兩種方式,第一種通過Context,第二種通過PackageManager。

 

1. 通過Context獲取Resources對象

 

在一個Acitvity或者一個Service中,我們直接this.getResources()方法,就可以獲得Reousrces對象。其實Acitivity或者Service本質上就是一個Context.getResources()方法來自Context,而真正實現Context接口是ContextImpl類,所以調用的實際上時ContextImpl類的getResources()方法。

我們查看ContextImpl類源碼可以看到,getResources方法直接返回內部的mResources變量,而對該變量的賦值在私有的構造方法中。


core/java/android/app/ContextImpl.java


    private ContextImpl(ContextImpl container, ActivityThread mainThread,

            LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,

            Display display, Configuration overrideConfiguration) {

        mOuterContext = this;

        mMainThread = mainThread;

        mActivityToken = activityToken;

        mRestricted = restricted;

        if (user == null) {

            user = Process.myUserHandle();

        }

        mUser = user;

        mPackageInfo = packageInfo;

        mContentResolver = new ApplicationContentResolver(this, mainThread, user);

        mResourcesManager = ResourcesManager.getInstance();

        mDisplay = display;

        mOverrideConfiguration = overrideConfiguration;

        final int displayId = getDisplayId();

        CompatibilityInfo compatInfo = null;

        if (container != null) {

            compatInfo = container.getDisplayAdjustments(displayId).getCompatibilityInfo();

        }

        if (compatInfo == null && displayId == Display.DEFAULT_DISPLAY) {

            compatInfo = packageInfo.getCompatibilityInfo();

        }

        mDisplayAdjustments.setCompatibilityInfo(compatInfo);

        mDisplayAdjustments.setActivityToken(activityToken);

        Resources resources = packageInfo.getResources(mainThread);

        if (resources != null) {

            if (activityToken != null

                    || displayId != Display.DEFAULT_DISPLAY

                    || overrideConfiguration != null

                    || (compatInfo != null && compatInfo.applicationScale

                            != resources.getCompatibilityInfo().applicationScale)) {

                resources = mResourcesManager.getTopLevelResources(

                        packageInfo.getResDir(), displayId,

                        overrideConfiguration, compatInfo, activityToken);

            }

        }

        mResources = resources;

        if (container != null) {

            mBasePackageName = container.mBasePackageName;

            mOpPackageName = container.mOpPackageName;

        } else {

            mBasePackageName = packageInfo.mPackageName;

            ApplicationInfo ainfo = packageInfo.getApplicationInfo();

            if (ainfo.uid == Process.SYSTEM_UID && ainfo.uid != Process.myUid()) {

                // Special case: system components allow themselves to be loaded in to other

                // processes.  For purposes of app ops, we must then consider the context as

                // belonging to the package of this process, not the system itself, otherwise

                // the package+uid verifications in app ops will fail.

                mOpPackageName = ActivityThread.currentPackageName();

            } else {

                mOpPackageName = mBasePackageName;

            }

        }

    }


mResources又是調用LoadedApk的getResources方法進行賦值。代碼如下。


public Resources getResources(ActivityThread mainThread) {  

    if (mResources == null) {  

        mResources = mainThread.getTopLevelResources(mResDir, this);  

    }  

    return mResources;  

}


 從代碼中可以看到,最終mResources的賦值是由AcitivtyThread的getTopLevelResources方法返回。代碼如下。


Resources getTopLevelResources(String resDir, CompatibilityInfo compInfo) {  

    ResourcesKey key = new ResourcesKey(resDir, compInfo.applicationScale);  

    Resources r;  

    synchronized (mPackages) {  

        // Resources is app scale dependent.  

        if (false) {  

            Slog.w(TAG, "getTopLevelResources: " + resDir + " / "  

                    + compInfo.applicationScale);  

        }  

        WeakReference<Resources> wr = mActiveResources.get(key);  

        r = wr != null ? wr.get() : null;  

          

        if (r != null && r.getAssets().isUpToDate()) {  

            if (false) {  

                Slog.w(TAG, "Returning cached resources " + r + " " + resDir  

                        + ": appScale=" + r.getCompatibilityInfo().applicationScale);  

            }  

            return r;  

        }  

    }  

  

    AssetManager assets = new AssetManager();  

    if (assets.addAssetPath(resDir) == 0) {  

        return null;  

    }  

  

    DisplayMetrics metrics = getDisplayMetricsLocked(false);  

    r = new Resources(assets, metrics, getConfiguration(), compInfo);  

    if (false) {  

        Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "  

                + r.getConfiguration() + " appScale="  

                + r.getCompatibilityInfo().applicationScale);  

    }  

      

    synchronized (mPackages) {  

        WeakReference<Resources> wr = mActiveResources.get(key);  

        Resources existing = wr != null ? wr.get() : null;  

        if (existing != null && existing.getAssets().isUpToDate()) {  

            // Someone else already created the resources while we were  

            // unlocked; go ahead and use theirs.  

            r.getAssets().close();  

            return existing;  

        }  

        // XXX need to remove entries when weak references go away  

        mActiveResources.put(key, new WeakReference<Resources>(r));  

        return r;  

    }  


以上代碼中,mActiveResources對象內部保存了該應用程序所使用到的所有Resources對象,其類型爲Hash<ResourcesKey,WeakReference<Resourcces>>,可以看出這些Resources對象都是以一個弱引用的方式保存,以便在內存緊張時可以釋放Resources所佔內存。

ResourcesKey的構造需要resDir和compInfo.applicationScale。resdDir變量的含義是資源文件所在路徑,實際指的是APK程序所在路徑,比如可以是:/data/app/com.haii.android.xxx-1.apk,該apk會對應/data/dalvik-cache目錄下的:data@[email protected]@classes.dex文件。

所以,如果一個應用程序沒有訪問該程序以外的資源,那麼mActiveResources變量中就僅有一個Resources對象。這也從側面說明,mActiveResources內部可能包含多個Resources對象,條件是必須有不同的ResourceKey,也就是必須有不同的resDir,這就意味着一個應用程序可以訪問另外的APK文件,並從中讀取讀取其資源。(PS:其實目前的“換膚”就是採用加載不同的資源apk實現主題切換的)

如果mActivityResources對象中沒有包含所要的Resources對象,那麼,就重新建立一個Resources對象


r = new Resources(assets, metrics, getConfiguration(), compInfo);  


 可以看出構造一個Resources需要一個AssetManager對象,一個DisplayMetrics對象,一個Configuration對象,一個CompatibilityInfo對象,後三者傳入的對象都與設備或者Android平臺相關的參數,因爲資源的使用與這些信息總是相關。還有一個AssetManager對象,其實它並不是訪問項目中res/assets下的資源,而是訪問res下所有的資源。以上代碼中的addAssetPath(resDir)非常關鍵,它爲所創建的AssetManager對象添加一個資源路徑。

AssetManager類的構造函數如下:


public AssetManager() {  

    synchronized (this) {  

        if (DEBUG_REFS) {  

            mNumRefs = 0;  

            incRefsLocked(this.hashCode());  

        }  

        init();  

        if (localLOGV) Log.v(TAG, "New asset manager: " + this);  

        ensureSystemAssets();  

    }  


構造方法中調用兩個方法init()和ensureSystemAssets(),init方法是一個native實現。AssetManager.java對應的C++文件是android_util_AssetManager.cpp(注意不是AssetManager.cpp,它是C++層內部使用的cpp文件,與Java層無關)。下面看一下init()的native實現。


static void android_content_AssetManager_init(JNIEnv* env, jobject clazz)  

{  

    AssetManager* am = new AssetManager();  

    if (am == NULL) {  

        jniThrowException(env, "java/lang/OutOfMemoryError", "");  

        return;  

    }  

  

    am->addDefaultAssets();  

  

    LOGV("Created AssetManager %p for Java object %p\n", am, clazz);  

    env->SetIntField(clazz, gAssetManagerOffsets.mObject, (jint)am);  


首先創建一個C++類的AssetManager對象,然後調用am->addDefaultAssets()方法,該方法的作用就是把framework的資源文件添加到這個AssetManager對象的路徑中。最後調用setInitField()方法把C++創建的AssetManager對象的引用保存到Java端的mObject變量中,這種方式是常用的C++層與Java層通信的方式。

addDefaultAssets代碼如下:


bool AssetManager::addDefaultAssets()  

{  

    const char* root = getenv("ANDROID_ROOT");  

    LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_ROOT not set");  

  

    String8 path(root);  

    path.appendPath(kSystemAssets);  

  

    return addAssetPath(path, NULL);  


該函數首先獲取Android的根目錄,getenv是一個Linux系統調用,用戶同樣可以使用以下終端命令獲取該值。

獲得根目錄後,再與kSystemAssets路徑進行組合,該變量的定義如下:


static const char* kSystemAssets = "framework/framework-res.apk";  


 所以最終獲得的路徑文件名稱爲/system/framework/framework-res.apk,這正式framework對應的資源文件。

分析完了AssetManager的init方法,再來看一下ensureSystemAssets方法。


private static void ensureSystemAssets() {  

    synchronized (sSync) {  

        if (sSystem == null) {  

            AssetManager system = new AssetManager(true);  

            system.makeStringBlocks(false);  

            sSystem = system;  

        }  

    }  

}  


 該方法實際上僅在framework啓動時就已經調用了,因爲sSystem是一個靜態的AssetManager對象,該變量在Zygote啓動時已經賦值了,以後都不爲空,所以該方法形同虛設。

由此可以知道,Resources對象內部的AssetManager對象除了包含應用程序本身的資源路徑外,還包含了framework的資源路徑,這就是爲什麼應用程序僅使用Resources對象就可以訪問應用資源和系統資源的原因。如


Resources res = getResources();  

Drawable btnPic = res.getDrawable(android.R.drawable.btn_default_small);  


那麼如何AssetManager如何區分訪問的是系統資源還是應用資源呢?當使用getXXX(int id)訪問資源時,如果id值小於0x10000000時,AssetManager會認爲要訪問的是系統資源。因爲aapt在對系統資源進行編譯時,所有的資源id都被編譯爲小於該值的一個int值,而當訪問應用資源時,id值都大於0x70000000。 

創建好了Resources對象後,就把該變量緩存到mActiveResources中,以便以後繼續使用。

訪問Resources內部的整個流程如下圖。


2. 通過PackageManager獲取Resources對象

    文件路徑/frameworks/base/+/android-4.4.4_r2.0.1/core/java/android/content/pm/PackageManager.java

    packageManager爲抽象類,,跟Resource相關的方法有:


public abstract Resources getResourcesForActivity(ComponentName activityName)

       throws NameNotFoundException;

public abstract Resources getResourcesForApplication(ApplicationInfo app)

public abstract Resources getResourcesForApplication(String appPackageName)

       throws NameNotFoundException;


/** @hide */

public abstract Resources getResourcesForApplicationAsUser(String appPackageName, int userId)

       throws NameNotFoundException;

       throws NameNotFoundException;


具體的實現類爲ApplicationPackageManager.java


    @Override public Resources getResourcesForActivity(

        ComponentName activityName) throws NameNotFoundException {

        return getResourcesForApplication(

            getActivityInfo(activityName, 0).applicationInfo);

    }

    @Override public Resources getResourcesForApplication(

        ApplicationInfo app) throws NameNotFoundException {

        if (app.packageName.equals("system")) {

            return mContext.mMainThread.getSystemContext().getResources();

        }

        Resources r = mContext.mMainThread.getTopLevelResources(

                app.uid == Process.myUid() ? app.sourceDir : app.publicSourceDir,

                        Display.DEFAULT_DISPLAY, null, mContext.mPackageInfo);

        if (r != null) {

            return r;

        }

        throw new NameNotFoundException("Unable to open " + app.publicSourceDir);

    }

    @Override public Resources getResourcesForApplication(

        String appPackageName) throws NameNotFoundException {

        return getResourcesForApplication(

            getApplicationInfo(appPackageName, 0));

    }

    /** @hide */

    @Override

    public Resources getResourcesForApplicationAsUser(String appPackageName, int userId)

            throws NameNotFoundException {

        if (userId < 0) {

            throw new IllegalArgumentException(

                    "Call does not support special user #" + userId);

        }

        if ("system".equals(appPackageName)) {

            return mContext.mMainThread.getSystemContext().getResources();

        }

        try {

            ApplicationInfo ai = mPM.getApplicationInfo(appPackageName, 0, userId);

            if (ai != null) {

                return getResourcesForApplication(ai);

            }

        } catch (RemoteException e) {

            throw new RuntimeException("Package manager has died", e);

        }

        throw new NameNotFoundException("Package " + appPackageName + " doesn't exist");

    }


1.包名爲system的情況,mContext.mMainThread.getSystemContext().getResources()

ActivityThread.getSystemContext


public ContextImpl getSystemContext() {  

    synchronized (this) {  

        if (mSystemContext == null) {  

            mSystemContext = ContextImpl.createSystemContext(this);  

        }  

        return mSystemContext;  

    }  

}


ContextImpl.createSystemContext


   static ContextImpl createSystemContext(ActivityThread mainThread) {

        LoadedApk packageInfo = new LoadedApk(mainThread);

        ContextImpl context = new ContextImpl(null, mainThread,

                packageInfo, null, null, false, null, null);

        context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(),

                context.mResourcesManager.getDisplayMetricsLocked(Display.DEFAULT_DISPLAY));

        return context;

    }

    static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {

        if (packageInfo == null) throw new IllegalArgumentException("packageInfo");

        return new ContextImpl(null, mainThread,

                packageInfo, null, null, false, null, null);

    }

    static ContextImpl createActivityContext(ActivityThread mainThread,

            LoadedApk packageInfo, IBinder activityToken) {

        if (packageInfo == null) throw new IllegalArgumentException("packageInfo");

        if (activityToken == null) throw new IllegalArgumentException("activityInfo");

        return new ContextImpl(null, mainThread,

                packageInfo, activityToken, null, false, null, null);

    }

    private ContextImpl(ContextImpl container, ActivityThread mainThread,

            LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,

            Display display, Configuration overrideConfiguration) {

        mOuterContext = this;

        mMainThread = mainThread;

        mActivityToken = activityToken;

        mRestricted = restricted;

        if (user == null) {

            user = Process.myUserHandle();

        }

        mUser = user;

        mPackageInfo = packageInfo;

        mContentResolver = new ApplicationContentResolver(this, mainThread, user);

        mResourcesManager = ResourcesManager.getInstance();

        mDisplay = display;

        mOverrideConfiguration = overrideConfiguration;

        final int displayId = getDisplayId();

        CompatibilityInfo compatInfo = null;

        if (container != null) {

            compatInfo = container.getDisplayAdjustments(displayId).getCompatibilityInfo();

        }

        if (compatInfo == null && displayId == Display.DEFAULT_DISPLAY) {

            compatInfo = packageInfo.getCompatibilityInfo();

        }

        mDisplayAdjustments.setCompatibilityInfo(compatInfo);

        mDisplayAdjustments.setActivityToken(activityToken);

        Resources resources = packageInfo.getResources(mainThread);

        if (resources != null) {

            if (activityToken != null

                    || displayId != Display.DEFAULT_DISPLAY

                    || overrideConfiguration != null

                    || (compatInfo != null && compatInfo.applicationScale

                            != resources.getCompatibilityInfo().applicationScale)) {

                resources = mResourcesManager.getTopLevelResources(

                        packageInfo.getResDir(), displayId,

                        overrideConfiguration, compatInfo, activityToken);

            }

        }

        mResources = resources;

        if (container != null) {

            mBasePackageName = container.mBasePackageName;

            mOpPackageName = container.mOpPackageName;

        } else {

            mBasePackageName = packageInfo.mPackageName;

            ApplicationInfo ainfo = packageInfo.getApplicationInfo();

            if (ainfo.uid == Process.SYSTEM_UID && ainfo.uid != Process.myUid()) {

                // Special case: system components allow themselves to be loaded in to other

                // processes.  For purposes of app ops, we must then consider the context as

                // belonging to the package of this process, not the system itself, otherwise

                // the package+uid verifications in app ops will fail.

                mOpPackageName = ActivityThread.currentPackageName();

            } else {

                mOpPackageName = mBasePackageName;

            }

        }

    }


最終的mResource是通過packageinfo.getResources和mResoucesManager.getTopLevelResources得到

packageInfo爲core/java/android/app/LoadedApk.java


public Resources getResources(ActivityThread mainThread) {

        if (mResources == null) {

            mResources = mainThread.getTopLevelResources(mResDir,

                    Display.DEFAULT_DISPLAY, null, this);

        }

        return mResources;

    }


core/java/android/app/ResourcesManager.java


  1. /**

  2. * Creates the top level Resources for applications with the given compatibility info.

  3. *

  4. * @param resDir the resource directory.

  5. * @param compatInfo the compability info. Must not be null.

  6. * @param token the application token for determining stack bounds.

  7. */

  8. public Resources getTopLevelResources(String resDir, int displayId,

  9.        Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) {

  10.    final float scale = compatInfo.applicationScale;

  11.    ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale,

  12.            token);

  13.    Resources r;

  14.    synchronized (this) {

  15.        // Resources is app scale dependent.

  16.        if (false) {

  17.            Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale);

  18.        }

  19.        WeakReference<Resources> wr = mActiveResources.get(key);

  20.        r = wr != null ? wr.get() : null;

  21.        //if (r != null) Slog.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());

  22.        if (r != null && r.getAssets().isUpToDate()) {

  23.            if (false) {

  24.                Slog.w(TAG, "Returning cached resources " + r + " " + resDir

  25.                        + ": appScale=" + r.getCompatibilityInfo().applicationScale);

  26.            }

  27.            return r;

  28.        }

  29.    }

  30.    //if (r != null) {

  31.    //    Slog.w(TAG, "Throwing away out-of-date resources!!!! "

  32.    //            + r + " " + resDir);

  33.    //}

  34.    AssetManager assets = new AssetManager();

  35.    if (assets.addAssetPath(resDir) == 0) {

  36.        return null;

  37.    }

  38.    //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);

  39.    DisplayMetrics dm = getDisplayMetricsLocked(displayId);

  40.    Configuration config;

  41.    boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);

  42.    final boolean hasOverrideConfig = key.hasOverrideConfiguration();

  43.    if (!isDefaultDisplay || hasOverrideConfig) {

  44.        config = new Configuration(getConfiguration());

  45.        if (!isDefaultDisplay) {

  46.            applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config);

  47.        }

  48.        if (hasOverrideConfig) {

  49.            config.updateFrom(key.mOverrideConfiguration);

  50.        }

  51.    } else {

  52.        config = getConfiguration();

  53.    }

  54.    r = new Resources(assets, dm, config, compatInfo, token);

  55.    if (false) {

  56.        Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "

  57.                + r.getConfiguration() + " appScale="

  58.                + r.getCompatibilityInfo().applicationScale);

  59.    }

  60.    synchronized (this) {

  61.        WeakReference<Resources> wr = mActiveResources.get(key);

  62.        Resources existing = wr != null ? wr.get() : null;

  63.        if (existing != null && existing.getAssets().isUpToDate()) {

  64.            // Someone else already created the resources while we were

  65.            // unlocked; go ahead and use theirs.

  66.            r.getAssets().close();

  67.            return existing;

  68.        }

  69.        // XXX need to remove entries when weak references go away

  70.        mActiveResources.put(key, new WeakReference<Resources>(r));

  71.        return r;

  72.    }

  73. }


這裏我們可以看到創建的Resouces保存到ResourcesManager中的mActiveResources中,相同的包名存對應的key,存在對應的Resources


final ArrayMap<ResourcesKey, WeakReference<Resources> > mActiveResources  

            = new ArrayMap<ResourcesKey, WeakReference<Resources> >();


2.通用情況,mContext.mMainThread.getTopLevelResources


<pre name="code" class="java">    /** 

     * Creates the top level resources for the given package. 

     */  

    Resources getTopLevelResources(String resDir,  

            int displayId, Configuration overrideConfiguration,  

            LoadedApk pkgInfo) {  

        return mResourcesManager.getTopLevelResources(resDir, displayId, overrideConfiguration,  

                pkgInfo.getCompatibilityInfo(), null);  

    }


結果同樣通過ResourcesManager.getTopLevelResources來獲取Resources



結論:

1.獲取Resources,最終都是通過ResourcesManager獲取

2.ResourcesManager中使用

ArrayMap<ResourcesKey, WeakReference<Resources> > mActiveResources保存Resources的鍵值對
在內存不足是,弱引用會被回收


PS:

在Resources被回收後,會重新創建Resources對象
->Activity.
getResources

->ContextThemeWrapper.getResources

->ContextWrapper.createConfigurationContext

->ContextImpl.createConfigurationContext


ContextThemeWrapper.getResources


    public Resources getResources() {

        if (mResources != null) {

            return mResources;

        }

        if (mOverrideConfiguration == null) {

            mResources = super.getResources();

            return mResources;

        } else {

            Context resc = createConfigurationContext(mOverrideConfiguration);

            mResources = resc.getResources();

            return mResources;

        }

    }


ContextWrapper.createConfigurationContext


public Context createConfigurationContext(Configuration overrideConfiguration) {  

    return mBase.createConfigurationContext(overrideConfiguration);  

}


ContextImpl.createConfigurationContext


public Context createConfigurationContext(Configuration overrideConfiguration) {  

    if (overrideConfiguration == null) {  

        throw new IllegalArgumentException("overrideConfiguration must not be null");  

    }  

    return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,  

            mUser, mRestricted, mDisplay, overrideConfiguration);  

}


最後調用ContextImpl的構造方法創建Context


良心的公衆號,更多精品文章,不要忘記關注哈

《Android和Java技術驛站》


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