大家好,关于Android 6.0时代:深入解析应用资源管理对象创建策略很多朋友都还不太明白,今天小编就来为大家分享关于的知识,希望对各位有所帮助!
其中Resources类可以根据ID查找资源,AssetManager类可以根据文件名查找资源。
事实上,从资源搜索过程来看,可以分为两大类。第一类资源没有对应的文件,第二类资源有文件。例如,字符串资源直接编译在resources.arsc文件中,而界面布局资源则对应在APK包中。的单独文件。
如果一个资源ID对应一个文件,那么Resources类首先根据该ID找到资源文件名,然后将文件名传递给AssetManager类来打开对应的文件。基本流程如下:
resources-3.jpg 而且这两个类都有相应的缓存机制来缓存资源,这样下次使用的时候就不需要再去查找和加载了。
App的Resources对象创建过程
既然这两个类负责管理Android资源,那么我们需要弄清楚这两个类的对象是如何以及何时在应用程序中创建的。
资源将包含一个AssetManager 对象。首先,重点关注Resources对象的创建过程。首先看一个整体时序图:
resources-4.jpg 创建的资源对象将缓存在LoadedApk中。并且在创建context时,LoadedApk是相同的,因此同一个应用中的不同ContextImpl获取的是同一组资源。
ActivityThread类的成员变量mActiveResources指向一个HashMap。这个HashMap用来维护当前应用进程中加载的每个Apk文件与其对应的Resources对象的对应关系。
也就是说,给定一个Apk文件路径,ActivityThread类的成员函数getTopLevelResources就可以检查成员变量mActiveResources中是否存在对应的Resources对象。如果不存在,就会创建一个新的,并保存在ActivityThread类的成员变量mActiveResources中。
Context中提供了getResources()方法来获取资源对象,因此在Activity中可以方便地获取该对象:
资源res=getResources(); Activity中的context实际上是对ComtextImpl的封装,所以最终是通过ContextImpl.getResources()来获取resources对象的:
公共资源getResources() {
返回mResources;
}而mResources是ContextImpl的属性成员。而mResources是在ContextImpl的构造函数中初始化的。
看一下ContextImpl的构造函数:
私有ContextImpl(ContextImpl容器,ActivityThread主线程,
LoadedApk packageInfo、IBinder ActivityToken、UserHandle 用户、布尔限制、
显示显示,配置overrideConfiguration,int createDisplayWithId) {
…………
资源resources=packageInfo.getResources(mainThread);
如果(资源!=null){
//由于6.0不支持多屏显示,因此不会走该分支。虽然相关代码已经很多了,但多屏操作在7.0才正式支持。
if (displayId !=Display.DEFAULT_DISPLAY
||覆盖配置!=null
|| (compatInfo !=null compatInfo.applicationScale
!=resources.getCompatibilityInfo().applicationScale)) {
.
}
}
mResources=资源;
.从ContextImpl构造函数中发现,是通过传入的LoadedApk对象的getResources()方法获取Resources对象:
公共资源getResources(ActivityThread mainThread) {
//缓存机制,如果LoadedApk中的mResources已经初始化,则直接返回。
//否则,通过ActivityThread创建资源对象
if (mResources==null) {
mResources=mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs,
mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null, this);
}
返回mResources;
在LoadedApk.getResources()方法中,首先判断mResources是否为null。如果为null,则调用ActivityThread.getTopLevelResources()方法获取Resources对象。
这里需要注意的是,LoadedApk类有两种构造方法:一种用于系统app,一种用于普通app。普通应用的构造方法中没有初始化mResources的值。系统应用LoadedApk使用的构造方法中,mResources的值被初始化为Resources.getSystem()。
这里我们只关心普通应用程序的LoadedApk对象是在没有初始化mResources对象的情况下创建的。那么当第一次调用LoadedApk对象的getResources()方法时,会通过调用以下方法创建一个Resources对象并缓存起来。以后通过LoadedApk.getResources()获取时,不需要重新创建Resources对象。只需返回到您之前创建的内容即可。
/**
* 为给定包创建顶级资源。
*/
资源getTopLevelResources(
String resDir, //app资源文件夹路径,其实就是apk文件的路径,如/data/app/包名/base.apk
String[] splitResDirs, //当一个app由多个apk组成时(将原apk切分成若干个apk),每个子apk中的资源文件夹
字符串[]覆盖目录,
String[] libDirs, //app依赖的共享jar/apk路径
int 显示ID,
配置覆盖配置,
LoadedApk pkgInfo //代表正在运行的应用程序
){
返回mResourcesManager.getTopLevelResources(resDir,splitResDirs,overlayDirs,libDirs,
displayId、overrideConfiguration、pkgInfo.getCompatibilityInfo());
}ActivityThread.getTopLevelResources()方法通过ResourcesManager类的getTopLevelResources()方法创建Resources对象:
/**
* 为具有给定兼容性信息的应用程序创建顶级资源。
*
* @param resDir 资源目录。
* @param splitResDirs 分割资源目录。
* @param overrideDirs 资源覆盖目录。
* @param libDirs 此应用程序引用的共享库资源目录。
* @param displayId 显示Id。
* @param overrideConfiguration 覆盖配置。
* @param compatInfo 兼容性信息。不得为空。
*/
资源getTopLevelResources(String resDir, String[] splitResDirs,
String[] OverlayDirs, String[] libDirs, int displayId,
配置overrideConfiguration, CompatibilityInfo compatInfo) {
……………………
//以apk路径作为参数创建密钥
ResourcesKey key=new ResourcesKey(resDir, displayId, overrideConfigCopy, scale);
资源;
同步(这个){
//资源取决于应用程序规模。
if (DEBUG) Slog.w(TAG, "getTopLevelResources: " + resDir + "/" + scale);
//检查apk对应的resources对象是否已经存在
WeakReferencewr=mActiveResources.get(key);
r=wr !=null ? 空;
//if (r !=null) Log.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());
if (r !=null r.getAssets().isUpToDate()) {
if (DEBUG) Slog.w(TAG, "返回缓存资源" + r + " " + resDir
+ ": appScale=" + r.getCompatibilityInfo().applicationScale
+ " key=" + key + " overrideConfig=" + overrideConfiguration);
返回r;
}
}
//创建AssetManager对象
AssetManager 资产=new AssetManager();
//将应用程序中的资源路径添加到AssetManager对象中
if (resDir !=null) {
if (assets.addAssetPath(resDir)==0) {
返回空值;
}
}
如果(splitResDirs!=null){
for (字符串splitResDir : splitResDirs) {
if (assets.addAssetPath(splitResDir)==0) {
返回空值;
}
}
}
如果(overlayDirs!=null){
for (String idmapPath : overrideDirs) {
资产.addOverlayPath(idmapPath);
}
}
if (libDirs !=null) {
for (String libDir : libDirs) {
//只选择共享依赖中的apk,因为jar中不会有资源文件
if (libDir.endsWith(".apk")) {
if (assets.addAssetPath(libDir)==0) {
Log.w(TAG, "资源路径"" + libDir +
""不存在或不包含任何资源。");
}
}
}
}
…………
r=新资源(资产、dm、配置、compatInfo);
...
mActiveResources.put(key, new WeakReference(r));
返回r
}创建Resources对象时,有一个创建AssetManager对象并向其添加应用程序资源路径的过程。
现在是时候回答Resources 对象何时创建的问题了:
当普通应用对应的LoadedApk对象第一次调用LoadedApk.getResources()方法时,由于LoadedApk中没有缓存,因此会创建该对象并缓存。当稍后再次调用LoadedApk.getResources()方法时,Resources对象将不会被创建,因为它被缓存了。
当创建ContextImpl 对象时,不一定会创建新的Resources 对象。通常,正在运行的应用程序的资源对象仅创建一次。并缓存在LoadedApk对象中。一个正在运行的应用程序可以有多个Context,但每个Context 都包含相同的LoadedApk 对象。
管理系统资源的Resources对象
Resources类中的关键数据成员是:
//在zygote中缓存预加载的资源
私有静态最终LongSparseArray[] sPreloadedDrawables;
私有静态最终LongSparseArraysPreloadedColorDrawables
=新的LongSparseArray();
私有静态最终LongSparseArraysPreloadedColorStateLists=new LongSparseArray();
//引用系统资源实例
静态资源mSystem=null;
//drawable和ColorStateList的缓存
私有最终DrawableCache mDrawableCache=new DrawableCache(this);
私有最终DrawableCache mColorDrawableCache=new DrawableCache(this);
私有最终ConfigurationBoundResourceCachemColorStateListCache=
新的ConfigurationBoundResourceCache(this);
私有最终ConfigurationBoundResourceCachemAnimatorCache=
新的ConfigurationBoundResourceCache(this);
私有最终ConfigurationBoundResourceCachemStateListAnimatorCache=
新的ConfigurationBoundResourceCache(this);
//xml文件的缓存
私有int mLastCachedXmlBlockIndex=-1;
私有最终int[] mCachedXmlBlockIds={ 0, 0, 0, 0 };
私有最终XmlBlock[] mCachedXmlBlocks=new XmlBlock[4];
//AssetManager实例的引用非常关键。
最终的AssetManager mAssets;对于上述静态类型的关键成员,在内存中只有一份,因此必须知道这些静态成员是在何时何地初始化的。
以mSystem为例,如下图:
ZygoteInit的preloadResources()方法中初始化的mSystem值意味着mSystem是由Zygote进程初始化的,所以它孵化的App进程肯定会继承这个东西。
mSystem主要负责预加载Android系统本身提供的资源(framework-res.apk)。加载后的资源存储在其他静态成员变量中,App以后可以通过它们访问系统资源。
zygote进程启动时只加载framework-res.apk的部分系统资源,而不是全部资源。将加载的资源在Framework中的res/values/arrays.xml中定义,例如Leaner等布局资源。
对于那些没有“预加载”的系统资源,它们不被视为缓冲到静态列表变量中。在这种情况下,如果多个应用程序进程需要非预加载的资源,它们将在各自的进程中维护。资源缓冲区。
事实上,App在创建Resources对象时,会在其中添加framework-res.apk,这样就可以访问未预加载的资源。
AssetManager对象的创建过程
从前面的分析可以看出,Android系统中实际管理资源的是AssetManager类。每个Resources 对象都与一个AssetManager 对象关联,Resources 将资源上的大部分操作委托给AssetManager。
另外,还会有一个native层的AssetManager对象与java层的AssetManager对象对应,而native层的AssetManager对象的内存地址存储在java层的AssetManager.mObject中。因此,在Java层AssetManager的jni方法中,可以快速找到其对应的native层的AssetManager对象。
普通应用创建AssetManager对象的过程如下:
创建AssetManager对象时,默认会通过addAssetPath()方法将system/framework/framework-res.apk添加到native层的AssetManager对象中。
AssetManager 类有两个构造函数:
一种是App创建Resources对象时使用的公共类型构造函数:
公共AssetManager() {
同步(这个){
如果(DEBUG_REFS){
mNumRefs=0;
incRefsLocked(this.hashCode());
}
初始化(假);
if (localLOGV) Log.v(TAG, "新资产管理器: " + this);
确保系统资产();
}
}一个是创建管理预加载系统资源的资源对象时使用的私有类型构造函数:
私有AssetManager(布尔isSystem){
如果(DEBUG_REFS){
同步(这个){
mNumRefs=0;
incRefsLocked(this.hashCode());
}
}
初始化(真);
if (localLOGV) Log.v(TAG, "新资产管理器: " + this);
}构造方法中会调用init()方法,该方法是native方法:
源代码路径:
框架/base/core/jni/android_util_AssetManager.cpp
Frameworks/base/libs/androidfw/AssetManager.cppstatic void android_content_AssetManager_init(JNIEnv* env, jobject clazz, jboolean isSystem)
{
如果(是系统){
验证系统Idmaps();
}
AssetManager* am=new AssetManager();
如果(am==NUL
L) { jniThrowException(env, "java/lang/OutOfMemoryError", ""); return; } am->addDefaultAssets(); ALOGV("Created AssetManager %p for Java object %pn", am, clazz); env->SetLongField(clazz, gAssetManagerOffsets.mObject, reinterpret_cast(am)); }在init()方法中创建一个native层中的AssetManager对象. 其中addDefaultAssets()方法将system/framework/framework-res.apk通过addAssetPath()方法加入到native层的AssetManager对象中. bool AssetManager::addDefaultAssets() { const char* root = getenv("ANDROID_ROOT"); LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_ROOT not set"); String8 path(root); // framework/framework-res.apk path.appendPath(kSystemAssets); return addAssetPath(path, NULL); }addAssetPath()方法其实很简单主要是将要加入的资源路径加入到 AssetManager类的成员mAssetPaths中,当mAssetPaths中包含这个资源路径时,不会再次加入。也就是说同一种资源是不会被重复加载的。 通过以上代码可知在创建一个Java层的AssetManager对象时,会创建一个native层的AssetManager对象,并把system/framework/framework-res.apk加入到资源路径集合mAssetPaths中去。 而且在前面介绍getTopLevelResources()时,也可以看到,当创建AssetManager对象之后,还会把app的资源路径,也就是apk路径通过ddAssetPath()方法加入到native层的AssetManager对象的mAssetPaths中去。 但是到这里为止,却还没有发现android对resources.arsc有任何操作,不要着急,继续往下看。 在看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来创建字符串资源池。 AssetManager类的成员函数ensureStringBlocks首先检查成员变量mStringBlocks的值是否等于null。如果等于null的话,那么就说明当前应用程序使用的资源表中的资源项值字符串资源池还没有读取出来,这时候就会调用另外一个成员函数makeStringBlocks来进行读取。 整个过程大致过程如下所示: 在上图中第九步中就会处理mAssetPaths路径中apk内的resources.arsc,并把结果缓存起来。其中ResTable类负责资源管理框架中加载resources.arsc,一个ResTable可以管理app中所有的resources.arsc。 先来看java层AssetManager中的ensureStringBlocks()方法: final void ensureStringBlocks() { if (mStringBlocks == null) { synchronized (this) { if (mStringBlocks == null) { makeStringBlocks(sSystem.mStringBlocks); } } } }其中sSystem是AssetManager中的一个静态属性成员: static AssetManager sSystem = null; private StringBlock mStringBlocks[] = null;前面介绍了,zygote启动的时候,会初始化该变量,该变量指向的AssetManager对象是用来管理系统资源的。而且mStringBlocks会被初始化为系统资源中字符串值池的个数。因为系统资源为framework-res.apk,其内部只有一个resources.arsc,所以只有一个字符串资源值池。StringBlocks个数也可以理解为加载的resources.arsc的个数。 final void makeStringBlocks(StringBlock[] seed) { // 系统预加载的resources.arsc的数量 final int seedNum = (seed != null) ? seed.length : 0; // 这是个jni方法,该方法很重要 // 该方法中回去打开前面加入到native层AssetManager.mAssetPaths中的apk中的resources.arsc // 至少返回2,系统资源+app自己的资源 的resources.arsc // 返回的个数包含了seedNum final int num = getStringBlockCount(); mStringBlocks = new StringBlock[num]; if (localLOGV) Log.v(TAG, "Making string blocks for " + this + ": " + num); for (int i=0; i这里要重点分析getStringBlockCount()和getNativeStringBlock()这两个jni方法。 首先分析getStringBlockCount(): static jint android_content_AssetManager_getStringBlockCount(JNIEnv* env, jobject clazz) { // 得到与java层AssetManager对象对应的natvie层AssetManager对象 AssetManager* am = assetManagerForJavaObject(env, clazz); if (am == NULL) { return 0; } return am->getResources().getTableCount(); }代码很简单,调用natvie层AssetManager的getResources()方法: const ResTable& AssetManager::getResources(bool required) const { const ResTable* rt = getResTable(required); return *rt; }先看看这个ResTable类中的属性成员:关于Android 6.0时代:深入解析应用资源管理对象创建策略,的介绍到此结束,希望对大家有所帮助。
【Android 6.0时代:深入解析应用资源管理对象创建策略】相关文章:
用户评论
好耶! 有关于Andriod开发的新知识点可以学习
有16位网友表示赞同!
这个版本的 Android 对 App 的资源管理有哪些变化啊?
有10位网友表示赞同!
看标题感觉是讲用代码新建资源对象的办法吧。
有20位网友表示赞同!
现在移动应用越来越复杂,高效的资源管理确实很关键。
有18位网友表示赞同!
学习了这个知识点以后,可以开发更流畅、更节省内存的游戏吗?
有16位网友表示赞同!
Android 6.0 不太常见了,能详细说说为什么要学习这个版本的内容吗?
有12位网友表示赞同!
之前一直以为 Android 的资源管理比较简单,现在看来我还需要深入了解一下啊。
有20位网友表示赞同!
我对 app 资源优化很感兴趣,这个教程应该能给我带来帮助!
有19位网友表示赞同!
希望能详细讲一讲不同的资源的类型和创建方式。
有13位网友表示赞同!
这种方法能不能提高 App 的运行速度?
有8位网友表示赞同!
感觉这个标题很有用啊,以后开发 app 可以参考一下。
有18位网友表示赞同!
希望教程能提供一些具体的代码示例,方便实践学习。
有10位网友表示赞同!
学习了这款 6.0 版本的资源管理技术,对更新版本也能起到一定的帮助吧?
有7位网友表示赞同!
安卓 App 开发总是需要不断的学习新的知识技能来跟上时代的步伐啊。
有8位网友表示赞同!
能不能用通俗易懂的话讲解一下这些复杂的概念呢?
有19位网友表示赞同!
我想做个有创意的 Android 应用,这个教程能帮我实现吗?
有5位网友表示赞同!
看来Android开发是一个非常深奥的技术领域,需要不断的学习和完善。
有18位网友表示赞同!
期待看到具体的案例分析,更加清晰地理解资源管理对象创建的方法。
有11位网友表示赞同!
感谢分享这么有用的安卓知识!
有12位网友表示赞同!