文章出处:http://blog.csdn.net/shift_wwx


前言:一般应用开发的时候,都会通过getResources().getXXX()来获取资源信息,例如getDrawable、getDimension、getString等,这里我小结了资源加载的机制以及加载过程的实现。

总结了两篇博文,分别是:

android资源访问机制

android 系统资源的加载和获取


获取Resources一般有两种方式:Context、PackageManager。


一、通过Context获取Resource

1、getResources()

Context类中:

public abstract Resources getResources();
是个抽象函数,具体实现是在ContextImpl.java中:
    @Override    public Resources getResources() {        return mResources;    }
private Resources mResources;
ContextImpl是在ActivityThread类中创建的,关于Context我会在后面做一下小结。

可以看到这个是个Resources对象,即不同的context对应不同的Resources对象。

而这个对象的赋值是在init()中,在创建ContextImpl的时候,会调用init()对内部的变量进行初始化。其中就包括Resources:

    final void init(LoadedApk packageInfo, IBinder activityToken, ActivityThread mainThread,            Resources container, String basePackageName, UserHandle user) {        mPackageInfo = packageInfo;        if (basePackageName != null) {            mBasePackageName = mOpPackageName = basePackageName;        } else {            mBasePackageName = packageInfo.mPackageName;            ApplicationInfo ainfo = packageInfo.getApplicationInfo();            if (ainfo.uid == Process.SYSTEM_UID && ainfo.uid != Process.myUid()) {                // Special case: system components allow themselves to be loaded in to other                // processes.  For purposes of app ops, we must then consider the context as                // belonging to the package of this process, not the system itself, otherwise                // the package+uid verifications in app ops will fail.                mOpPackageName = ActivityThread.currentPackageName();            } else {                mOpPackageName = mBasePackageName;            }        }        mResources = mPackageInfo.getResources(mainThread);        mResourcesManager = ResourcesManager.getInstance();


2、从代码中可以看出mResources = mPackageInfo.getResources(mainThread)

    public Resources getResources(ActivityThread mainThread) {        if (mResources == null) {            mResources = mainThread.getTopLevelResources(mResDir,                    Display.DEFAULT_DISPLAY, null, this);        }        return mResources;    }
mainThread是ActivityThread对象,一个应用程序只有一个ActivityThread对象,
    /**     * Creates the top level resources for the given package.     */    Resources getTopLevelResources(String resDir,            int displayId, Configuration overrideConfiguration,            LoadedApk pkgInfo) {        return mResourcesManager.getTopLevelResources(resDir, displayId, overrideConfiguration,                pkgInfo.getCompatibilityInfo(), null);    }

注意这里的mResourcesManager:

    ActivityThread() {        mResourcesManager = ResourcesManager.getInstance();    }
    public static ResourcesManager getInstance() {        synchronized (ResourcesManager.class) {            if (sResourcesManager == null) {                sResourcesManager = new ResourcesManager();            }            return sResourcesManager;        }    }
可以看出这个对象是个单例,这样就保证了不同的ContextImpl访问的是同一套资源,注意,这里说的同一套资源未必是同一个资源,因为资源可能位于不同的目录,但它一定是我们的应用的资源,或许这样来描述更准确,在设备参数和显示参数不变的情况下,不同的ContextImpl访问到的是同一份资源。设备参数不变是指手机的屏幕和android版本不变,显示参数不变是指手机的分辨率和横竖屏状态。也就是说,尽管Application、Activity、Service都有自己的ContextImpl,并且每个ContextImpl都有自己的mResources成员,但是由于它们的mResources成员都来自于唯一的ResourcesManager实例,所以它们看似不同的mResources其实都指向的是同一块内存(C语言的概念),因此,它们的mResources都是同一个对象(在设备参数和显示参数不变的情况下)。在横竖屏切换的情况下且应用中为横竖屏状态提供了不同的资源,处在横屏状态下的ContextImpl和处在竖屏状态下的ContextImpl访问的资源不是同一个资源对象。

接着:

    /**     * Creates the top level Resources for applications with the given compatibility info.     *     * @param resDir the resource directory.     * @param compatInfo the compability info. Must not be null.     * @param token the application token for determining stack bounds.     */    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;        }    }

以上代码中,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实现主题切换的)

            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;            }
通过code可以知道;如果mActivityResources对象中没有包含所要的Resources对象,那么,就重新建立一个Resources对象
        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);
可以看出构造一个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();    ALOGV("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;            }        }    }
其实这个函数的最终目的是获取sSystem:
/*package*/ static AssetManager sSystem = null;
可以看到这个变量是static,这个变量在zygote启动的时候就被赋值了。那也就是说sSystem是不可能为空的,那 if 就不会进入了,该函数形同虚设。


根据以上AssetManager构造函数分析可知:

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中,以便以后继续使用。

        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;        }
