对象类Android应用程序资源管理器(Asset Manager)的创建过程分析
发一下牢骚和主题无关:
在面前一篇文章中,我们析分了Android应用程序源资的编译和打包进程,终最失掉的应用程序源资就与应用程序代码一同打包在一个APK文件中。Android应用程序在行运的进程中,是通过一个称为AssetManager的源资管理器来读取打包在APK文件里头的源资文件的。在本文中,我们就将详细析分Android应用程序源资管理器的创立以及初始化进程,为接上去的一篇文章析分应用程序源资的读取进程打下基本。
老罗的新浪微博:http://weibo.com/shengyangluo,欢送注关!
从面前Android应用程序窗口(Activity)的行运上下文环境(Context)的创立进程析分一文可以道知,应用程序的个一每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中。上述ContextImpl、Resourses和AssetManager的关系如图1所示:
图1 ContextImpl、Resources和AssetManager的关系图
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类就能够问访应用程序的源资了。
从面前Android应用程序动启进程源代码析分一文可以道知,个一每Activity组件在进程的加载进程中,都市创立一个对应的ContextImpl,并且调用这个ContextImpl对象的成员函数init来行执初始化Activity组件行运上下文环境的任务,其中就括包创立用来问访应用程序源资的Resources对象和AssetManager对象的任务,接上去,我们就从ContextImpl类的成员函数init开始析分Resources对象和AssetManager对象的创立以及初始化进程,如图2所示:
图2 应用程序源资管理器的创立和初始化进程
这个进程可以分为14个步调,接上去我们就详细析分个一每步调。
Step 1. ContextImpl.init
class ContextImpl extends Context { ...... /*package*/ LoadedApk mPackageInfo; private Resources mResources; ...... final void init(LoadedApk packageInfo, IBinder activityToken, ActivityThread mainThread) { init(packageInfo, activityToken, mainThread, null); } final void init(LoadedApk packageInfo, IBinder activityToken, ActivityThread mainThread, Resources container) { mPackageInfo = packageInfo; mResources = mPackageInfo.getResources(mainThread); ...... } ...... }
这个函数义定在文件frameworks/base/core/java/android/app/ContextImpl.java中。
参数packageInfo指向的是一个LoadedApk对象,这个LoadedApk对象描述的是前当正在动启的Activity组所属的Apk。三个参数版本的成员函数init调用了四个参数版本的成员函数init来初始化前当正在动启的Activity组件的行运上下文环境。其中,用来问访应用程序源资的Resources对象是通过调用参数packageInfo所指向的是一个LoadedApk对象的成员函数getResources来创立的。这个Resources对象创立成完后之,就会存保在ContextImpl类的成员变量mResources中。
接上去,我们就继承析分LoadedApk类的成员函数getResources的实现。
Step 2. LoadedApk.getResources
final class LoadedApk { ...... private final String mResDir; ...... Resources mResources; ...... public Resources getResources(ActivityThread mainThread) { if (mResources == null) { mResources = mainThread.getTopLevelResources(mResDir, this); } return mResources; } ...... }
这个函数义定在文件frameworks/base/core/java/android/app/LoadedApk.java中。
参数mainThread指向了一个ActivityThread对象,这个ActivityThread对象描述的是前当正在行运的应用程序进程。
LoadedApk类的成员函数getResources首先查检其成员变量mResources的值是不是于等null。如果不于等的话,那么就会将它所指向的是一个Resources对象返回给调用者,否则的话,就会调用参数mainThread所指向的一个ActivityThread对象的成员函数getTopLevelResources来取得这个Resources对象,然后再返回给调用者。
在调用ActivityThread类的成员函数getTopLevelResources来取得一个Resources对象的时候,要需指定要取获的Resources对象所对应的Apk文件径路,这个Apk文件径路就存保在LoadedApk类的成员变量mResDir中。例如,设假我们要取获的Resources对象是用来问访统系自带的音乐播放器的源资的,那么对应的Apk文件径路就为/system/app/Music.apk。
接上去,我们就继承析分ActivityThread类的成员函数getTopLevelResources的实现。
Step 3. ActivityThread.getTopLevelResources
public final class ActivityThread { ...... final HashMap<ResourcesKey, WeakReference<Resources> > mActiveResources = new HashMap<ResourcesKey, WeakReference<Resources> >(); ...... Resources getTopLevelResources(String resDir, CompatibilityInfo compInfo) { ResourcesKey key = new ResourcesKey(resDir, compInfo.applicationScale); Resources r; synchronized (mPackages) { ...... WeakReference<Resources> wr = mActiveResources.get(key); r = wr != null ? wr.get() : null; ...... if (r != null && r.getAssets().isUpToDate()) { ...... return r; } } ...... AssetManager assets = new AssetManager(); if (assets.addAssetPath(resDir) == 0) { return null; } ...... r = new Resources(assets, metrics, getConfiguration(), compInfo); ...... 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; } } ...... }
这个函数义定在文件frameworks/base/core/java/android/app/ActivityThread.java中。
ActivityThread类的成员变量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对象的创立以及初始化进程。
Step 4. new AssetManager
public final class AssetManager { ...... private static AssetManager sSystem = null; ...... public AssetManager() { synchronized (this) { ...... init(); ...... ensureSystemAssets(); } } private static void ensureSystemAssets() { synchronized (sSync) { if (sSystem == null) { AssetManager system = new AssetManager(true); system.makeStringBlocks(false); sSystem = system; } } } ...... }
这个函数义定在文件frameworks/base/core/java/android/content/res/AssetManager.java中。
AssetManager类的构造函数是通过调用另外一个成员函数init来行执初始化任务的。在初始化成完前当正在创立的AssetManager对象后之,AssetManager类的构造函数还会调用另外一个成员函数ensureSystemAssets来查检前当进程是不是已创立了一个用来问访统系源资的AssetManager对象。
如果用来问访统系源资的AssetManager对象还没有创立的话,那么AssetManager类的成员函数ensureSystemAssets就会创立并且初始化它,并且将它存保在AssetManager类的静态成员变量sSystem中。意注,创立用来问访统系源资和应用程序源资的AssetManager对象的进程是一样的,区别只在于它们所要问访的Apk文件不一样,因此,接上去我们就只析分用来问访应用源资的AssetManager对象的创立进程以及初始化进程。
Step 5. AssetManager.init
public final class AssetManager { ...... private int mObject; ...... private native final void init(); ...... }
这个函数义定在文件frameworks/base/core/java/android/content/res/AssetManager.java中。
AssetManager类的成员函数init是一个JNI函数,它是由C++层的函数android_content_AssetManager_init来实现的:
static void android_content_AssetManager_init(JNIEnv* env, jobject clazz) { AssetManager* am = new AssetManager(); ..... am->addDefaultAssets(); ...... env->SetIntField(clazz, gAssetManagerOffsets.mObject, (jint)am); }
这个函数义定在文件frameworks/base/core/jni/android_util_AssetManager.cpp中。
函数android_content_AssetManager_init首先创立一个C++层的AssetManager对象,接着调用这个C++层的AssetManager对象的成员函数addDefaultAssets来添加认默的源资径路,最后将这个这个C++层的AssetManager对象的地址存保在参数clazz所描述的一个Java层的AssetManager对象的成员变量mObject中。
Step 6. 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); }
这个函数义定在文件frameworks/base/libs/utils/AssetManager.cpp中。
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对象中去。
Step 7. AssetManager.addAssetPath
static const char* kAppZipName = NULL; //"classes.jar"; ...... 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) { ...... 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; } } ...... 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); } ...... } } return true; }
这个函数义定在文件frameworks/base/libs/utils/AssetManager.cpp中。
如果全局变量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中返回给调用者。
AssetManager类的成员函数addAssetPath的最后一个任务是查检刚刚添加的Apk文件径路是不是是存保在/system/framework/目录下面的。如果是的话,那么就会在/vendor/overlay/framework/目录下找到一个同名的Apk文件,并且也会将该Apk文件添加到成员变量mAssetPaths所描述的一个Vector中去。这是一种源资覆盖机制,手机厂商可以利用它来自义定的统系源资,即用自义定的统系源资来覆盖统系认默的统系源资,以达到个性化统系界面的目的。
如果手机厂商要利用上述的源资覆盖机制来自义定自己的统系源资,那么还要需提供一个idmap文件,用来说明它在/vendor/overlay/framework/目录提供的Apk文件要覆盖统系的哪些认默源资,应用源资ID来描述,因此,这个idmap文件实际上就是一个源资ID映射文件。这个idmap文件终最存保在/data/resource-cache/目录下,并且按照一定的格式来命令,例如,设假手机厂商提供的覆盖源资文件为/vendor/overlay/framework/framework-res.apk,那么对应的idmap文件就会以名称为@vendor@overlay@framework@framework-res.apk@idmap的形式存保在目录/data/resource-cache/下。
关于Android统系的源资覆盖(Overlay)机制,可以参考frameworks/base/libs/utils目录下的READ文件。
这一步行执成完后之,回到面前的Step 3中,即ActivityThread类的成员函数getTopLevelResources中,接上去它就会调用面前所创立的Java层的AssetManager对象的成员函数addAssetPath来添加指定的应用程序源资文件径路。
Step 8. AssetManager.addAssetPath
public final class AssetManager { ...... /** * Add an additional set of assets to the asset manager. This can be * either a directory or ZIP file. Not for use by applications. Returns * the cookie of the added asset, or 0 on failure. * {@hide} */ public native final int addAssetPath(String path); ...... }
这个函数义定在文件frameworks/base/core/java/android/content/res/AssetManager.java中。
AssetManager类的成员函数addAssetPath是一个JNI方法,它是由C++层的函数android_content_AssetManager_addAssetPath来实现的,如下所示:
static jint android_content_AssetManager_addAssetPath(JNIEnv* env, jobject clazz, jstring path) { ...... AssetManager* am = assetManagerForJavaObject(env, clazz); ...... const char* path8 = env->GetStringUTFChars(path, NULL); void* cookie; bool res = am->addAssetPath(String8(path8), &cookie); env->ReleaseStringUTFChars(path, path8); return (res) ? (jint)cookie : 0; }
这个函数义定在文件frameworks/base/core/jni/android_util_AssetManager.cpp中。
参数clazz指向的是Java层的一个AssetManager对象,函数android_content_AssetManager_addAssetPath首先调用另外一个函数assetManagerForJavaObject来将它的成员函数mObject转换为一个C++层的AssetManager对象。有了这个C++层的AssetManager对象后之,就能够调用它的成员函数addAssetPath来将参数path所描述的一个Apk文件径路添加到它里头去了,这个进程可以参考面前的Step 7。
这一步行执成完后之,回到面前的Step 3中,即ActivityThread类的成员函数getTopLevelResources中,接上去就会根据面前所创立的Java层的AssetManager对象来创立一个Resources对象。
Step 9. new Resources
public class Resources { ...... /*package*/ final AssetManager mAssets; ...... public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config, CompatibilityInfo compInfo) { mAssets = assets; ...... updateConfiguration(config, metrics); assets.ensureStringBlocks(); } ...... }
这个函数义定在文件frameworks/base/core/java/android/content/res/Resources.java中。
Resources类的构造函数首先将参数assets所指向的一个AssetManager对象存保在成员变量mAssets中,以便后以可以通过它来问访应用程序的源资,接上去调用另外一个成员函数updateConfiguration来置设设备置配息信,最后调用参数assets所指向的一个AssetManager对象的成员函数ensureStringBlocks来创立字符串源资池。
接上去,我们就首先析分Resources类的成员函数updateConfiguration的实现,接着再析分AssetManager类的成员函数ensureStringBlocks的实现。
Step 10. Resources.updateConfiguration
public class Resources { ...... private final Configuration mConfiguration = new Configuration(); ...... public void updateConfiguration(Configuration config, DisplayMetrics metrics) { synchronized (mTmpValue) { int configChanges = 0xfffffff; if (config != null) { configChanges = mConfiguration.updateFrom(config); } if (mConfiguration.locale == null) { mConfiguration.locale = Locale.getDefault(); } if (metrics != null) { mMetrics.setTo(metrics); mMetrics.updateMetrics(mCompatibilityInfo, mConfiguration.orientation, mConfiguration.screenLayout); } mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale; String locale = null; if (mConfiguration.locale != null) { locale = mConfiguration.locale.getLanguage(); if (mConfiguration.locale.getCountry() != null) { locale += "-" + mConfiguration.locale.getCountry(); } } 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; } int keyboardHidden = mConfiguration.keyboardHidden; if (keyboardHidden == Configuration.KEYBOARDHIDDEN_NO && mConfiguration.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) { keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT; } mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc, locale, mConfiguration.orientation, mConfiguration.touchscreen, (int)(mMetrics.density*160), mConfiguration.keyboard, keyboardHidden, mConfiguration.navigation, width, height, mConfiguration.screenLayout, mConfiguration.uiMode, sSdkVersion); ...... } ...... } ...... }
这个函数义定在文件frameworks/base/core/java/android/content/res/Resources.java中。
Resources类的成员变量mConfiguration指向的是一个Configuration对象,用来描述设备前当的置配息信,这些置配息信对应的就是在面前Android源资管理框架(Asset Manager)简要介绍和学习计划一文中提到18个源资维度。
Resources类的成员函数updateConfiguration首先是根据参数config和metrics来更新设备的前当置配息信,例如,屏幕小大和密码、家国地域和语言、键盘置配情况等等,接着再调用成员变量mAssets所指向的一个Java层的AssetManager对象的成员函数setConfiguration来将这些置配息信置设到与之关联的C++层的AssetManager对象中去。
接上去,我们就继承析分AssetManager类的成员函数setConfiguration的实现,以便可以懂得设备置配息信的置设进程。
Step 11. AssetManager.setConfiguration
public final class AssetManager { ...... /** * Change the configuation used when retrieving resources. Not for use by * applications. * {@hide} */ public native final void setConfiguration(int mcc, int mnc, String locale, int orientation, int touchscreen, int density, int keyboard, int keyboardHidden, int navigation, int screenWidth, int screenHeight, int screenLayout, int uiMode, int majorVersion); ...... }
这个函数义定在文件frameworks/base/core/java/android/content/res/AssetManager.java中。
AssetManager类的成员函数setConfiguration是一个JNI方法,它是由C++层的函数android_content_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 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; 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.screenLayout = (uint8_t)screenLayout; config.uiMode = (uint8_t)uiMode; config.sdkVersion = (uint16_t)sdkVersion; config.minorVersion = 0; am->setConfiguration(config, locale8); if (locale != NULL) env->ReleaseStringUTFChars(locale, locale8); }
这个函数义定在文件frameworks/base/core/jni/android_util_AssetManager.cpp中。
参数clazz指向的是一个Java层的AssetManager对象,函数android_content_AssetManager_setConfiguration首先调用另外一个函数assetManagerForJavaObject将它的成员变量mObject转换为一个C++层的AssetManager对象。
函数android_content_AssetManager_setConfiguration接上去再根据其它参数来创立一个ResTable_config对象,这个ResTable_config对象就用来描述设备的前当置配息信。
函数android_content_AssetManager_setConfiguration最后调用面前取得C++层的AssetManager对象的成员函数setConfiguration来将面前创立的ResTable_config对象置设到它内部去,以便C++层的AssetManager对象可以根据设备的前当置配息信来找到最合适的源资。
Step 12. 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[9]; spec[0] = config.language[0]; spec[1] = config.language[1]; if (config.country[0] != 0) { spec[2] = '_'; spec[3] = config.country[0]; spec[4] = config.country[1]; spec[5] = 0; } else { spec[3] = 0; } setLocaleLocked(spec); } else { updateResourceParamsLocked(); } }
这个函数义定在文件frameworks/base/libs/utils/AssetManager.cpp中。
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; } size_t llen = mLocale ? strlen(mLocale) : 0; mConfig->language[0] = 0; mConfig->language[1] = 0; mConfig->country[0] = 0; mConfig->country[1] = 0; if (llen >= 2) { mConfig->language[0] = mLocale[0]; mConfig->language[1] = mLocale[1]; } if (llen >= 5) { mConfig->country[0] = mLocale[3]; mConfig->country[1] = mLocale[4]; } mConfig->size = sizeof(*mConfig); res->setParameters(mConfig); }
这个函数义定在文件frameworks/base/libs/utils/AssetManager.cpp中。
AssetManager类的成员变量mResources指向的是一个ResTable对象,这个ResTable对象描述的就是一个源资索引表。Android应用程序的源资索引表的格式以及生成进程可以参考面前Android应用程序源资的编译和打包进程析分一文。
AssetManager类的成员函数updateResourceParamsLocked首先是将成员变量mLocale所描述的家国、地域和语言息信更新到另外一个成员变量mConfig中去,接着再将成员变量mConfig所含包的设备置配息信置设到成员变量mResources所描述的一个源资索引表中去,这是通过调用成员变量mResources所指向的一个ResTable对象的成员函数setParameters来实现的。
这一步行执成完后之,返回到面前的Step 9中,即Resources类的构造函数,接上去它就会调用AssetManager类的成员函数ensureStringBlocks来创立字符串源资池。
Step 13. AssetManager.ensureStringBlocks
public final class AssetManager { ...... private StringBlock mStringBlocks[] = null; ...... /*package*/ final void ensureStringBlocks() { if (mStringBlocks == null) { synchronized (this) { if (mStringBlocks == null) { makeStringBlocks(true); } } } } ...... }
这个函数义定在文件frameworks/base/core/java/android/content/res/AssetManager.java中。
AssetManager类的成员变量mStringBlocks指向的是一个StringBlock数组,其中,个一每StringBlock对象都是用来描述一个字符串源资池。从面前Android应用程序源资的编译和打包进程析分一文可以道知,个一每源资表都含包有一个源资项值字符串源资池,AssetManager类的成员变量mStringBlocks就是用来存保所有的源资表中的源资项值字符串源资池的。
AssetManager类的成员函数ensureStringBlocks首先查检成员变量mStringBlocks的值是不是于等null。如果于等null的话,那么就说明前当应用程序应用的源资表中的源资项值字符串源资池还没有读取出来,这时候就会调用另外一个成员函数makeStringBlocks来进行读取。
Step 14. AssetManager.makeStringBlocks
public final class AssetManager { ...... private final void makeStringBlocks(boolean copyFromSystem) { final int sysNum = copyFromSystem ? sSystem.mStringBlocks.length : 0; final int num = getStringBlockCount(); mStringBlocks = new StringBlock[num]; ...... for (int i=0; i<num; i++) { if (i < sysNum) { mStringBlocks[i] = sSystem.mStringBlocks[i]; } else { mStringBlocks[i] = new StringBlock(getNativeStringBlock(i), true); } } } ...... private native final int getStringBlockCount(); private native final int getNativeStringBlock(int block); ...... }
这个函数义定在文件frameworks/base/core/java/android/content/res/AssetManager.java中。
参数copyFromSystem表示是不是要将统系源资表里头的源资项值字符串源资池也一同拷贝到成员变量mStringBlokcs所描述的一个数组中去。如果它的值于等true的时候,那么AssetManager就会首先取得makeStringBlocks首先取得统系源资表的个数sysNum,接着再取得总的源资表个数num,这是通过调用JNI方法getStringBlockCount来实现的。意注,总的源资表个数num是含包了统系源资表的个数sysNum的。
从面前的Step 4可以道知,用来问访统系源资包的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中。关于源资表中的源资项值字符串源资池的更多息信,可以参考面前Android应用程序源资的编译和打包进程析分一文。
至此,我们就析分成完Android应用程序源资管理器的创立的初始化进程了,主要就是创立和初始化用来问访应用程序源资的AssetManager对象和Resources对象,其中,初始化操作括包置设AssetManager对象的源资文件径路以及设备置配息信等。有了这两个初始化的AssetManager对象和Resources对象后之,在接上去的一篇文章中,我们就能够继承析分应用程序源资的找查进程了,敬请注关!
老罗的新浪微博:http://weibo.com/shengyangluo,欢送注关!
文章结束给大家分享下程序员的一些笑话语录:
Bphone之你们聊,我先走了!移动说:我在phone前加o,我叫o缝;苹果说:我在phone前i,我是i缝;微软说:我在phone前加w,我叫w缝;三星说:你们聊,我先走了!
将来王建宙写回忆录的时候,一定要有一句“常小兵为中国移动的发展做出了不可磨灭的贡献”。