内容简介:最近在看这个问题是使我写下这边文章的原因,下边我们带着问题来找答案~!~!~!这个问题的解释网上答案比较少,在滴滴的插件化框架相关文章
最近在看 《深入探索Android热修复技术原理7.3Q.pdf》 时,遇到一个之前没有注意过的问题:关于资源修更新的Android的版本兼容?作为 程序员 我们需要非常严谨的思路,是什么导致了资源的修复更新需要做版本兼容?
这个问题是使我写下这边文章的原因,下边我们带着问题来找答案~!~!~!
这个问题的解释网上答案比较少,在滴滴的插件化框架相关文章 VirtualAPK 资源篇 和 阿里云移动热修复(Sophix) 相关文章
Android热修复升级探索——资源更新之新思路 中 都有一句概括性质的话语:
AndroidL之后资源在初始化之后可以加载,而在AndroidL之前是不可以的。因为在Android KK及以下版本,addAssetPath只是把补丁包的路径添加到了mAssetPath中,而真正解析的资源包的逻辑是在app第一次执行AssetManager::getResTable的时候。
FTSC
为了比较完整的对前面提出的问题做解答,下边我在老罗写的 Android应用程序资源管理器(Asset Manager)的创建过程分析 这篇文章的基础上分析。
跟踪getResourceText
我们都知道在Android中获取资源调用的 Resources.getText(int id)
内部都是在调用 AssetManager.getResourceText(id)
真正对资源进行管理的是 AssetManager
。
下边我们就以 getResourceText
方法的调用顺序引子查找:
public final class AssetManager { ...... /*package*/ static AssetManager sSystem = null; private native final void init(boolean isSystem); ...... //构造方法 public AssetManager() { synchronized (this) { ...... init(false); ...... //每个AssetManager实例都会初始化系统的资源 ensureSystemAssets(); } } private static void ensureSystemAssets() { synchronized (sSync) { if (sSystem == null) { AssetManager system = new AssetManager(true); system.makeStringBlocks(false); sSystem = system; } } } ...... //添加资源路径 public final int addAssetPath(String path) { synchronized (this) { int res = addAssetPathNative(path); if (mStringBlocks != null) { makeStringBlocks(mStringBlocks); } return res; } } private native final int addAssetPathNative(String path); /*package*/ final CharSequence getResourceText(int ident) { synchronized (this) { TypedValue tmpValue = mValue; int block = loadResourceValue(ident, (short) 0, tmpValue, true); if (block >= 0) { if (tmpValue.type == TypedValue.TYPE_STRING) { return mStringBlocks[block].get(tmpValue.data); } return tmpValue.coerceToString(); } } return null; } //查找并加载资源 private native final int loadResourceValue(int ident, short density, TypedValue outValue, boolean resolve); }
AssetManager.init方法的C层实现:
//frameworks/base/core/jni/android_util_AssetManager.cpp static void android_content_AssetManager_init(JNIEnv* env, jobject clazz, jboolean isSystem) { if (isSystem) { verifySystemIdmaps(); } //构造C++层的AssetManager的对象 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)); }
AssetManager.loadResourceValue方法的C层实现:
//frameworks/base/core/jni/android_util_AssetManager.cpp static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject clazz, jint ident, jshort density, jobject outValue, jboolean resolve) { /***部分代码省略***/ //这行代码最重要,通过获取C层的AssetManager的成员变量ResTable来获取资源 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 (block >= 0) { return copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config); } return static_cast<jint>(block); } //getResources实际获取的是ResTable const ResTable& AssetManager::getResources(bool required) const { const ResTable* rt = getResTable(required); return *rt; }
我们可以看到获取资源实际是在操作 C层的AssetManager的成员变量ResTable
。如果没有将资源加入到 ResTable
那么是无法获取到的。下边我们分别看看AndroidL和AndroidL之前 addAssetPath
方法的实现。
ResTable的构造
//frameworks/base/libs/androidfw/AssetManager.cpp const ResTable* AssetManager::getResTable(bool required) const { ResTable* rt = mResources; if (rt) { return rt; } /***部分代码省略***/ const size_t N = mAssetPaths.size(); for (size_t i=0; i<N; i++) { //遍历mAssetPaths将其ResTable中 /***部分代码省略***/ } /***部分代码省略***/ return rt; }
如果已经创建那么直接返回,如果没有那么将 mAssetPaths
集合中的资源路径全部添加到 ResTable
中后返回。下边我们继续看看资源的路径是怎么被插入到 mAssetPaths
中的,或者是怎么被直接插入到 ResTable
中的。
addAssetPath的差异
//frameworks/base/libs/androidfw/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); String8 pathCM(root); pathCM.appendPath(kCMSDKAssets); return addAssetPath(path, NULL) & addAssetPath(pathCM, NULL); } bool AssetManager::addAssetPath(const String8& path, int32_t* cookie) { AutoMutex _l(mLock); asset_path ap; String8 realPath(path); if (kAppZipName) { realPath.appendPath(kAppZipName); } /***部分代码省略***/ //将path添加到mAssetPaths中 mAssetPaths.add(ap); if (mResources != NULL) { size_t index = mAssetPaths.size() - 1; //添加到ResTable中 appendPathToResTable(ap, &index); } // new paths are always added at the end if (cookie) { *cookie = static_cast<int32_t>(mAssetPaths.size()); } /***部分代码省略***/ return true; }
以上是Android5.1的源码,我们发现无论是否初始化过 ResTable
我们都可以直接调用 addAssetPath
是可以添加资源。
下边我们看看Android4.4的源码:
//frameworks/base/libs/androidfw/AssetManager.cpp bool AssetManager::addAssetPath(const String8& path, void** cookie) { AutoMutex _l(mLock); asset_path ap; String8 realPath(path); if (kAppZipName) { realPath.appendPath(kAppZipName); } /***部分代码省略***/ //将path添加到mAssetPaths中 mAssetPaths.add(ap); // new paths are always added at the end if (cookie) { *cookie = (void*)mAssetPaths.size(); } /***部分代码省略***/ return true; }
我们发现 addAssetPath
只是将 path
添加到了 mAssetPaths
里面,但是并没法添加到 ResTable
中。
如果AndroidL之前调用 addAssetPath
没有初始化 ResTable
那么这次添加就是有效的,否则添加无效。下边我们接着看看 ResTable
的初始化时机。
ResTable的初始化时机
public final class AssetManager { /***部分代码省略***/ //构造方法 public AssetManager() { synchronized (this) { ...... init(false); ...... //每个AssetManager实例都会初始化系统的资源 ensureSystemAssets(); } } private static void ensureSystemAssets() { synchronized (sSync) { if (sSystem == null) { AssetManager system = new AssetManager(true); //初始化系统的字符串块 system.makeStringBlocks(false); sSystem = system; } } } /*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<num; i++) { if (i < seedNum) { mStringBlocks[i] = seed[i]; } else { mStringBlocks[i] = new StringBlock(getNativeStringBlock(i), true); } } } private native final int getStringBlockCount(); /***部分代码省略***/ }
避免大家往上翻看,我又将 AssetManager
的沟通方法摘出来放大家看看。我们在初始化系统的资源时调用了 AssetManager
的 makeStringBlocks
方法,最后调用了 C层
的 getStringBlockCount
方法。
AssetManager.getStringBlockCount的C层的实现:
//frameworks/base/core/jni/android_util_AssetManager.cpp static jint android_content_AssetManager_getStringBlockCount(JNIEnv* env, jobject clazz) { AssetManager* am = assetManagerForJavaObject(env, clazz); if (am == NULL) { return 0; } //初次调用资源,进行初始化ResTable return am->getResources().getTableCount(); }
好了,我们找到了 ResTable
的时机,它是发生在 java 层的 AssetManager
构造的时候。
结论
- Android中进行资源管理的是
AssetManager
; - 资源由C层
AssetManager
的ResTable
提供; -
ResTable
构造是遍历mAssetPaths
中的资源路径; - AndroidL
addAssetPath
方法可以直接将资源路添加到ResTable
中使用; - AndroidL之前
addAssetPath
方法只是将资源路径添加到了mAssetPaths
中; -
ResTable
构造包含在Java层AssetManager
的构造中的;
文章到这里就全部讲述完啦,若有其他需要交流的可以留言哦~!~!
想阅读作者的更多文章,可以查看我个人博客 和公共号:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Growth Hack 這樣做
Xdite / PCuSER電腦人文化 / 2016-5-7 / 300.00台幣
◎具體教你在預算有限的情況之下,把成長做出來的可行與必要方法! ◎帶動台灣成長駭客話題的專業講師,親授讓產品突破80分的成長秘笈 @這本書要給誰看? 1. 創業者、個人品牌經營者,想要提高自己服務轉換率的人。 2. 空有產品,但是賣不出去,花了錢投廣告卻效果低落的人。 @這本書有什麼不一樣? 1.全球最重要的趨勢,台灣最知名的 Growth Hack 講師 Xd......一起来看看 《Growth Hack 這樣做》 这本书的介绍吧!