Android资源访问机制—获取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变量,而对该变量的赋值在私有的构造方法中。

core/java/android/app/ContextImpl.java

private ContextImpl(ContextImpl container, ActivityThread mainThread,
            LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,
            Display display, Configuration overrideConfiguration) {
        mOuterContext = this;
        mMainThread = mainThread;
        mActivityToken = activityToken;
        mRestricted = restricted;
        if (user == null) {
            user = Process.myUserHandle();
        }
        mUser = user;
        mPackageInfo = packageInfo;
        mContentResolver = new ApplicationContentResolver(this, mainThread, user);
        mResourcesManager = ResourcesManager.getInstance();
        mDisplay = display;
        mOverrideConfiguration = overrideConfiguration;
        final int displayId = getDisplayId();
        CompatibilityInfo compatInfo = null;
        if (container != null) {
            compatInfo = container.getDisplayAdjustments(displayId).getCompatibilityInfo();
        }
        if (compatInfo == null && displayId == Display.DEFAULT_DISPLAY) {
            compatInfo = packageInfo.getCompatibilityInfo();
        }
        mDisplayAdjustments.setCompatibilityInfo(compatInfo);
        mDisplayAdjustments.setActivityToken(activityToken);
        Resources resources = packageInfo.getResources(mainThread);
        if (resources != null) {
            if (activityToken != null
                    || displayId != Display.DEFAULT_DISPLAY
                    || overrideConfiguration != null
                    || (compatInfo != null && compatInfo.applicationScale
                            != resources.getCompatibilityInfo().applicationScale)) {
                resources = mResourcesManager.getTopLevelResources(
                        packageInfo.getResDir(), displayId,
                        overrideConfiguration, compatInfo, activityToken);
            }
        }
        mResources = resources;
        if (container != null) {
            mBasePackageName = container.mBasePackageName;
            mOpPackageName = container.mOpPackageName;
        } 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又是调用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对象
1. 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内部的整个流程如下图。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wFbyzvbF-1677139166724)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/7c20ac72-d5b7-4f7a-bf43-c0bdc7a53b56/Untitled.png)]

二、通过PackageManager获取Resources

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CC4cizL0-1677139166730)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/ee4ca3aa-12aa-4483-8911-b7cfafac2206/Untitled.png)]

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](notion://slog.v/)("PackageManager", "returning cur default = " + sPackageManager);
return sPackageManager;
}
IBinder b = ServiceManager.getService("package");
[//Slog.v](notion://slog.v/)("PackageManager", "default service binder = " + b);
sPackageManager = IPackageManager.Stub.asInterface(b);
[//Slog.v](notion://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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值