Android资源加载过程分析

涉及源码(Android 4.4.2):
/frameworks/base/core/java/android/app/ResourcesManager.java
/frameworks/base/core/jni/android_util_AssetManager.cpp
/core/java/android/content/res/AssetManager.java
/frameworks/base/libs/androidfw/AssetManager.cpp

我们知道,如果我们希望使用或者得到某些资源,我们通常的做法是使用Context的getXXX方法,例如:

public final Drawable getDrawable(int id) {
    return getResources().getDrawable(id, getTheme());
}

可以看到它首先需要获取到Resources资源对象,从资源对象中得到想要的资源。当我们使用Context的getResources方法来获取Resources对象的时候,最终调用的是ResourcesManager的getTopLevelResources方法来获取到对应的Resources对象。

ResourcesManager采用的是单例模式,这就保证了所有的Context调用的都是同一个ResourcesManager对象的getTopLevelResources方法,所以不同Context的getResources方法获取是同一套资源对象。

/frameworks/base/core/java/android/app/ResourcesManager.java

// 单例模式获取ResourcesManager对象
public static ResourcesManager getInstance() {  
    synchronized (ResourcesManager.class) {  
        if (sResourcesManager == null) {  
            sResourcesManager = new ResourcesManager();  
        }  
        return sResourcesManager;  
    }  
} 

下面来看看ResourcesManager的getTopLevelResources方法

/frameworks/base/core/java/android/app/ResourcesManager.java

// 资源对象存放在ArrayMap的集合中,并且对象使用的是弱引用
final ArrayMap<ResourcesKey, WeakReference<Resources> > mActiveResources
            = new ArrayMap<ResourcesKey, WeakReference<Resources> >();
            
