`
willsunforjava
  • 浏览: 167848 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

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变量,而对该变量的赋值在init()方法中。

 

    final void init(LoadedApk packageInfo,
                IBinder activityToken, ActivityThread mainThread,
                Resources container) {
        mPackageInfo = packageInfo;
        mResources = mPackageInfo.getResources(mainThread);

        if (mResources != null && container != null
                && container.getCompatibilityInfo().applicationScale !=
                        mResources.getCompatibilityInfo().applicationScale) {
            if (DEBUG) {
                Log.d(TAG, "loaded context has different scaling. Using container's" +
                        " compatiblity info:" + container.getDisplayMetrics());
            }
            mResources = mainThread.getTopLevelResources(
                    mPackageInfo.getResDir(), container.getCompatibilityInfo().copy());
        }
        mMainThread = mainThread;
        mContentResolver = new ApplicationContentResolver(this, mainThread);

        setActivityToken(activityToken);
    }

 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@app@com.haii.android.xxx-1.apk@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对象

 waiting...

 

 

  • 大小: 6.8 KB
  • 大小: 4.7 KB
  • 大小: 8.9 KB
分享到:
评论
3 楼 beach126 2014-11-17  
从上述几个类的继承关系看,context是一个抽象类, contextWrap继承该类并实现该类中的方法,而activity为contextWrap的后代,而contextWrap与contextImp似乎没有关联,activity中getResource方法应该是contextWrap中的实现版本吧? 不是很明白为什么最后实际调用的是cotextImp中的方法。
2 楼 北极光之吻 2014-08-12  
junfeng2010 写道
“该方法实际上仅在framework启动时就已经调用了,因为sSystem是一个静态的AssetManager对象,该变量在Zygote启动时已经赋值了,以后都不为空,所以该方法形同虚设。”

有个疑惑:每个app 基本都对应一个进程,新进程的sSystem 应该为空吧,这个方法还是有用的吧?


按照你说的新进程为空。
private static void ensureSystemAssets() {
        synchronized (sSync) {
            if (sSystem == null) {
                AssetManager system = new AssetManager(true);
                system.makeStringBlocks(false);
                sSystem = system;
            }
        }
    }

那样会调用new AssetManager(true); 也就是进而调用init() 方法。
1 楼 junfeng2010 2013-08-07  
“该方法实际上仅在framework启动时就已经调用了,因为sSystem是一个静态的AssetManager对象,该变量在Zygote启动时已经赋值了,以后都不为空,所以该方法形同虚设。”

有个疑惑:每个app 基本都对应一个进程,新进程的sSystem 应该为空吧,这个方法还是有用的吧?

相关推荐

    Android开发者学习笔记——View、Canvas、bitmap

    首先,需要获取资源:`Resources res = getResources();` 使用 BitmapDrawable 获取位图可以通过以下两种方式: 1. 使用 BitmapDrawable 构造函数:`BitmapDrawable bmpDraw = new BitmapDrawable(is);` 2. 使用 ...

    安卓Android源码——获取手机屏幕尺寸的代码段.rar

    本资源"安卓Android源码——获取手机屏幕尺寸的代码段.rar"提供了一段用于获取手机屏幕尺寸的源码,下面我们将深入探讨这个主题。 首先,Android系统提供了`DisplayMetrics`类,它是用来描述设备显示特性的一个对象...

    CMDN CLUB#14期:Android系统资源访问机制的探讨

    访问资源时,Android系统采用了资源ID的方式,使得资源在编译时被赋予一个唯一的整数值。开发者在XML布局文件或代码中通过这个资源ID来引用资源,当应用运行时,系统将资源ID转换为实际的资源对象。 应用级别的SDK...

    安卓Android源码——Android游戏源码——忍者快跑.zip

    在本压缩包“安卓Android源码——Android游戏源码——忍者快跑.zip”中,包含的是一个基于Android平台的游戏应用源代码,名为“忍者快跑”。这个游戏源码是学习和研究Android游戏开发的理想资源,它能帮助开发者深入...

    Android资源访问

    运行时,系统通过`Context`的`getResources()`方法获取`Resources`对象,进而加载资源。 9. **实例源码** 实际开发中,我们常会看到如下代码: ```java String appName = getResources().getString(R.string.app...

    Android-Plugin插件设计-获取插件资源

    2. **资源映射**:由于Android系统在运行时通过R类访问资源,因此需要将插件的资源ID映射到主应用的R类中。这可以通过动态生成或修改R类实现,使得主应用能正确调用插件的资源。 3. **资源加载**:在运行时,主应用...

    android动态加载外部资源文件

    4. 创建Resources对象:有了AssetManager后,可以结合DisplayMetrics和Configuration创建一个新的Resources对象,用于访问外部apk的资源。 四、获取图片资源 1. 使用新的Resources对象,通过getIdentifier()方法...

    Android动态加载(下)——加载已安装APK中的类和资源

    有了Resources对象,我们就可以像操作原生资源一样访问目标APK的资源了。 为了更好地管理和控制动态加载的内容,通常会有一个插件管理器来负责加载、卸载和管理插件。插件管理器会维护一个类加载器和资源对象的映射...

    安卓Android源码——iconFile1.rar

    Android系统使用资源ID来引用这些图标,资源ID在编译时生成,并在运行时通过Resources类访问。 2. **资源解析**: 当应用程序启动时,Android系统会通过Resource Manager解析APK中的资源文件,将图标转换为内存中...

    安卓Android源码——节奏大师.rar

    1. **Resources**:在Android开发中,资源文件通常存放于`res`目录下,包括了应用程序的各种非代码资源,如界面布局(XML文件)、图像资源(PNG、JPEG等)、字符串资源(strings.xml)、颜色资源(colors.xml)、...

    安卓Android源码——节奏大师.zip

    《安卓Android源码——节奏大师》是一份专为Android开发者准备的宝贵资源,它揭示了移动游戏开发的内部机制,特别是对于"节奏大师"这款深受玩家喜爱的音乐节奏游戏。通过对这份源码的学习,开发者可以深入理解...

    android贪吃蛇游戏——snake

    这款由你提供的项目,"android贪吃蛇游戏——snake",显然是一款用Java或Kotlin语言实现的Android应用程序,它利用Android SDK中的图形库和事件处理机制来创建一个交互式的贪吃蛇游戏界面。 首先,我们要理解贪吃蛇...

    android studio创建9.patch图片,使用时出现`Error: Duplicate resources`

    android studio创建9.patch图片,使用时出现Error: Duplicate resources 笔者运行环境:MacOs Catalina , android studio 3.6.3 原创文章 5获赞 2访问量 249 关注 私信 展开阅读全文 作者:徐州捕快

    Android Studio中文汉化jar(resources_cn.jar)

    Android Studio中文汉化jar。先关闭Android Studio,然后将resources_cn.jar放到安装目录lib文件夹下,重新打开Android Studio即可

    安卓Android源码——精典源码之换肤.zip

    2. **资源替换**:将解析出的新资源替换到应用的Resources对象中。这通常涉及到反射操作,因为Android SDK并未提供直接修改Resources的方法。 3. **界面更新**:通知所有的View重新绘制,以应用新的皮肤。可以通过...

    android益智游戏——推箱子

    Android提供`Resources`类来访问这些资源,而`Bitmap`类用于加载和处理图像。 7. **用户界面设计**:使用XML布局文件设计游戏启动界面、游戏结束界面、设置界面等。可以利用Android Studio提供的设计工具进行可视化...

    Android游戏源码——忍者快跑.zip

    Android提供了Asset Manager和Resources类来处理这些资源,源码中会有对应的加载和释放代码,确保游戏运行流畅,不占用过多内存。 8. **Android生命周期管理**:游戏的Activity需要遵循Android的生命周期,如暂停、...

    android java反射,通过图片名获取图片

    在给定的场景中,“android java反射,通过图片名获取图片”这个主题涉及到利用反射机制来动态地加载和使用资源图片。下面将详细阐述这一知识点。 首先,我们需要理解Java反射的基本概念。Java反射是Java语言提供的...

    安卓Android源码——XmlResourceParserSample.rar

    本示例“XmlResourceParserSample”着重探讨的是Android中的XML解析器——XmlResourceParser,它是Android系统处理XML资源的核心工具。下面我们将详细剖析这一关键组件及其在实际应用中的运用。 首先,...

Global site tag (gtag.js) - Google Analytics