Android应用程序在运行的过程中,是通过一个称为AssetManager的资源管理器来读取打包在APK文件里面的资源文件的。在本文中,我们就将详细分析Android应用程序资源管理器的创建以及初始化过程。

应用程序的每一个Activity组件都关联有一个ContextImpl对象,这个ContextImpl对象就是用来描述Activity组件的运行上下文环境的。Activity组件是从Context类继承下来的,而ContextImpl同样是从Context类继承下来的。我们在Activity组件调用的大部分成员函数都是转发给与它所关联的一个ContextImpl对象的对应的成员函数来处理的,其中就包括用来访问应用程序资源的两个成员函数getResources和getAssets。

        ContextImpl类的成员函数getResources返回的是一个Resources对象,有了这个Resources对象之后,我们就可以通过资源ID来访问那些被编译过的应用程序资源了。ContextImpl类的成员函数getAssets返回的是一个AssetManager对象,有了这个AssetManager对象之后,我们就可以通过文件名来访问那些被编译过或者没有被编译过的应用程序资源文件了。事实上,Resources类也是通过AssetManager类来访问那些被编译过的应用程序资源文件的,不过在访问之前,它会先根据资源ID查找得到对应的资源文件名。

        我们知道,在Android系统中,一个进程是可以同时加载多个应用程序的,也就是可以同时加载多个APK文件。每一个APK文件在进程中都对应有一个全局的Resourses对象以及一个全局的AssetManager对象。其中,这个全局的Resourses对象保存在一个对应的ContextImpl对象的成员变量mResources中,而这个全局的AssetManager对象保存在这个全局的Resourses对象的成员变量mAssets中。

        Resources类有一个成员函数getAssets,通过它就可以获得保存在Resources类的成员变量mAssets中的AssetManager,例如,ContextImpl类的成员函数getAssets就是通过调用其成员变量mResources所指向的一个Resources对象的成员函数getAssets来获得一个可以用来访问应用程序的非编译资源文件的AssetManager。

        我们知道,Android应用程序除了要访问自己的资源之外,还需要访问系统的资源。系统的资源打包在/system/framework/framework-res.apk文件中,它在应用程序进程中是通过一个单独的Resources对象和一个单独的AssetManager对象来管理的。这个单独的Resources对象就保存在Resources类的静态成员变量mSystem中,我们可以通过Resources类的静态成员函数getSystem就可以获得这个Resources对象,而这个单独的AssetManager对象就保存在AssetManager类的静态成员变量sSystem中,我们可以通过AssetManager类的静态成员函数getSystem同样可以获得这个AssetManager对象。

        AssetManager类除了在Java层有一个实现之外,在 C++层也有一个对应的实现,而Java层的AssetManager类的功能就是通过C++层的AssetManager类来实现的。Java层的每一个AssetManager对象都有一个类型为int的成员变量mObject,它保存的便是在C++层对应的AssetManager对象的地址,因此,通过这个成员变量就可以将Java层的AssetManager对象与C++层的AssetManager对象关联起来。

        C++层的AssetManager类有三个重要的成员变量mAssetPaths、mResources和mConfig。其中,mAssetPaths保存的是资源存放目录,mResources指向的是一个资源索引表,而mConfig保存的是设备的本地配置信息,例如屏幕密度和大小、国家地区和语言等等配置信息。有了这三个成员变量之后,C++层的AssetManager类就可以访问应用程序的资源了。


一、ContextImpl创建

之前我们分析过Activity是在ActivityThread中的performLaunchActivity函数中创建的,我们来看看Activity的继承关系

public class Activity extends ContextThemeWrapper

而ContextThemeWrapper又是继承ContextWrapper

public class ContextThemeWrapper extends ContextWrapper
我们再来看ContextWrapper的getAssets和getResources函数都是调用mBase的相关函数。

    @Override    public AssetManager getAssets() {        return mBase.getAssets();    }    @Override    public Resources getResources()    {        return mBase.getResources();    }

mBase是Context类型的,这个对象又在何时创建的。

    Context mBase;


mBase是在Activity的attach的时候创建的,我们来看ActivityThread中的performLaunchActivity函数中下面这段代码,先调用了createBaseContextForActivity函数创建了一个Context对象,然后将这个对象作为参数传入了attach函数。

            if (activity != null) {                Context appContext = createBaseContextForActivity(r, activity);                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());                Configuration config = new Configuration(mCompatConfiguration);                if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "                        + r.activityInfo.name + " with config " + config);                activity.attach(appContext, this, getInstrumentation(), r.token,                        r.ident, app, r.intent, r.activityInfo, title, r.parent,                        r.embeddedID, r.lastNonConfigurationInstances, config,                        r.referrer, r.voiceInteractor);