public Resources getTopLevelResources(String resDir, int displayId,
        Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) {
    final float scale = compatInfo.applicationScale;
    // 1、通过传入的参数确定资源对象的key值
    ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale,
            token);
    Resources r;
    synchronized (this) {
        // 2、通过key值,从mActiveResources Map集合中获取对应的资源对象
        WeakReference<Resources> wr = mActiveResources.get(key);
        r = wr != null ? wr.get() : null;
        // 3、如果资源对象不为空,直接将其返回,否则执行步骤4
        if (r != null && r.getAssets().isUpToDate()) {
            return r;
        }
    }

    // 4、创建AssetManager对象,并且将资源目录(实际为apk文件路径)加入资源路径
    AssetManager assets = new AssetManager();
    if (assets.addAssetPath(resDir) == 0) {
        return null;
    }

    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();
    }
    
    // 5、创建资源对象
    r = new Resources(assets, dm, config, compatInfo, token);
    
    // 6、如果mActiveResources Map集合没有该资源对象,则将其加入,并将资源对象返回
    synchronized (this) {
        WeakReference<Resources> wr = mActiveResources.get(key);
        Resources existing = wr != null ? wr.get() : null;
        if (existing != null && existing.getAssets().isUpToDate()) {
            r.getAssets().close();
            return existing;
        }

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

从上面可以总结以下几点:
1、Resources对象使用了一个ArrayMap对象进行缓存,因此表明其内部可能包含多个Resources对象。
2、Resources对象中包含一个AssetManager对象,资源的添加工作是通过该对象的addAssetPath完成的。

下面就来看看具体的资源资源添加的过程

AssetManager的创建

/core/java/android/content/res/AssetManager.java

public AssetManager() {
    synchronized (this) {
        // 1、调用init初始化方法
        init();
        // 2、保证系统资源对象的存在
        ensureSystemAssets();
    }
}
1、调用init初始化方法

init方法是一个native方法,它最终调用的是android_util_AssetManager.cpp中的android_content_AssetManager_init方法,下面就进入native层进行操作了,前面的操作都是在java层处理的。

/frameworks/base/core/jni/android_util_AssetManager.cpp

static void android_content_AssetManager_init(JNIEnv* env, jobject clazz)
{   
    // 1、创建一个AssetManager对象
    AssetManager* am = new AssetManager();
    // 2、添加默认的资源
    am->addDefaultAssets();
    // 3、将AssetManager对象(C++对象)的引用保存在Java层AssetManager对象的mObject中
    env->SetIntField(clazz, gAssetManagerOffsets.mObject, (jint)am);
}
(1) 添加系统默认资源操作

/frameworks/base/libs/androidfw/AssetManager.cpp

static const char* kSystemAssets = "framework/framework-res.apk";  
bool AssetManager::addDefaultAssets()
{
    // 1、得到系统目录/system/
    const char* root = getenv("ANDROID_ROOT");
    String8 path(root);
    // 2、得到系统资源的完整路径/system/framework/framework-res.apk
    path.appendPath(kSystemAssets);
    // 3、将系统资源添加到资源路径
    return addAssetPath(path, NULL);
}

从上面可以看到,默认会将系统资源添加到资源路径,这也是我们应用可以访问到系统资源的原因。

(2)添加AssetManager对象(C++对象)引用到Java层AssetManager对象的mObject上的操作

其实就是要弄清楚gAssetManagerOffsets.mObject的什么东西?

下面来看看android_util_AssetManager.cpp中register_android_content_AssetManager方法。

/frameworks/base/core/jni/android_util_AssetManager.cpp

int register_android_content_AssetManager(JNIEnv* env)
{
    // 1、获取到java层的AssetManager类
    jclass assetManager = env->FindClass("android/content/res/AssetManager");
    // 2、获取java层的AssetManager类的mObject字段,并将其保存在gAssetManagerOffsets.mObject中
    gAssetManagerOffsets.mObject
        = env->GetFieldID(assetManager, "mObject", "I");
    return AndroidRuntime::registerNativeMethods(env,
            "android/content/res/AssetManager", gAssetManagerMethods, NELEM(gAssetManagerMethods));
}

从上面就可以知道,gAssetManagerOffsets.mObject对应就是java层的AssetManager类的mObject字段。

2、保证系统资源对象的存在

/core/java/android/content/res/AssetManager.java

private static void ensureSystemAssets() {  
    synchronized (sSync) {  
        if (sSystem == null) {  
            AssetManager system = new AssetManager(true);  
            system.makeStringBlocks(false);  
            sSystem = system;  
        }  
    }  
} 

sSystem是一个静态的AssetManager对象,在Zygote启动时已经赋值了,主要就是初次启动的时候会执行,供系统使用,上面创建AssetManager对象,创建过程跟前面相同。

上面类之间的关系如下图所示:

resourceload.png

资源路径的添加

从上图可以看到,在java层的AssetManager类中,addAssetPath方法会调用addAssetPathNative方法,addAssetPathNative方法是一个native方法,它对应的就是android_util_AssetManager.cpp中的android_content_AssetManager_addAssetPath方法。

/frameworks/base/core/jni/android_util_AssetManager.cpp

static jint android_content_AssetManager_addAssetPath(JNIEnv* env, jobject clazz,
                                                       jstring path)
{
    ScopedUtfChars path8(env, path);
    if (path8.c_str() == NULL) {
        return 0;
    }
    // 1、得到C++对象AssetManager对象
    AssetManager* am = assetManagerForJavaObject(env, clazz);
    if (am == NULL) {
        return 0;
    }

    void* cookie;
    // 2、执行addAssetPath方法
    bool res = am->addAssetPath(String8(path8.c_str()), &cookie);

    return (res) ? (jint)cookie : 0;
}

assetManagerForJavaObject方法就是拿到前面存放在java层的AssetManager类的mObject字段的值,它就是一个AssetManager的C++对象引用

AssetManager* assetManagerForJavaObject(JNIEnv* env, jobject obj)
{
    AssetManager* am = (AssetManager*)env->GetIntField(obj, gAssetManagerOffsets.mObject);
    if (am != NULL) {
        return am;
    }
    jniThrowException(env, "java/lang/IllegalStateException", "AssetManager has been finalized!");
    return NULL;
}

/frameworks/base/libs/androidfw/AssetManager.cpp

// 资源路径存放在一个Vector集合中
Vector<asset_path> mAssetPaths;

// 存放资源路径的结构体
struct asset_path
{
    String8 path;  // 路径名
    FileType type; // 文件类型
    String8 idmap;
};
bool AssetManager::addAssetPath(const String8& path, void** cookie)
{
    AutoMutex _l(mLock);

    asset_path ap;

    // 1、构造一个asset_path结构体,其实就是确定路径名和文件类型,并保存在结构体中
    String8 realPath(path);
    if (kAppZipName) {
        realPath.appendPath(kAppZipName);
    }
    ap.type = ::getFileType(realPath.string());
    if (ap.type == kFileTypeRegular) {
        ap.path = realPath;
    } else {
        ap.path = path;
        ap.type = ::getFileType(path.string());
        if (ap.type != kFileTypeDirectory && ap.type != kFileTypeRegular) {
            ALOGW("Asset path %s is neither a directory nor file (type=%d).",
                 path.string(), (int)ap.type);
            return false;
        }
    }

    // 2、如果该路径已经添加到集合中,则直接设置cookie为(索引+1)并返回,否则进入步骤3
    for (size_t i=0; i<mAssetPaths.size(); i++) {
        if (mAssetPaths[i].path == ap.path) {
            if (cookie) {
                *cookie = (void*)(i+1);
            }
            return true;
        }
    }
    // 3、将路径结构体添加到集合中
    mAssetPaths.add(ap);

    // 4、将该路径的集合size(索引+1)作为cookie的值
    if (cookie) {
        *cookie = (void*)mAssetPaths.size();
    }

    // 5、资源替换的过程,这个暂不关注
    // (Java) package manager
    if (strncmp(path.string(), "/system/framework/", 18) == 0) {
        // When there is an environment variable for /vendor, this
        // should be changed to something similar to how ANDROID_ROOT
        // and ANDROID_DATA are used in this file.
        String8 overlayPath("/vendor/overlay/framework/");
        overlayPath.append(path.getPathLeaf());
        if (TEMP_FAILURE_RETRY(access(overlayPath.string(), R_OK)) == 0) {
            asset_path oap;
            oap.path = overlayPath;
            oap.type = ::getFileType(overlayPath.string());
            bool addOverlay = (oap.type == kFileTypeRegular); // only .apks supported as overlay
            if (addOverlay) {
                oap.idmap = idmapPathForPackagePath(overlayPath);

                if (isIdmapStaleLocked(ap.path, oap.path, oap.idmap)) {
                    addOverlay = createIdmapFileLocked(ap.path, oap.path, oap.idmap);
                }
            }
            if (addOverlay) {
                mAssetPaths.add(oap);
            } else {
                ALOGW("failed to add overlay package %s\n", overlayPath.string());
            }
        }
    }

    return true;
}

如下图所示:

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

推荐阅读更多精彩内容