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

为什么会有多个资源对象?
因为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<IBinder>(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());  
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容