访问Resources内部的整个流程如下图。


二、通过PackageManager获取Resources

该方法主要用于访问其他程序中的资源,其典型应用就是切换主题,但这种切换仅限于一个程序内部,而不是整个系统。比如市面上aHome桌面,其工作原理如下图:


PackageManager获得资源的代码如下:

PackageManager pm = mContext.getPackageManager();pm.getResourcesForApplication("com.android.hiii.client");
其中getPackageManager()用于返回一个PackageManager对象,该对象只是一个本地对象,但是对象内的方法一般都是调用远程PackageManagerService。

代码如下:

    @Override    public PackageManager getPackageManager() {        if (mPackageManager != null) {            return mPackageManager;        }        IPackageManager pm = ActivityThread.getPackageManager();        if (pm != null) {            // Doesn't matter if we make more than one instance.            return (mPackageManager = new ApplicationPackageManager(this, pm));        }        return null;    }
PackageManager类是个abstract类:

public abstract class PackageManager {
通过之前的code可以看出真正实现这个类的是ApplicationPackageManager类。

通过code可以看出

return (mPackageManager = new ApplicationPackageManager(this, pm));
    ApplicationPackageManager(ContextImpl context,                              IPackageManager pm) {        mContext = context;        mPM = pm;    }
需要记住当前的Context和IPackageManager,而IPackageManager是通过ActivityThread获取的,

    public static IPackageManager getPackageManager() {        if (sPackageManager != null) {            //Slog.v("PackageManager", "returning cur default = " + sPackageManager);            return sPackageManager;        }        IBinder b = ServiceManager.getService("package");        //Slog.v("PackageManager", "default service binder = " + b);        sPackageManager = IPackageManager.Stub.asInterface(b);        //Slog.v("PackageManager", "default service = " + sPackageManager);        return sPackageManager;    }
通过这里就可以知道了所谓的PackageManager完全就是PackageManagerService在本地的代理,本地应用通过binder来调用PackageManagerService中的接口。

pm获取之后,通过接口getResourcesForApplication获得Resources对象:

    @Override public Resources getResourcesForApplication(        String appPackageName) throws NameNotFoundException {        return getResourcesForApplication(            getApplicationInfo(appPackageName, 0));    }
接着:

    @Override public Resources getResourcesForApplication(        ApplicationInfo app) throws NameNotFoundException {        if (app.packageName.equals("system")) {            return mContext.mMainThread.getSystemContext().getResources();        }        Resources r = mContext.mMainThread.getTopLevelResources(                app.uid == Process.myUid() ? app.sourceDir : app.publicSourceDir,                        Display.DEFAULT_DISPLAY, null, mContext.mPackageInfo);        if (r != null) {            return r;        }        throw new NameNotFoundException("Unable to open " + app.publicSourceDir);    }
通过code可以看出获取Resources可以有两种方式:

if (app.packageName.equals("system")) {    return mContext.mMainThread.getSystemContext().getResources();}
Resources r = mContext.mMainThread.getTopLevelResources(        app.uid == Process.myUid() ? app.sourceDir : app.publicSourceDir,                 Display.DEFAULT_DISPLAY, null, mContext.mPackageInfo);if (r != null) {    return r;}
其中第一种方式的判断条件我不明白,什么时候等于system,需要研究 一把。

第二种,是当前应用context 调用getTopLevelResources,含义是如果目标资源程序和当前成的uid是一样的话,就用目标程序的sourceDir作为路径,反之则用目标程序的publicSourceDir目录,该目录可以在目标程序的AndroidManifest.xml中设定。

到了getTopLevelResources就跟之前context中说的一样了,会在mActiveResources中添加一个新创建的Resources对象,这个Resources就是对应目标程序的包名。到这里会发现不但可以获取framework-res.apk中的资源、程序本身的资源,其他程序的资源也是可以获取的。


资源加载和获取请看另一篇博文:《android 系统资源的加载和获取



更多相关文章

  1. 解决android使用google map时显示方格的问题
  2. Drawable资源——Transition Drawable
  3. Java/Android引用类型及其使用全面分析
  4. Android(安卓)网络连接 打开 Url下载 信息
  5. Context都没弄明白,还怎么做Android开发?[转]
  6. Android(安卓)获取 H5中的按钮,点击
  7. 【Android】ContentValues的用法
  8. CharSequence的getText()与String的getString()『Android系列七
  9. android drawable 应用

随机推荐

  1. Android中的消息机制
  2. android之sqliteDatabase,sqliteOpenHelp
  3. Google Android操作系统内核编译图文教程
  4. 那些你不知道的Android小事儿
  5. Android读写XML(中)——SAX
  6. unable to access android sdk add-on li
  7. android 智能指针
  8. android:layout_alignleft layout_tolefto
  9. TV - 新的战场
  10. android之调用webservice 实现图片上传