Android源碼分析-資源加載機制

轉載請註明出處:http://blog.csdn.net/singwhatiwanna/article/details/23387079 (來自singwhatiwanna的csdn博客)
java

前言

咱們知道,在activity內部訪問資源(字符串,圖片等)是很簡單的,只要getResources而後就能夠獲得Resources對象,有了Resources對象就能夠訪問各類資源了,這很簡單,不過本文不是介紹這個的,本文主要介紹在這套邏輯之下的資源加載機制android

資源加載機制

很明確,不一樣的Context獲得的都是同一份資源。這是很好理解的,請看下面的分析
獲得資源的方式爲context.getResources,而真正的實現位於ContextImpl中的getResources方法,在ContextImpl中有一個成員 private Resources mResources,它就是getResources方法返回的結果,mResources的賦值代碼爲:
mResources = mResourcesManager.getTopLevelResources(mPackageInfo.getResDir(),
                    Display.DEFAULT_DISPLAY, null, compatInfo, activityToken);
下面看一下ResourcesManager的getTopLevelResources方法,這個方法的思想是這樣的:在ResourcesManager中,全部的資源對象都被存儲在ArrayMap中,首先根據當前的請求參數去查找資源,若是找到了就返回,不然就建立一個資源對象放到ArrayMap中。有一點須要說明的是爲何會有多個資源對象,緣由很簡單,由於res下可能存在多個適配不一樣設備、不一樣分辨率、不一樣系統版本的目錄,按照android系統的設計,不一樣設備在訪問同一個應用的時候訪問的資源能夠不一樣,好比drawable-hdpi和drawable-xhdpi就是典型的例子。
api

public Resources getTopLevelResources(String resDir, int displayId,
		Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) {
	final float scale = compatInfo.applicationScale;
	ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale,
			token);
	Resources r;
	synchronized (this) {
		// Resources is app scale dependent.
		if (false) {
			Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale);
		}
		WeakReference<Resources> wr = mActiveResources.get(key);
		r = wr != null ? wr.get() : null;
		//if (r != null) Slog.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());
		if (r != null && r.getAssets().isUpToDate()) {
			if (false) {
				Slog.w(TAG, "Returning cached resources " + r + " " + resDir
						+ ": appScale=" + r.getCompatibilityInfo().applicationScale);
			}
			return r;
		}
	}

	//if (r != null) {
	//    Slog.w(TAG, "Throwing away out-of-date resources!!!! "
	//            + r + " " + resDir);
	//}

	AssetManager assets = new AssetManager();
	if (assets.addAssetPath(resDir) == 0) {
		return null;
	}

	//Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
	DisplayMetrics dm = getDisplayMetricsLocked(displayId);
	Configuration config;
	boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
	final boolean hasOverrideConfig = key.hasOverrideConfiguration();
	if (!isDefaultDisplay || hasOverrideConfig) {
		config = new Configuration(getConfiguration());
		if (!isDefaultDisplay) {
			applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config);
		}
		if (hasOverrideConfig) {
			config.updateFrom(key.mOverrideConfiguration);
		}
	} else {
		config = getConfiguration();
	}
	r = new Resources(assets, dm, config, compatInfo, token);
	if (false) {
		Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "
				+ r.getConfiguration() + " appScale="
				+ r.getCompatibilityInfo().applicationScale);
	}

	synchronized (this) {
		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;
	}
}
根據上述代碼中資源的請求機制,再加上ResourcesManager採用單例模式,這樣就保證了不一樣的ContextImpl訪問的是同一套資源,注意,這裏說的同一套資源未必是同一個資源,由於資源可能位於不一樣的目錄,但它必定是咱們的應用的資源,或許這樣來描述更準確,在設備參數和顯示參數不變的狀況下,不一樣的ContextImpl訪問到的是同一份資源。設備參數不變是指手機的屏幕和android版本不變,顯示參數不變是指手機的分辨率和橫豎屏狀態。也就是說,儘管Application、Activity、Service都有本身的ContextImpl,而且每一個ContextImpl都有本身的mResources成員,可是因爲它們的mResources成員都來自於惟一的ResourcesManager實例,因此它們看似不一樣的mResources其實都指向的是同一塊內存(C語言的概念),所以,它們的mResources都是同一個對象(在設備參數和顯示參數不變的狀況下)。在橫豎屏切換的狀況下且應用中爲橫豎屏狀態提供了不一樣的資源,處在橫屏狀態下的ContextImpl和處在豎屏狀態下的ContextImpl訪問的資源不是同一個資源對象。

