Android资源加载机制

参考1
参考2

获取资源的方式

先通过Context.getResources();获取Resources对象,有了Resources对象就可以访问各种资源了。

资源加载机制

通过Context获取Resources,实际是通过ContextImpl的getResources()方法;ContextImpl内部有一个成员mResources,它就是getResources()方法返回的结果;

Context.getResources();

ContextImpl

private Resources mResources
mResources = mResourcesManager.getTopLevelResources(mPackageInfo.getResDir(), Display.DEFAULT_DISPLAY, null, compatInfo, activityToken);

ResourcesManager.getTopLevelResources()
思想:在ResourcesManager中,所有的资源对象都被存储在ArrayMap中,首先根据当前的请求参数去查找资源,如果找到了就返回,否则就创建一个资源对象放到ArrayMap中。

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 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 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(r));  
        return r;  
    }  
}  
 

为什么会有多个资源对象?
因为res下可能存在多个适配不同设备、不同分辨率、不同系统版本的目录,按照Android系统的设计,不同设备在访问同一个应用的时候访问的资源可以不同,比如drawable-hdpi和drawable-xhdpi就是典型的例子。
在同一应用中不同的ContextImpl获取到的是同一套资源
根据上述代码中(ResourcesManager.getTopLevelResources())资源的请求机制,再加上ResourcesManager采用单例模式,这样就保证了不同的ContextImpl访问的是同一套资源;尽管Application、Activity、Service都有自己的ContextImpl,并且每个ContextImpl都有自己的mResources成员,但是由于它们的mResources成员都来自于唯一的ResourcesManager实例;所以它们看似不同的mResources其实都指向的是同一块内存

public static ResourcesManager getInstance() {  
    synchronized (ResourcesManager.class) {  
        if (sResourcesManager == null) {  
            sResourcesManager = new ResourcesManager();  
        }  
        return sResourcesManager;  
    }  
}  

Resources对象的创建过程

构造方法:
简单起见,我们应该采用第一个
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config)
它接受3个参数,第一个是AssetManager,后面两个是和设备相关的配置参数

方法一
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {  
    this(assets, metrics, config, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);  
}  
方法二
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config,  
        CompatibilityInfo compatInfo, IBinder token) {  
    mAssets = assets;  
    mMetrics.setToDefaults();  
    if (compatInfo != null) {  
        mCompatibilityInfo = compatInfo;  
    }  
    mToken = new WeakReference(token);  
    updateConfiguration(config, metrics);  
    assets.ensureStringBlocks();  
}  

所以创建Resources的关键就是创建AssetManager
系统创建AssetManager的方法;

ResourcesManager.getTopLevelResources()

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

assets.addAssetPath(resDir)这句话的意思是把资源目录里的资源都加载到AssetManager对象中;
但是addAssetPath方法是{@hide},这意味着即使它是public的,但是外界仍然无法访问它,因为android sdk导出的时候会自动忽略隐藏的api;

public final int addAssetPath(String path) {  
    int res = addAssetPathNative(path);  
    return res;  
}  

所以我们要通过反射才能调用 addAssetPath方法;

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

你可能感兴趣的:(Android资源加载机制)