我们要获取资源,Resource对象起着关键作用。那么要获取Resouce对象有两种方式:1、Context方式获取。2、PackageManager方式获取。
1、Context方式获取,
我们在Activity中调用getResource方法就可以获取一个Resource对象,其流程如下:
ContextWrapper.JAVA
public Resources getResources() { return mBase.getResources(); }
ContextImpl.java
@Override public Resources getResources() { return mResources; }
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; } }
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@[email protected]@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); }
static const char* kSystemAssets = "framework/framework-res.apk";将这个路径加载到path中,故而能够访问Framework层的资源。
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; }
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; }
@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); }