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


我们要获取资源,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@[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);
}

该函数首先获取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。


你可能感兴趣的:(resource,获取Resource对象)