我们来看下createBaseContextForActivity函数,调用了ContextImpl.createActivityContext函数来创建了一个ContextImpl对象

    private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) {        int displayId = Display.DEFAULT_DISPLAY;        try {            displayId = ActivityManagerNative.getDefault().getActivityDisplayId(r.token);        } catch (RemoteException e) {        }        ContextImpl appContext = ContextImpl.createActivityContext(                this, r.packageInfo, displayId, r.overrideConfig);        appContext.setOuterContext(activity);        Context baseContext = appContext;        final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();        // For debugging purposes, if the activity's package name contains the value of        // the "debug.use-second-display" system property as a substring, then show        // its content on a secondary display if there is one.        String pkgName = SystemProperties.get("debug.second-display.pkg");//只是调试相关pkg第二个显示        if (pkgName != null && !pkgName.isEmpty()                && r.packageInfo.mPackageName.contains(pkgName)) {            for (int id : dm.getDisplayIds()) {                if (id != Display.DEFAULT_DISPLAY) {                    Display display =                            dm.getCompatibleDisplay(id, appContext.getDisplayAdjustments(id));                    baseContext = appContext.createDisplayContext(display);                    break;                }            }        }        return baseContext;    }

ContextImpl 的静态函数createActivityContext,就是创建一个ContextImpl对象。

    static ContextImpl createActivityContext(ActivityThread mainThread,            LoadedApk packageInfo, int displayId, Configuration overrideConfiguration) {        if (packageInfo == null) throw new IllegalArgumentException("packageInfo");        return new ContextImpl(null, mainThread, packageInfo, null, null, false,                null, overrideConfiguration, displayId);    }

我们再来看Activity的attach方法,第一行代码就是调用了attachBaseContext函数

    final void attach(Context context, ActivityThread aThread,            Instrumentation instr, IBinder token, int ident,            Application application, Intent intent, ActivityInfo info,            CharSequence title, Activity parent, String id,            NonConfigurationInstances lastNonConfigurationInstances,            Configuration config, String referrer, IVoiceInteractor voiceInteractor) {        attachBaseContext(context);

最终调用了ContextWrapper的attachBaseContext函数,就是给mBase赋值。

    protected void attachBaseContext(Context base) {        if (mBase != null) {            throw new IllegalStateException("Base context already set");        }        mBase = base;    }

这样每个Activity的ContextImpl就创建成功了。


二、Resources对象创建

我们来看下ContextImpl的构造函数如下一段代码,正常情况下是调用LoadedApk的getResources方法来得到ContextImpl的mResources对象。

        Resources resources = packageInfo.getResources(mainThread);        if (resources != null) {            if (displayId != Display.DEFAULT_DISPLAY                    || overrideConfiguration != null                    || (compatInfo != null && compatInfo.applicationScale                            != resources.getCompatibilityInfo().applicationScale)) {                resources = mResourcesManager.getTopLevelResources(packageInfo.getResDir(),                        packageInfo.getSplitResDirs(), packageInfo.getOverlayDirs(),                        packageInfo.getApplicationInfo().sharedLibraryFiles, displayId,                        overrideConfiguration, compatInfo);            }        }        mResources = resources;

我们来看LoadedApk的getResources方法,当mResources为null,就调用ActivityThread的getTopLevelResources获取。

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

在调用ActivityThread类的成员函数getTopLevelResources来获得一个Resources对象的时候,需要指定要获取的Resources对象所对应的Apk文件路径,这个Apk文件路径就保存在LoadedApk类的成员变量mResDir中。例如,假设我们要获取的Resources对象是用来访问系统自带的音乐播放器的资源的,那么对应的Apk文件路径就为/system/app/Music.apk。


2.1 LoadedApk创建

那我们再来看看mResDir是如何得到的
这里我们从ActivityThread开始分析,下面是从AMS调用来的,然后在scheduleLaunchActivity函数中,创建了一个ActivityClientRecord 对象

        @Override        public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,                ActivityInfo info, Configuration curConfig, Configuration overrideConfig,                CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,                int procState, Bundle state, PersistableBundle persistentState,                List pendingResults, List pendingNewIntents,                boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {            updateProcessState(procState, false);            ActivityClientRecord r = new ActivityClientRecord();            r.token = token;            r.ident = ident;            r.intent = intent;            r.referrer = referrer;            r.voiceInteractor = voiceInteractor;            r.activityInfo = info;            r.compatInfo = compatInfo;            r.state = state;            r.persistentState = persistentState;            r.pendingResults = pendingResults;            r.pendingIntents = pendingNewIntents;            r.startsNotResumed = notResumed;            r.isForward = isForward;            r.profilerInfo = profilerInfo;            r.overrideConfig = overrideConfig;            updatePendingConfiguration(curConfig);            sendMessage(H.LAUNCH_ACTIVITY, r);        }

我们再看消息处理,在调用handleLaunchActivity之前先调用了getPackageInfoNoCheck函数

                case LAUNCH_ACTIVITY: {                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;                    r.packageInfo = getPackageInfoNoCheck(                            r.activityInfo.applicationInfo, r.compatInfo);                    handleLaunchActivity(r, null);                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

先调用了getPackageInfoNoCheck函数

    public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,            CompatibilityInfo compatInfo) {        return getPackageInfo(ai, compatInfo, null, false, true, false);    }

然后调用了getPackageInfo函数,在这个函数中就新建了LoadedApk对象。

    private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,            ClassLoader baseLoader, boolean securityViolation, boolean includeCode,            boolean registerPackage) {        final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));        synchronized (mResourcesManager) {            WeakReference ref;            if (differentUser) {                // Caching not supported across users                ref = null;            } else if (includeCode) {                ref = mPackages.get(aInfo.packageName);            } else {                ref = mResourcePackages.get(aInfo.packageName);            }            LoadedApk packageInfo = ref != null ? ref.get() : null;            if (packageInfo == null || (packageInfo.mResources != null                    && !packageInfo.mResources.getAssets().isUpToDate())) {                if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package "                        : "Loading resource-only package ") + aInfo.packageName                        + " (in " + (mBoundApplication != null                                ? mBoundApplication.processName : null)                        + ")");                packageInfo =                    new LoadedApk(this, aInfo, compatInfo, baseLoader,                            securityViolation, includeCode &&                            (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);                if (mSystemThread && "android".equals(aInfo.packageName)) {                    packageInfo.installSystemApplicationInfo(aInfo,                            getSystemContext().mPackageInfo.getClassLoader());                }                if (differentUser) {                    // Caching not supported across users                } else if (includeCode) {                    mPackages.put(aInfo.packageName,                            new WeakReference(packageInfo));                } else {                    mResourcePackages.put(aInfo.packageName,                            new WeakReference(packageInfo));                }            }            return packageInfo;        }    }

LoadedApk对象的构造函数如下,其中mResDir也是从ApplicationInfo 获取的。

    public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo,            CompatibilityInfo compatInfo, ClassLoader baseLoader,            boolean securityViolation, boolean includeCode, boolean registerPackage) {        final int myUid = Process.myUid();        aInfo = adjustNativeLibraryPaths(aInfo);        mActivityThread = activityThread;        mApplicationInfo = aInfo;        mPackageName = aInfo.packageName;        mAppDir = aInfo.sourceDir;        mResDir = aInfo.uid == myUid ? aInfo.sourceDir : aInfo.publicSourceDir;        mSplitAppDirs = aInfo.splitSourceDirs;        mSplitResDirs = aInfo.uid == myUid ? aInfo.splitSourceDirs : aInfo.splitPublicSourceDirs;        mOverlayDirs = aInfo.resourceDirs;        mSharedLibraries = aInfo.sharedLibraryFiles;        mDataDir = aInfo.dataDir;        mDataDirFile = mDataDir != null ? new File(mDataDir) : null;        mLibDir = aInfo.nativeLibraryDir;        mBaseClassLoader = baseLoader;        mSecurityViolation = securityViolation;        mIncludeCode = includeCode;        mRegisterPackage = registerPackage;        mDisplayAdjustments.setCompatibilityInfo(compatInfo);    }


2.2 ResourcesManager的getTopLevelResources函数

继续分析Resources的创建,在ActivityThread的getTopLevelResources函数中,最后调用了ResourcesManager.getTopLevelResources函数,而这个ResourcesManager是在ActivityThread的构造函数中创建,是调用的单例函数。

    Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs,            String[] libDirs, int displayId, Configuration overrideConfiguration,            LoadedApk pkgInfo) {        return mResourcesManager.getTopLevelResources(resDir, splitResDirs, overlayDirs, libDirs,                displayId, overrideConfiguration, pkgInfo.getCompatibilityInfo());    }

