您的位置:首页 > 其它

资源访问机制学习笔记-Context的获取

2014-04-17 18:46 666 查看
我们要获取资源,Resource对象起着关键作用。那么要获取Resouce对象有两种方式:1、Context方式获取。2、PackageManager方式获取。

1、Context方式获取,

我们在Activity中调用getResource方法就可以获取一个Resource对象,其流程如下:

ContextWrapper.JAVA

public Resources getResources()
{
return mBase.getResources();
}


mBase为Context,Context是一个抽象类,其不能进行实例化,这里我们其实调用的是ComtextImpl。

ContextImpl.java

@Override
public Resources getResources() {
return mResources;
}


该mResources对象时在ContextImpl初始化时赋值的,也就是其init方法。

final void init(LoadedApk packageInfo,
IBinder activityToken, ActivityThread mainThread,
Resources container, String basePackageName) {
mPackageInfo = packageInfo;
mBasePackageName = basePackageName != null ? basePackageName : packageInfo.mPackageName;
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());
}
mMainThread = mainThread;
mContentResolver = new ApplicationContentResolver(this, mainThread);

setActivityToken(activityToken);
}
我们看到其由mPackageInfo.getResource方法获取。

一个应用程中有多个ContextImpl,但是只有一个PackageInfo,也就是说多个ConetxtImpl对象调用同一个PackageInfo对象。也意味着Resource对象只有一个。

public Resources getResources(ActivityThread mainThread) {
if (mResources == null) {
mResources = mainThread.getTopLevelResources(mResDir, this);
}
return mResources;
}
mainThread指ActivityThread对象,一个应用程序只有一个该对象。
ActivityThread
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) 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 metrics = getDisplayMetricsLocked(null, 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;
}
}


ps:

1、mActiveResources对象为ActivityThread对象的成员变量,其内部保存了该应用程序所用到的所有Resource对象。其类型为:HashMap<ResourcesKey, WeakReference<Resources> >。即用ResourceKey映射到Resource类,这些Resource对象使用一个弱引用对象保存,在内存紧张时释放Resource所占用的内存。

2、ResourcesKey key = new ResourcesKey(resDir, compInfo.applicationScale);

ResourceKey对象用resDir和另一个变量构造而成。resDir变量的含义是资源文件所在的路径,实际指的就是APK程序所在的路径。比如:/data/app/com.android.settings.apk,该apk会对应/data/dalvik-cache目录下的data@app@com.android.settings@classes.dex文件。

因此:如果一个应用程序不访问一个程序以外的资源,那么mActivityResource变量中就仅有一个Resource对象。这也从侧面说明,mActivityResource对象内部可以包含多个Resource对象,条件是拥有不同的ResourceKey对象,进而说明必须有不同的resDir目录。这也意味着一个应用程序可以访问另外的apk文件中的资源。

想法:我们可以利用这一点,实现系统主题的切换,或者叫做换肤。

3、AssetManager assets = new AssetManager();创建AssetManager对象。

我们可以通过Resource对象的getAsset方法获得AssetManager对象,进而访问res/asset目录下的资源,但是AssetManager可以访问res目录下的所有资源。 

assets.addAssetPath(resDir),调用AssetManager对象的addAsetPath方法。

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方法。

android_util_AssetManager.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);
}
以上代码首先创建了一个AssetManager,这个c中的AssetManager不是java中的。

调用am的addDefaultAsset方法,该方法的作用就是把Framework层的资源路径添加到这个AssetManager对象的路径中。

最后是通过setIntField方法,将此AssetManager对象保存到java层的AssetManager中的mObject中,这个方式可以最大程度持久化C中对象,java中只要进程不退出,就可以长久的保存一个进程中的对象在内存中。C无此特性。 

AssetManager.cpp

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的root目录,然后再跟kSystemAssets变量代表的目录结合。 

static const char* kSystemAssets = "framework/framework-res.apk";
将这个路径加载到path中,故而能够访问Framework层的资源。

addAssetPath方法逻辑如下:

AssetManager.cpp
bool AssetManager::addAssetPath(const String8& path, void** cookie)
{
AutoMutex _l(mLock);

asset_path ap;

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) {
LOGW("Asset path %s is neither a directory nor file (type=%d).",
path.string(), (int)ap.type);
return false;
}
}

// Skip if we have it already.
for (size_t i=0; i<mAssetPaths.size(); i++) {
if (mAssetPaths[i].path == ap.path) {
if (cookie) {
*cookie = (void*)(i+1);
}
return true;
}
}

LOGV("In %p Asset %s path: %s", this,
ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string());

mAssetPaths.add(ap);

// new paths are always added at the end
if (cookie) {
*cookie = (void*)mAssetPaths.size();
}

// add overlay packages for /system/framework; apps are handled by the
// (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 {
LOGW("failed to add overlay package %s\n", overlayPath.string());
}
}
}

return true;
}

2、PackageManager方式获取

PackageManager获取Resource对象的代码如下:

PackageManager pm = mContext.getPackageManager();
pm.getResourcesForApplication("com.example.theme1");
getPackageManager用于得到一个PackageManager对象,该对象是一个本地对象,对象内部通过Binder机制调用远程PackageManagerService。PackageManager只是远程PackageManagerService的一个代理,控制了PackageManagerService的接口访问。

ContextImpl.java
@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类,真正实现此类的有上面代码所述为ApplicationPackageManager。构造方法中包含了一个Binder远程调用。通过ActivityThread的静态方法getPackageManager获得。

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对象,接着调用geetResourceForApplication方法,该方法的实现在ApplicationPackageManager中。

@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, mContext.mPackageInfo);
if (r != null) {
return r;
}
throw new NameNotFoundException("Unable to open " + app.publicSourceDir);
}


获取Resource对象和1中流程类似,通过getTopLevelResources方法,其参数是首先判断目标资源程序和当前程序是否为同一个Uid,如果是,就是用目标程序的sourceDir,如果不是,则使用目标程序的publicDir。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