系统资源的预加载过程

2017-01-10 10:08:47来源:作者:StriveG Blog人点击

第七城市

在Zygote进程那篇文章中,提到过,在初始化的时候会预加载系统资源,这样,应用进程在fork了之后,不需要通过加载过程,就可以直接使用这些资源,那么,今天就来看下,这个过程是怎么样的。

ZygoteInit#preloadResources private static void preloadResources() { final VMRuntime runtime = VMRuntime.getRuntime(); try { mResources = Resources.getSystem(); mResources.startPreloading(); if (PRELOAD_RESOURCES) { Log.i(TAG, "Preloading resources..."); long startTime = SystemClock.uptimeMillis(); TypedArray ar = mResources.obtainTypedArray( com.android.internal.R.array.preloaded_drawables); int N = preloadDrawables(runtime, ar); ar.recycle(); Log.i(TAG, "...preloaded " + N + " resources in " + (SystemClock.uptimeMillis()-startTime) + "ms."); startTime = SystemClock.uptimeMillis(); ar = mResources.obtainTypedArray( com.android.internal.R.array.preloaded_color_state_lists); N = preloadColorStateLists(runtime, ar); ar.recycle(); Log.i(TAG, "...preloaded " + N + " resources in " + (SystemClock.uptimeMillis()-startTime) + "ms."); } mResources.finishPreloading(); } catch (RuntimeException e) { Log.w(TAG, "Failure preloading resources", e); }}

上面的过程分为三步

得到Resources对象 得到TypedArray对象 preloadDrawables,preloadColorStateLists 加载资源

现在,按照上面的三部分来学习下。

Resources#getSystem

Resources代码资源,提供了许多方法让我们获取,这里获取的是经过映射的资源,resources.arsc。

public static Resources getSystem() { synchronized (sSync) { Resources ret = mSystem; if (ret == null) { ret = new Resources(); mSystem = ret; } return ret; }}

在这里初始化了一个Resources对象,并且赋值给mSystem变量,那么我们现在看下Resources的构造方法。

private Resources() { mAssets = AssetManager.getSystem(); // NOTE: Intentionally leaving this uninitialized (all values set // to zero), so that anyone who tries to do something that requires // metrics will get a very wrong value. mConfiguration.setToDefaults(); mMetrics.setToDefaults(); updateConfiguration(null, null); mAssets.ensureStringBlocks();}

在初始化方法中,通过AssetManager.getSystem获取一个AssetManager对象,这个是用来访问原始资源的(assets目录)。并且,将Configuration和DisplayMetrics都设置默认,更新配置,初始化StringBlocks。那么,我们来看下AssetManager.getSystem。这个方法中调用ensureSystemAssets,ensureSystemAssets代码如下:

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

可以看到,这里初始化了一个AssetManager,好吧,接着看AssetManager的构造函数。

private AssetManager(boolean isSystem) { if (DEBUG_REFS) { synchronized (this) { mNumRefs = 0; incRefsLocked(this.hashCode()); } } init(true); if (localLOGV) Log.v(TAG, "New asset manager: " + this);}

这里调用init去初始化,这是个native函数,实现在android_util_AssetManager.cpp中,对应的方法为android_content_AssetManager_init

android_content_AssetManager_init 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<jlong>(am));}

从代码中来看,分为四步

验证idmaps 生成c++的AssetManager对象 添加默认的assets 设置java层AssetManager的mObject为c++的AssetManager地址。

我们看添加默认的assets那一步。

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为system static const char* kSystemAssets = “framework/framework-res.apk”;