我们再来看下ResourcesManager.getTopLevelResources函数

    Resources getTopLevelResources(String resDir, String[] splitResDirs,            String[] overlayDirs, String[] libDirs, int displayId,            Configuration overrideConfiguration, CompatibilityInfo compatInfo) {        final float scale = compatInfo.applicationScale;        Configuration overrideConfigCopy = (overrideConfiguration != null)                ? new Configuration(overrideConfiguration) : null;        ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfigCopy, scale);        Resources r;        synchronized (this) {            // Resources is app scale dependent.            if (DEBUG) Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale);            WeakReference wr = mActiveResources.get(key);            r = wr != null ? wr.get() : null;            //if (r != null) Log.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());            if (r != null && r.getAssets().isUpToDate()) {                if (DEBUG) Slog.w(TAG, "Returning cached resources " + r + " " + resDir                        + ": appScale=" + r.getCompatibilityInfo().applicationScale                        + " key=" + key + " overrideConfig=" + overrideConfiguration);                return r;            }        }        //if (r != null) {        //    Log.w(TAG, "Throwing away out-of-date resources!!!! "        //            + r + " " + resDir);        //}        AssetManager assets = new AssetManager();        // resDir can be null if the 'android' package is creating a new Resources object.        // This is fine, since each AssetManager automatically loads the 'android' package        // already.        if (resDir != null) {            if (assets.addAssetPath(resDir) == 0) {                return null;            }        }        if (splitResDirs != null) {            for (String splitResDir : splitResDirs) {                if (assets.addAssetPath(splitResDir) == 0) {                    return null;                }            }        }        if (overlayDirs != null) {            for (String idmapPath : overlayDirs) {                assets.addOverlayPath(idmapPath);            }        }        if (libDirs != null) {            for (String libDir : libDirs) {                if (libDir.endsWith(".apk")) {                    // Avoid opening files we know do not have resources,                    // like code-only .jar files.                    if (assets.addAssetPath(libDir) == 0) {                        Log.w(TAG, "Asset path '" + libDir +                                "' does not exist or contains no resources.");                    }                }            }        }        //Log.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);        DisplayMetrics dm = getDisplayMetricsLocked(displayId);        Configuration config;        final 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);                if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration);            }        } else {            config = getConfiguration();        }        r = new Resources(assets, dm, config, compatInfo);        if (DEBUG) Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "                + r.getConfiguration() + " appScale=" + r.getCompatibilityInfo().applicationScale);        synchronized (this) {            WeakReference 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<>(r));            if (DEBUG) Slog.v(TAG, "mActiveResources.size()=" + mActiveResources.size());            return r;        }    }

ResourcesManager类的成员变量mActiveResources指向的是一个HashMap。这个HashMap用来维护在当前应用程序进程中加载的每一个Apk文件及其对应的Resources对象的对应关系。也就是说,给定一个Apk文件路径,ActivityThread类的成员函数getTopLevelResources可以在成员变量mActiveResources中检查是否存在一个对应的Resources对象。如果不存在,那么就会新建一个,并且保存在ActivityThread类的成员变量mActiveResources中。

        参数resDir即为要获取其对应的Resources对象的Apk文件路径,ActivityThread类的成员函数getTopLevelResources首先根据它来创建一个ResourcesKey对象,然后再以这个ResourcesKey对象在ActivityThread类的成员变量mActiveResources中检查是否存在一个Resources对象。如果存在,并且这个Resources对象里面包含的资源文件没有过时,即调用这个Resources对象的成员函数getAssets所获得一个AssetManager对象的成员函数isUpToDate的返回值等于true,那么ActivityThread类的成员函数getTopLevelResources就可以将该Resources对象返回给调用者了。

        如果不存在与参数resDir对应的Resources对象,或者存在这个Resources对象,但是存在的这个Resources对象是过时的,那么ActivityThread类的成员函数getTopLevelResources就会新创建一个AssetManager对象,并且调用这个新创建的AssetManager对象的成员函数addAssetPath来将参数resDir所描述的Apk文件路径作为它的资源目录。

        创建了一个新的AssetManager对象,ActivityThread类的成员函数getTopLevelResources还需要这个AssetManager对象来创建一个新的Resources对象。这个新创建的Resources对象需要以前面所创建的ResourcesKey对象为键值缓存在ActivityThread类的成员变量mActiveResources所描述的一个HashMap中,以便以后可以获取回来使用。不过,这个新创建的Resources对象在缓存到ActivityThread类的成员变量mActiveResources所描述的一个HashMap去之前,需要再次检查该HashMap是否已经存在一个对应的Resources对象了,这是因为当前线程在创建新的AssetManager对象和Resources对象的过程中,可能有其它线程抢先一步创建了与参数resDir对应的Resources对象,并且将该Resources对象保存到该HashMap中去了。

        如果没有其它线程抢先创建一个与参数resDir对应的Resources对象,或者其它线程抢先创建出来的Resources对象是过时的,那么ActivityThread类的成员函数getTopLevelResources就会将前面创建的Resources对象缓存到成员变量mActiveResources所描述的一个HashMap中去,并且将前面创建的Resources对象返回给调用者,否则扩知,就会将其它线程抢先创建的Resources对象返回给调用者。

        接下来,我们首先分析AssetManager类的构造函数和成员函数addAssetPath的实现,接着再分析Resources类的构造函数的实现,以便可以了解用来访问应用程序资源的AssetManager对象和Resources对象的创建以及初始化过程。


三、AssetManager

上面在ResourcesManager.getTopLevelResources函数中,会新建AssetManager对象。


3.1 AssetManager的构造函数

构造函数中主要是调用了init函数,是一个native函数,然后调用了ensureSystemAssets函数

    public AssetManager() {        synchronized (this) {            init(false);            if (localLOGV) Log.v(TAG, "New asset manager: " + this);            ensureSystemAssets();        }    }

ensureSystemAssets函数ensureSystemAssets来检查当前进程是否已经创建了一个用来访问系统资源的AssetManager对象。

如果用来访问系统资源的AssetManager对象还没有创建的话,那么AssetManager类的成员函数ensureSystemAssets就会创建并且初始化它,并且将它保存在AssetManager类的静态成员变量sSystem中。注意,创建用来访问系统资源和应用程序资源的AssetManager对象的过程是一样的,区别只在于它们所要访问的Apk文件不一样。

    private static void ensureSystemAssets() {        synchronized (sSync) {            if (sSystem == null) {                AssetManager system = new AssetManager(true);                system.makeStringBlocks(null);                sSystem = system;            }        }    }


3.2 init native函数

init的native函数定义在文件frameworks/base/core/jni/android_util_AssetManager.cpp中。

static void android_content_AssetManager_init(JNIEnv* env, jobject clazz, jboolean isSystem){    if (isSystem) {        verifySystemIdmaps();    }    AssetManager* am = new AssetManager();    if (am == NULL) {        jniThrowException(env, "java/lang/OutOfMemoryError", "");        return;    }    am->addDefaultAssets();    ALOGV("Created AssetManager %p for Java object %p\n", am, clazz);    env->SetLongField(clazz, gAssetManagerOffsets.mObject, reinterpret_cast(am));}

函数android_content_AssetManager_init首先创建一个C++层的AssetManager对象,接着调用这个C++层的AssetManager对象的成员函数addDefaultAssets来添加默认的资源路径,最后将这个这个C++层的AssetManager对象的地址保存在参数clazz所描述的一个Java层的AssetManager对象的成员变量mObject中。


3.3 c层AssetManager.addDefaultAssets

static const char* kSystemAssets = "framework/framework-res.apk";......bool AssetManager::addDefaultAssets(){    const char* root = getenv("ANDROID_ROOT");    ......    String8 path(root);    path.appendPath(kSystemAssets);    return addAssetPath(path, NULL);}

       AssetManager类的成员函数addDefaultAssets首先通过环境变量ANDROID_ROOT来获得Android的系统路径,接着再将全局变量kSystemAssets所指向的字符串“framework/framework-res.apk”附加到这个系统路径的后面去,这样就可以得到系统资源文件framework-res.apk的绝对路径了。一般来说,环境变量ANDROID_ROOT所设置的Android系统路径就是“/system”,因此,最终得到的系统资源文件的绝对路径就为“/system/framework/framework-res.apk”。

       得到了系统资源文件framework-res.apk的绝对路径之后,就调用AssetManager类的成员函数addAssetPath来将它添加到当前正在初始化的AssetManager对象中去。


3.4 c层AssetManager::addAssetPath

我们再来看下AssetManager::addAssetPath函数

bool AssetManager::addAssetPath(const String8& path, int32_t* 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) {            ALOGW("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(i+1);            }            return true;        }    }    ALOGV("In %p Asset %s path: %s", this,         ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string());    // Check that the path has an AndroidManifest.xml    Asset* manifestAsset = const_cast(this)->openNonAssetInPathLocked(            kAndroidManifest, Asset::ACCESS_BUFFER, ap);    if (manifestAsset == NULL) {        // This asset path does not contain any resources.        delete manifestAsset;        return false;    }    delete manifestAsset;    mAssetPaths.add(ap);    // new paths are always added at the end    if (cookie) {        *cookie = static_cast(mAssetPaths.size());    }#ifdef HAVE_ANDROID_OS    // Load overlays, if any    asset_path oap;    for (size_t idx = 0; mZipSet.getOverlay(ap.path, idx, &oap); idx++) {        mAssetPaths.add(oap);    }#endif    if (mResources != NULL) {        appendPathToResTable(ap);    }    return true;}

如果全局变量kAppZipName的值不等于NULL的话,那么它的值一般就是被设置为“classes.jar”,这时候就表示应用程序的资源文件是保存在参数path所描述的一个目录下的一个classes.jar文件中。全局变量kAppZipName的值一般被设置为NULL,并且参数path指向的是一个Apk文件,因此,接下来我们只考虑应用程序资源不是保存在一个classes.jar文件的情况。

        AssetManager类的成员函数addAssetPath首先是要检查参数path指向的是一个文件或者目录,并且该文件或者目录存在,否则的话,它就会直接返回一个false值,而不会再继续往下处理了。

        AssetManager类的成员函数addAssetPath接着再检查在其成员变量mAssetPaths所描述的一个类型为asset_path的Vector中是否已经添加过参数path所描述的一个Apk文件路径了。如果已经添加过了,那么AssetManager类的成员函数addAssetPath就不会再继续往下处理了,而是将与参数path所描述的一个Apk文件路径所对应的一个Cookie返回给调用者,即保存在输出参数cookie中,前提是参数cookie的值不等于NULL。一个Apk文件路径所对应的Cookie实际上只是一个整数,这个整数表示该Apk文件路径所对应的一个asset_path对象在成员变量mAssetPaths所描述的一个Vector中的索引再加上1。

        经过上面的检查之后,AssetManager类的成员函数addAssetPath确保参数path所描述的一个Apk文件路径之前没有被添加过,于是接下来就会将与该Apk文件路径所对应的一个asset_path对象保存在成员变量mAssetPaths所描述的一个Vector的最末尾位置上,并且将这时候得到的Vector的大小作为一个Cookie值保存在输出参数cookie中返回给调用者。

最终是将path放在了mAssetPaths中,剩下代码是关于overlay相关的。


我们再回到ResourcesManager的getTopLevelResources方法,在新建一个AssetManager对象之后,后面调用了其addAssetPath

        if (resDir != null) {            if (assets.addAssetPath(resDir) == 0) {                return null;            }        }        if (splitResDirs != null) {            for (String splitResDir : splitResDirs) {                if (assets.addAssetPath(splitResDir) == 0) {                    return null;                }            }        }        if (overlayDirs != null) {            for (String idmapPath : overlayDirs) {                assets.addOverlayPath(idmapPath);            }        }

下面就是AssetManager的addAssetPath方法

    public final int addAssetPath(String path) {        synchronized (this) {            int res = addAssetPathNative(path);            makeStringBlocks(mStringBlocks);            return res;        }    }

其中addAssetPathNative是一个native方法,我们来看其jni方法,最后还是调用了c层的AssetManager的addAssetPath方法。

static jint android_content_AssetManager_addAssetPath(JNIEnv* env, jobject clazz,                                                       jstring path){    ScopedUtfChars path8(env, path);    if (path8.c_str() == NULL) {        return 0;    }    AssetManager* am = assetManagerForJavaObject(env, clazz);//保存在java层的AssetManger对象指针    if (am == NULL) {        return 0;    }    int32_t cookie;    bool res = am->addAssetPath(String8(path8.c_str()), &cookie);    return (res) ? static_cast(cookie) : 0;}


三、Resources

我们继续分析ResourcesManager的getTopLevelResources方法,下面就是新建Resources对象了,其中AssetManager就是作为一个参数

        r = new Resources(assets, dm, config, compatInfo);


3.1 Resources构造函数

    public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config,            CompatibilityInfo compatInfo) {        mAssets = assets;        mMetrics.setToDefaults();        if (compatInfo != null) {            mCompatibilityInfo = compatInfo;        }        updateConfiguration(config, metrics);        assets.ensureStringBlocks();    }

Resources类的构造函数首先将参数assets所指向的一个AssetManager对象保存在成员变量mAssets中,以便以后可以通过它来访问应用程序的资源,接下来调用另外一个成员函数updateConfiguration来设置设备配置信息,最后调用参数assets所指向的一个AssetManager对象的成员函数ensureStringBlocks来创建字符串资源池。

        接下来,我们就首先分析Resources类的成员函数updateConfiguration的实现,接着再分析AssetManager类的成员函数ensureStringBlocks的实现。


3.2 updateConfiguration函数

    public void updateConfiguration(Configuration config,            DisplayMetrics metrics, CompatibilityInfo compat) {        synchronized (mAccessLock) {            if (false) {                Slog.i(TAG, "**** Updating config of " + this + ": old config is "                        + mConfiguration + " old compat is " + mCompatibilityInfo);                Slog.i(TAG, "**** Updating config of " + this + ": new config is "                        + config + " new compat is " + compat);            }            if (compat != null) {                mCompatibilityInfo = compat;            }            if (metrics != null) {                mMetrics.setTo(metrics);            }            // NOTE: We should re-arrange this code to create a Display            // with the CompatibilityInfo that is used everywhere we deal            // with the display in relation to this app, rather than            // doing the conversion here.  This impl should be okay because            // we make sure to return a compatible display in the places            // where there are public APIs to retrieve the display...  but            // it would be cleaner and more maintainble to just be            // consistently dealing with a compatible display everywhere in            // the framework.            mCompatibilityInfo.applyToDisplayMetrics(mMetrics);            final int configChanges = calcConfigChanges(config);            if (mConfiguration.locale == null) {                mConfiguration.locale = Locale.getDefault();                mConfiguration.setLayoutDirection(mConfiguration.locale);            }            if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) {                mMetrics.densityDpi = mConfiguration.densityDpi;                mMetrics.density = mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;            }            mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale;            String locale = null;            if (mConfiguration.locale != null) {                locale = adjustLanguageTag(mConfiguration.locale.toLanguageTag());            }            final int width, height;            if (mMetrics.widthPixels >= mMetrics.heightPixels) {                width = mMetrics.widthPixels;                height = mMetrics.heightPixels;            } else {                //noinspection SuspiciousNameCombination                width = mMetrics.heightPixels;                //noinspection SuspiciousNameCombination                height = mMetrics.widthPixels;            }            final int keyboardHidden;            if (mConfiguration.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO                    && mConfiguration.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) {                keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT;            } else {                keyboardHidden = mConfiguration.keyboardHidden;            }            mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,                    locale, mConfiguration.orientation,                    mConfiguration.touchscreen,                    mConfiguration.densityDpi, mConfiguration.keyboard,                    keyboardHidden, mConfiguration.navigation, width, height,                    mConfiguration.smallestScreenWidthDp,                    mConfiguration.screenWidthDp, mConfiguration.screenHeightDp,                    mConfiguration.screenLayout, mConfiguration.uiMode,                    Build.VERSION.RESOURCES_SDK_INT);//调用AssetManager的setConfiguration函数            if (DEBUG_CONFIG) {                Slog.i(TAG, "**** Updating config of " + this + ": final config is " + mConfiguration                        + " final compat is " + mCompatibilityInfo);            }            mDrawableCache.onConfigurationChange(configChanges);            mColorDrawableCache.onConfigurationChange(configChanges);            mColorStateListCache.onConfigurationChange(configChanges);            mAnimatorCache.onConfigurationChange(configChanges);            mStateListAnimatorCache.onConfigurationChange(configChanges);            flushLayoutCache();        }        synchronized (sSync) {            if (mPluralRule != null) {                mPluralRule = NativePluralRules.forLocale(config.locale);            }        }    }

Resources类的成员函数updateConfiguration首先是根据参数config和metrics来更新设备的当前配置信息,例如,屏幕大小和密码、国家地区和语言、键盘配置情况等等,接着再调用成员变量mAssets所指向的一个Java层的AssetManager对象的成员函数setConfiguration来将这些配置信息设置到与之关联的C++层的AssetManager对象中去。


Resources类的成员变量mConfiguration指向的是一个Configuration对象,用来描述设备当前的配置信息,这些配置信息对应的就是如下图。我们接下来再看应用程序资源的组织。应用程序资源的组织方式有18个维度,如图所示:



        接下来,我们就继续分析AssetManager类的成员函数setConfiguration的实现,以便可以了解设备配置信息的设置过程。


3.3 c层AssetManager的setConfiguration函数

而AssetManager的setConfiguration函数是一个native函数,它新建了一个Restable_config,然后对其赋值,最后调用了AssetManager的setConfiguration函数。

static void android_content_AssetManager_setConfiguration(JNIEnv* env, jobject clazz,                                                          jint mcc, jint mnc,                                                          jstring locale, jint orientation,                                                          jint touchscreen, jint density,                                                          jint keyboard, jint keyboardHidden,                                                          jint navigation,                                                          jint screenWidth, jint screenHeight,                                                          jint smallestScreenWidthDp,                                                          jint screenWidthDp, jint screenHeightDp,                                                          jint screenLayout, jint uiMode,                                                          jint sdkVersion){    AssetManager* am = assetManagerForJavaObject(env, clazz);    if (am == NULL) {        return;    }    ResTable_config config;    memset(&config, 0, sizeof(config));    const char* locale8 = locale != NULL ? env->GetStringUTFChars(locale, NULL) : NULL;    // Constants duplicated from Java class android.content.res.Configuration.    static const jint kScreenLayoutRoundMask = 0x300;    static const jint kScreenLayoutRoundShift = 8;    config.mcc = (uint16_t)mcc;    config.mnc = (uint16_t)mnc;    config.orientation = (uint8_t)orientation;    config.touchscreen = (uint8_t)touchscreen;    config.density = (uint16_t)density;    config.keyboard = (uint8_t)keyboard;    config.inputFlags = (uint8_t)keyboardHidden;    config.navigation = (uint8_t)navigation;    config.screenWidth = (uint16_t)screenWidth;    config.screenHeight = (uint16_t)screenHeight;    config.smallestScreenWidthDp = (uint16_t)smallestScreenWidthDp;    config.screenWidthDp = (uint16_t)screenWidthDp;    config.screenHeightDp = (uint16_t)screenHeightDp;    config.screenLayout = (uint8_t)screenLayout;    config.uiMode = (uint8_t)uiMode;    config.sdkVersion = (uint16_t)sdkVersion;    config.minorVersion = 0;    // In Java, we use a 32bit integer for screenLayout, while we only use an 8bit integer    // in C++. We must extract the round qualifier out of the Java screenLayout and put it    // into screenLayout2.    config.screenLayout2 =            (uint8_t)((screenLayout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift);    am->setConfiguration(config, locale8);    if (locale != NULL) env->ReleaseStringUTFChars(locale, locale8);}

AssetManager::setConfiguration函数如下:

void AssetManager::setConfiguration(const ResTable_config& config, const char* locale){    AutoMutex _l(mLock);    *mConfig = config;    if (locale) {        setLocaleLocked(locale);    } else if (config.language[0] != 0) {        char spec[RESTABLE_MAX_LOCALE_LEN];        config.getBcp47Locale(spec);        setLocaleLocked(spec);    } else {        updateResourceParamsLocked();    }}

AssetManager类的成员变量mConfig指向的是一个ResTable_config对象,用来描述设备的当前配置信息,AssetManager类的成员函数setConfiguration首先将参数config所描述的设备配置信息拷贝到它里面去。

        如果参数local的值不等于NULL,那么它指向的字符串就是用来描述设备的国家、地区和语言信息的,这时候AssetManager类的成员函数setConfiguration就会调用另外一个成员函数setLocalLocked来将它们设置到AssetManager类的另外一个成员变量mLocale中去。

        如果参数local的值等于NULL,并且参数config指向的一个ResTable_config对象包含了设备的国家、地区和语言信息,那么AssetManager类的成员函数setConfiguration同样会调用另外一个成员函数setLocalLocked来将它们设置到AssetManager类的另外一个成员变量mLocale中去。

        如果参数local的值等于NULL,并且参数config指向的一个ResTable_config对象没有包含设备的国家、地区和语言信息,那么就说明设备的国家、地区和语言等信息不需要更新,这时候AssetManager类的成员函数setConfiguration就会直接调用另外一个成员函数updateResourceParamsLocked来更新资源表中的设备配置信息。

        注意,AssetManager类的成员函数setLocalLocked来更新了成员变量mLocale的内容之后,同样会调用另外一个成员函数updateResourceParamsLocked来更新资源表中的设备配置信息。

        AssetManager类的成员函数updateResourceParamsLocked的实现如下所示:

void AssetManager::updateResourceParamsLocked() const{    ResTable* res = mResources;    if (!res) {        return;    }    if (mLocale) {        mConfig->setBcp47Locale(mLocale);    } else {        mConfig->clearLocale();    }    res->setParameters(mConfig);}

AssetManager类的成员变量mResources指向的是一个ResTable对象,这个ResTable对象描述的就是一个资源索引表。Android应用程序的资源索引表的格式以及生成过程可以参考http://blog.csdn.net/luoshengyang/article/details/8744683博客。

updateResourceParamsLocked函数将成员变量mConfig所包含的设备配置信息设置到成员变量mResources所描述的一个资源索引表中去,这是通过调用成员变量mResources所指向的一个ResTable对象的成员函数setParameters来实现的。


3.4 ensureStringBlocks函数

在Resources的构造函数中最后调用了AssetManager的ensureStringBlocks函数

    /*package*/ final void ensureStringBlocks() {        if (mStringBlocks == null) {            synchronized (this) {                if (mStringBlocks == null) {                    makeStringBlocks(sSystem.mStringBlocks);                }            }        }    }

AssetManager类的成员变量mStringBlocks指向的是一个StringBlock数组,其中,每一个StringBlock对象都是用来描述一个字符串资源池。在http://blog.csdn.net/luoshengyang/article/details/8744683这篇博客可以知道,每一个资源表都包含有一个资源项值字符串资源池,AssetManager类的成员变量mStringBlocks就是用来保存所有的资源表中的资源项值字符串资源池的。

        AssetManager类的成员函数ensureStringBlocks首先检查成员变量mStringBlocks的值是否等于null。如果等于null的话,那么就说明当前应用程序使用的资源表中的资源项值字符串资源池还没有读取出来,这时候就会调用另外一个成员函数makeStringBlocks来进行读取。

    /*package*/ final void makeStringBlocks(StringBlock[] seed) {        final int seedNum = (seed != null) ? seed.length : 0;        final int num = getStringBlockCount();        mStringBlocks = new StringBlock[num];        if (localLOGV) Log.v(TAG, "Making string blocks for " + this                + ": " + num);        for (int i=0; i

这两个方法也是native方法

    private native final int getStringBlockCount();    private native final long getNativeStringBlock(int block);

参数seed表示系统资源表里面的资源项值字符串资源池也要一起拷贝到成员变量mStringBlokcs所描述的一个数组中去。如果它的值不为空的时候,那么AssetManager就会首先获得makeStringBlocks首先获得系统资源表的个数seedNum,接着再获得总的资源表个数num,这是通过调用JNI方法getStringBlockCount来实现的。注意,总的资源表个数num是包含了系统资源表的个数seedNum的。

        用来访问系统资源包的AssetManager对象就保存在AssetManager类的静态成员变量sSystem中,并且这个AssetManager对象是最先被创建以及初始化的。也就是说,当执行到这一步的时候,所有系统资源表的资源项值字符串资源池已经读取出来,它们就保存在AssetManager类的静态成员变量sSystem所描述的一个AssetManager对象的成员变量mStringBlocks中,因此,只将它们拷贝到当前正在处理的AssetManager对象的成员变量mStringBlokcs的前sysNum个位置上去就可以了。

        最后,AssetManager类的成员函数makeStringBlocks就调用另外一个JNI方法getNativeStringBlock来读取剩余的其它资源表的资源项值字符串资源池,并且分别将它们封装在一个StringBlock对象保存在成员变量mStringBlokcs所描述的一个数组中。

        AssetManager类的JNI方法getNativeStringBlock实际上就是将每一个资源包里面的resources.arsc文件的资源项值字符串资源池数据块读取出来,并且封装在一个C++层的StringPool对象中,然后AssetManager类的成员函数makeStringBlocks再将该StringPool对象封装成一个Java层的StringBlock中。


这篇博客主要就是创建和初始化用来访问应用程序资源的AssetManager对象和Resources对象,其中,初始化操作包括设置AssetManager对象的资源文件路径以及设备配置信息等。有了这两个初始化的AssetManager对象和Resources对象之后,下篇博客就要分析应用程序资源的查找过程了。

更多相关文章

  1. C语言函数的递归(上)
  2. Android(安卓)Log系统介绍 (基于Android(安卓)N)
  3. 兼容性(一) - 使代码向前兼容SDK版本
  4. Android---Handler消息处理机制
  5. Android(安卓)官方架构组件(三)——ViewModel
  6. Android(安卓)Asynchronous Http Client 中文手册
  7. ( 经典 ) Android深入浅出之Binder机制
  8. Android:overridePendingTransition()函数介绍
  9. Android(安卓)JNI详述

随机推荐

  1. Android之View的诞生之谜
  2. GreenDao 3.0 简介、使用及踩坑
  3. Android init.rc
  4. Unknown host ‘XXXX: nodename nor serv
  5. 深入浅出 - Android系统移植与平台开发(五
  6. Android生命周期中几个重要的函数
  7. Android Camera数据流分析全程记录
  8. 【转载】微信Android 视频编码爬过的那些
  9. android 内存和性能优化汇总
  10. android 中fragment和activity的区别?