代碼:單例模式的ResourcesManager類
public static ResourcesManager getInstance() {
        synchronized (ResourcesManager.class) {
            if (sResourcesManager == null) {
                sResourcesManager = new ResourcesManager();
            }
            return sResourcesManager;
        }
    }

Resources對象的建立過程

經過閱讀Resources類的源碼能夠知道,Resources對資源的訪問其實是經過AssetManager來實現的,那麼如何建立一個Resources對象呢,有人會問,我爲何要去建立一個Resources對象呢,直接getResources不就能夠了嗎?我要說的是在某些特殊狀況下你的確須要去建立一個資源對象,好比動態加載apk。很簡單,首先看一下它的幾個構造方法:cookie

/**
     * Create a new Resources object on top of an existing set of assets in an
     * AssetManager.
     * 
     * @param assets Previously created AssetManager. 
     * @param metrics Current display metrics to consider when 
     *                selecting/computing resource values.
     * @param config Desired device configuration to consider when 
     *               selecting/computing resource values (optional).
     */
    public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
        this(assets, metrics, config, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
    }

    /**
     * Creates a new Resources object with CompatibilityInfo.
     * 
     * @param assets Previously created AssetManager. 
     * @param metrics Current display metrics to consider when 
     *                selecting/computing resource values.
     * @param config Desired device configuration to consider when 
     *               selecting/computing resource values (optional).
     * @param compatInfo this resource's compatibility info. Must not be null.
     * @param token The Activity token for determining stack affiliation. Usually null.
     * @hide
     */
    public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config,
            CompatibilityInfo compatInfo, IBinder token) {
        mAssets = assets;
        mMetrics.setToDefaults();
        if (compatInfo != null) {
            mCompatibilityInfo = compatInfo;
        }
        mToken = new WeakReference<IBinder>(token);
        updateConfiguration(config, metrics);
        assets.ensureStringBlocks();
    }
除了這兩個構造方法還有一個私有的無參方法,因爲是私有的,因此無法訪問。上面兩個構造方法,從簡單起見,咱們應該採用第一個

public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config)app

它接受3個參數,第一個是AssetManager,後面兩個是和設備相關的配置參數,咱們能夠直接用當前應用的配置就好,因此,問題的關鍵在於如何建立AssetManager,下面請看分析,爲了建立一個咱們本身的AssetManager,咱們先去看看系統是怎麼建立的。還記得getResources的底層實現嗎,在ResourcesManager的getTopLevelResources方法中有這麼兩句:ide

AssetManager assets = new AssetManager();
	if (assets.addAssetPath(resDir) == 0) {
		return null;
	}

這兩句就是建立一個AssetManager對象,後面會用這個對象來建立Resources對象,ok,AssetManager就是這麼建立的,assets.addAssetPath(resDir)這句話的意思是把資源目錄裏的資源都加載到AssetManager對象中,具體的實如今jni中,你們感興趣本身去了解下。而資源目錄就是咱們的res目錄,固然resDir能夠是一個目錄也能夠是一個zip文件。有沒有想過,若是咱們把一個未安裝的apk的路徑傳給這個方法,那麼apk中的資源是否是就被加載到AssetManager對象裏面了呢?事實證實,的確是這樣,具體狀況能夠參見Android apk動態加載機制的研究(二):資源加載和activity生命週期管理這篇文章。addAssetPath方法的定義以下,注意到它的註釋裏面有一個{@hide}關鍵字,這意味着即便它是public的,可是外界仍然沒法訪問它,由於android sdk導出的時候會自動忽略隱藏的api,所以只能經過反射來調用。this

/**
     * Add an additional set of assets to the asset manager.  This can be
     * either a directory or ZIP file.  Not for use by applications.  Returns
     * the cookie of the added asset, or 0 on failure.
     * {@hide}
     */
    public final int addAssetPath(String path) {
        int res = addAssetPathNative(path);
        return res;
    }

有了AssetManager對象後,咱們就能夠建立本身的Resources對象了,代碼以下:.net

try {
		AssetManager assetManager = AssetManager.class.newInstance();
		Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
		addAssetPath.invoke(assetManager, mDexPath);
		mAssetManager = assetManager;
	} catch (Exception e) {
		e.printStackTrace();
	}
	Resources currentRes = this.getResources();
	mResources = new Resources(mAssetManager, currentRes.getDisplayMetrics(),
			currentRes.getConfiguration());
有了Resources對象,咱們就能夠經過Resources對象來訪問裏面的各類資源了,經過這種方法,咱們能夠完成一些特殊的功能,好比換膚、換語言包、動態加載apk等,歡迎你們交流。
相關文章
相關標籤/搜索