然后调用addAssetPath两个参数的方法,将framework-res.apk加进去。

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<mAssetPaths.size(); i++) { if (mAssetPaths[i].path == ap.path) { if (cookie) { *cookie = static_cast<int32_t>(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<AssetManager*>(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<int32_t>(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;}

asset_path结构体存储文件路径、文件类型等,结构体定义如下

struct asset_path{ asset_path() : path(""), type(kFileTypeRegular), idmap(""), isSystemOverlay(false) {} String8 path; FileType type; String8 idmap; bool isSystemOverlay;};

kAppZipName一般为null,static const char* kAppZipName = NULL; //“classes.jar”;

如果已经在mAssetPaths,就反悔 如果路径下没有AndroidManifest.xml文件,返回false 添加到mAssetPaths中 appendPathToResTable将资源进行解析添加 AssetManager::appendPathToResTable bool AssetManager::appendPathToResTable(const asset_path& ap) const { // skip those ap's that correspond to system overlays if (ap.isSystemOverlay) { return true; } Asset* ass = NULL; ResTable* sharedRes = NULL; bool shared = true; bool onlyEmptyResources = true; MY_TRACE_BEGIN(ap.path.string()); Asset* idmap = openIdmapLocked(ap); size_t nextEntryIdx = mResources->getTableCount(); ALOGV("Looking for resource asset in '%s'/n", ap.path.string()); if (ap.type != kFileTypeDirectory) { if (nextEntryIdx == 0) { // The first item is typically the framework resources, // which we want to avoid parsing every time. sharedRes = const_cast<AssetManager*>(this)-> mZipSet.getZipResourceTable(ap.path); if (sharedRes != NULL) { // skip ahead the number of system overlay packages preloaded nextEntryIdx = sharedRes->getTableCount(); } } if (sharedRes == NULL) { ass = const_cast<AssetManager*>(this)-> mZipSet.getZipResourceTableAsset(ap.path); if (ass == NULL) { ALOGV("loading resource table %s/n", ap.path.string()); ass = const_cast<AssetManager*>(this)-> openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, ap); if (ass != NULL && ass != kExcludedAsset) { ass = const_cast<AssetManager*>(this)-> mZipSet.setZipResourceTableAsset(ap.path, ass); } } if (nextEntryIdx == 0 && ass != NULL) { // If this is the first resource table in the asset // manager, then we are going to cache it so that we // can quickly copy it out for others. ALOGV("Creating shared resources for %s", ap.path.string()); sharedRes = new ResTable(); sharedRes->add(ass, idmap, nextEntryIdx + 1, false);#ifdef HAVE_ANDROID_OS const char* data = getenv("ANDROID_DATA"); LOG_ALWAYS_FATAL_IF(data == NULL, "ANDROID_DATA not set"); String8 overlaysListPath(data); overlaysListPath.appendPath(kResourceCache); overlaysListPath.appendPath("overlays.list"); addSystemOverlays(overlaysListPath.string(), ap.path, sharedRes, nextEntryIdx);#endif sharedRes = const_cast<AssetManager*>(this)-> mZipSet.setZipResourceTable(ap.path, sharedRes); } } } else { ALOGV("loading resource table %s/n", ap.path.string()); ass = const_cast<AssetManager*>(this)-> openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, ap); shared = false; } if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) { ALOGV("Installing resource asset %p in to table %p/n", ass, mResources); if (sharedRes != NULL) { ALOGV("Copying existing resources for %s", ap.path.string()); mResources->add(sharedRes); } else { ALOGV("Parsing resources for %s", ap.path.string()); mResources->add(ass, idmap, nextEntryIdx + 1, !shared); } onlyEmptyResources = false; if (!shared) { delete ass; } } else { ALOGV("Installing empty resources in to table %p/n", mResources); mResources->addEmpty(nextEntryIdx + 1); } if (idmap != NULL) { delete idmap; } MY_TRACE_END(); return onlyEmptyResources;} 通过openIdmapLocked解析资源包中的idmap表 ap.type != kFileTypeDirectory,不是目录,就是资源包,如果nextEntryIdx为0,则为framework.apk资源包,解析并保存在sharedRes中,这个是共享的资源。 sharedRes == NULL 表示为应用资源包,这解析保存在ass中, nextEntryIdx == 0 && ass != NULL,在zygote进程第一次调用才成立,也就是预加载资源,这个时候把framework.apk的资源索引表创建起来,并添加到mZipSet中 如果是目录,就创建resources.arsc的asset对象,这个不是共享的。 如果存在sharedRes,就保存到mResources中,否则,这是应用的资源,则把应用的资源加入到mResources中。

上面的比较乱,总结一下。简单来说就是这样的,就是对资源包进行解析,并加入到mResources中,如果是framework.apk,就是共享的,否则就是应用的资源包,不共享。

TypedArray TypedArray ar = mResources.obtainTypedArray( com.android.internal.R.array.preloaded_drawables);

mResources.obtainTypedArray的代码如下:

public TypedArray obtainTypedArray(@ArrayRes int id) throws NotFoundException { int len = mAssets.getArraySize(id); if (len < 0) { throw new NotFoundException("Array resource ID #0x" + Integer.toHexString(id)); } TypedArray array = TypedArray.obtain(this, len); array.mLength = mAssets.retrieveArray(id, array.mData); array.mIndices[0] = 0; return array;} 先获取id对应的数组长度 然后根据长度生成TypedArray

最后mAssets.retrieveArray赋值,将资源对应的id写入到java层array.mData中。写入部分代码如下

dest[STYLE_TYPE] = value.dataType; dest[STYLE_DATA] = value.data; dest[STYLE_ASSET_COOKIE] = block != kXmlBlock ? static_cast<jint>(res.getTableCookie(block)) : -1; dest[STYLE_RESOURCE_ID] = resid; dest[STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags; dest[STYLE_DENSITY] = config.density; getArraySize获取资源id对应的数组长度

这是个native方法,对应的实现在android_util_AssetManager.cpp的android_content_AssetManager_getArraySize方法中,代码如下:

static jint android_content_AssetManager_getArraySize(JNIEnv* env, jobject clazz, jint id){ AssetManager* am = assetManagerForJavaObject(env, clazz); if (am == NULL) { return 0; } const ResTable& res(am->getResources()); res.lock(); const ResTable::bag_entry* defStyleEnt = NULL; ssize_t bagOff = res.getBagLocked(id, &defStyleEnt); res.unlock(); return static_cast<jint>(bagOff);} 首先将java对象转换为对应的c++对象,上面有说到过,AssetManager中的mobject字段存的是对应c++中,AssetManager对象的地址,因此,我们很容易做到转换 通过ResTable的getBagLocked方法获取id,对应的数组长度(该方法的实现在ResourceTypes.cpp中),实现太长,看不懂,略过。 preloadDrawables来说明资源的预加载过程 private static int preloadDrawables(VMRuntime runtime, TypedArray ar) { int N = ar.length(); for (int i=0; i<N; i++) { int id = ar.getResourceId(i, 0); if (false) { Log.v(TAG, "Preloading resource #" + Integer.toHexString(id)); } if (id != 0) { if (mResources.getDrawable(id, null) == null) { throw new IllegalArgumentException( "Unable to find preloaded drawable resource #0x" + Integer.toHexString(id) + " (" + ar.getString(i) + ")"); } } } return N;} 首先通过getResourceId获取资源id getDrawable获取资源 getResourceId 获取资源id public int getResourceId(int index, int defValue) { if (mRecycled) { throw new RuntimeException("Cannot make calls to a recycled instance!"); } index *= AssetManager.STYLE_NUM_ENTRIES; final int[] data = mData; if (data[index+AssetManager.STYLE_TYPE] != TypedValue.TYPE_NULL) { final int resid = data[index+AssetManager.STYLE_RESOURCE_ID]; if (resid != 0) { return resid; } } return defValue;} 其中STYLE_NUM_ENTRIES为6, 从赋值id的代码中,知道第四个为id值,因此STYLE_RESOURCE_ID为3 getDrawable public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme) throws NotFoundException { TypedValue value; synchronized (mAccessLock) { value = mTmpValue; if (value == null) { value = new TypedValue(); } else { mTmpValue = null; } getValue(id, value, true); } final Drawable res = loadDrawable(value, id, theme); synchronized (mAccessLock) { if (mTmpValue == null) { mTmpValue = value; } } return res;} getValue查找与id对应的资源,没找到对应的就抛出NotFoundException异常 loadDrawable 去加载

在getValue中,会调用AssetManager的getResourceValue方法,这个是native方法,实现在android_uitl_AssetManager.cpp的android_content_AssetManager_loadResourceValue中

android_content_AssetManager_loadResourceValue static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject clazz, jint ident, jshort density, jobject outValue, jboolean resolve){ if (outValue == NULL) { jniThrowNullPointerException(env, "outValue"); return 0; } AssetManager* am = assetManagerForJavaObject(env, clazz); if (am == NULL) { return 0; } const ResTable& res(am->getResources()); Res_value value; ResTable_config config; uint32_t typeSpecFlags; ssize_t block = res.getResource(ident, &value, false, density, &typeSpecFlags, &config); if (kThrowOnBadId) { if (block == BAD_INDEX) { jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); return 0; } } uint32_t ref = ident; if (resolve) { block = res.resolveReference(&value, block, &ref, &typeSpecFlags, &config); if (kThrowOnBadId) { if (block == BAD_INDEX) { jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); return 0; } } } if (block >= 0) { return copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config); } return static_cast<jint>(block);} 得到ResTable对象 resolveReference查找是否有匹配的资源 如果有,就将数据复制给copyValue这个java层对象

具体的代码就不往下追了。

loadDrawable

经过上面的步骤,资源的信息就保存在了TypedValue中,解析来就是通过loadDrawable去加载了。

Drawable loadDrawable(TypedValue value, int id, Theme theme) throws NotFoundException { if (TRACE_FOR_PRELOAD) { // Log only framework resources if ((id >>> 24) == 0x1) { final String name = getResourceName(id); if (name != null) { Log.d("PreloadDrawable", name); } } } final boolean isColorDrawable; final DrawableCache caches; final long key; if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT && value.type <= TypedValue.TYPE_LAST_COLOR_INT) { isColorDrawable = true; caches = mColorDrawableCache; key = value.data; } else { isColorDrawable = false; caches = mDrawableCache; key = (((long) value.assetCookie) << 32) | value.data; } // First, check whether we have a cached version of this drawable // that was inflated against the specified theme. if (!mPreloading) { final Drawable cachedDrawable = caches.getInstance(key, theme); if (cachedDrawable != null) { return cachedDrawable; } } // Next, check preloaded drawables. These may contain unresolved theme // attributes. final ConstantState cs; if (isColorDrawable) { cs = sPreloadedColorDrawables.get(key); } else { cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key); } Drawable dr; if (cs != null) { dr = cs.newDrawable(this); } else if (isColorDrawable) { dr = new ColorDrawable(value.data); } else { dr = loadDrawableForCookie(value, id, null); } // Determine if the drawable has unresolved theme attributes. If it // does, we'll need to apply a theme and store it in a theme-specific // cache. final boolean canApplyTheme = dr != null && dr.canApplyTheme(); if (canApplyTheme && theme != null) { dr = dr.mutate(); dr.applyTheme(theme); dr.clearMutated(); } // If we were able to obtain a drawable, store it in the appropriate // cache: preload, not themed, null theme, or theme-specific. if (dr != null) { dr.setChangingConfigurations(value.changingConfigurations); cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr); } return dr;} 根据判断是否是ColorDrawable,赋值不同的cache和key 如果不是预加载,就从cache中找,找到返回 从预加载数组中得到key对应的ConstantState 根据cs是否为null以及是不是ColorDrawable,来得到的Drawable 设置主题相关的 进行缓存

而我们预加载资源的主要过程是loadDrawableForCookie。这个方法是从xml或者流里面加载Drawable。

核心代码如下:

if (file.endsWith(".xml")) { final XmlResourceParser rp = loadXmlResourceParser( file, id, value.assetCookie, "drawable"); dr = Drawable.createFromXml(this, rp, theme); rp.close();} else { final InputStream is = mAssets.openNonAsset( value.assetCookie, file, AssetManager.ACCESS_STREAMING); dr = Drawable.createFromResourceStream(this, value, is, file, null); is.close();}

关于Drawable的生成过程,这里就不说了。

占坑 aapt资源打包过程 idmap解析过程openIdmapLocked 其他 最近访客
第七城市

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台