您的位置:首页 > 移动开发 > Android开发

Android资源访问机制—获取Resources对象

2015-08-26 17:19 651 查看
前人指路http://willsunforjava.iteye.com/blog/1663355

modify base on android-4.4.4_r2.0.1

我们知道在开发中,需要应用程序资源,如应用工程中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变量,而对该变量的赋值在私有的构造方法中。

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方法进行赋值。代码如下。

Java代码


public Resources getResources(ActivityThread mainThread) {

if (mResources == null) {

mResources = mainThread.getTopLevelResources(mResDir, this);

}

return mResources;

}

从代码中可以看到,最终mResources的赋值是由AcitivtyThread的getTopLevelResources方法返回。代码如下。

Java代码


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对象

Java代码


r = new Resources(assets, metrics, getConfiguration(), compInfo);

可以看出构造一个Resources需要一个AssetManager对象,一个DisplayMetrics对象,一个Configuration对象,一个CompatibilityInfo对象,后三者传入的对象都与设备或者Android平台相关的参数,因为资源的使用与这些信息总是相关。还有一个AssetManager对象,其实它并不是访问项目中res/assets下的资源,而是访问res下所有的资源。以上代码中的addAssetPath(resDir)非常关键,它为所创建的AssetManager对象添加一个资源路径。

AssetManager类的构造函数如下:

Java代码


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实现。

Cpp代码


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代码如下:

Java代码


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路径进行组合,该变量的定义如下:

Cpp代码


static const char* kSystemAssets = "framework/framework-res.apk";

所以最终获得的路径文件名称为/system/framework/framework-res.apk,这正式framework对应的资源文件。

分析完了AssetManager的init方法,再来看一下ensureSystemAssets方法。

Java代码


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对象就可以访问应用资源和系统资源的原因。如

Java代码


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对象

文件路径/frameworks/base/+/android-4.4.4_r2.0.1/core/java/android/content/pm/PackageManager.java

packageManager为抽象类,,跟Resource相关的方法有:

public abstract Resources getResourcesForActivity(ComponentName activityName)
throws NameNotFoundException;
public abstract Resources getResourcesForApplication(ApplicationInfo app)
public abstract Resources getResourcesForApplication(String appPackageName)
throws NameNotFoundException;

/** @hide */
public abstract Resources getResourcesForApplicationAsUser(String appPackageName, int userId)
throws NameNotFoundException;
throws NameNotFoundException;


具体的实现类为ApplicationPackageManager.java

@Override public Resources getResourcesForActivity(
ComponentName activityName) throws NameNotFoundException {
return getResourcesForApplication(
getActivityInfo(activityName, 0).applicationInfo);
}
@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);
}
@Override public Resources getResourcesForApplication(
String appPackageName) throws NameNotFoundException {
return getResourcesForApplication(
getApplicationInfo(appPackageName, 0));
}
/** @hide */
@Override
public Resources getResourcesForApplicationAsUser(String appPackageName, int userId)
throws NameNotFoundException {
if (userId < 0) {
throw new IllegalArgumentException(
"Call does not support special user #" + userId);
}
if ("system".equals(appPackageName)) {
return mContext.mMainThread.getSystemContext().getResources();
}
try {
ApplicationInfo ai = mPM.getApplicationInfo(appPackageName, 0, userId);
if (ai != null) {
return getResourcesForApplication(ai);
}
} catch (RemoteException e) {
throw new RuntimeException("Package manager has died", e);
}
throw new NameNotFoundException("Package " + appPackageName + " doesn't exist");
}


1.包名为system的情况,mContext.mMainThread.getSystemContext().getResources()

ActivityThread.getSystemContext

public ContextImpl getSystemContext() {
synchronized (this) {
if (mSystemContext == null) {
mSystemContext = ContextImpl.createSystemContext(this);
}
return mSystemContext;
}
}


ContextImpl.createSystemContext

static ContextImpl createSystemContext(ActivityThread mainThread) {
LoadedApk packageInfo = new LoadedApk(mainThread);
ContextImpl context = new ContextImpl(null, mainThread,
packageInfo, null, null, false, null, null);
context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(),
context.mResourcesManager.getDisplayMetricsLocked(Display.DEFAULT_DISPLAY));
return context;
}
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
return new ContextImpl(null, mainThread,
packageInfo, null, null, false, null, null);
}
static ContextImpl createActivityContext(ActivityThread mainThread,
LoadedApk packageInfo, IBinder activityToken) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
if (activityToken == null) throw new IllegalArgumentException("activityInfo");
return new ContextImpl(null, mainThread,
packageInfo, activityToken, null, false, null, null);
}
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;
}
}
}


最终的mResource是通过packageinfo.getResources和mResoucesManager.getTopLevelResources得到

packageInfo为core/java/android/app/LoadedApk.java

public Resources getResources(ActivityThread mainThread) {
if (mResources == null) {
mResources = mainThread.getTopLevelResources(mResDir,
Display.DEFAULT_DISPLAY, null, this);
}
return mResources;
}


core/java/android/app/ResourcesManager.java

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


这里我们可以看到创建的Resouces保存到ResourcesManager中的mActiveResources中,相同的包名存对应的key,存在对应的Resources

final ArrayMap<ResourcesKey, WeakReference<Resources> > mActiveResources
= new ArrayMap<ResourcesKey, WeakReference<Resources> >();


2.通用情况,mContext.mMainThread.getTopLevelResources

<pre name="code" class="java">    /**
* 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);
}




结果同样通过ResourcesManager.getTopLevelResources来获取Resources

结论:

1.获取Resources,最终都是通过ResourcesManager获取

2.ResourcesManager中使用

ArrayMap<ResourcesKey, WeakReference<Resources> > mActiveResources保存Resources的键值对

在内存不足是,弱引用会被回收



PS:

在Resources被回收后,会重新创建Resources对象

->Activity.getResources

->ContextThemeWrapper.getResources

->ContextWrapper.createConfigurationContext

->ContextImpl.createConfigurationContext

ContextThemeWrapper.getResources

public Resources getResources() {
if (mResources != null) {
return mResources;
}
if (mOverrideConfiguration == null) {
mResources = super.getResources();
return mResources;
} else {
Context resc = createConfigurationContext(mOverrideConfiguration);
mResources = resc.getResources();
return mResources;
}
}


ContextWrapper.createConfigurationContext

public Context createConfigurationContext(Configuration overrideConfiguration) {
return mBase.createConfigurationContext(overrideConfiguration);
}


ContextImpl.createConfigurationContext

public Context createConfigurationContext(Configuration overrideConfiguration) {
if (overrideConfiguration == null) {
throw new IllegalArgumentException("overrideConfiguration must not be null");
}
return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
mUser, mRestricted, mDisplay, overrideConfiguration);
}


最后调用ContextImpl的构造方法创建Context
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: