Android更新資原始檔淺思考
最近在看 Android_hotfix.pdf" target="_blank" rel="nofollow,noindex">《深入探索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
的構造中的;
文章到這裡就全部講述完啦,若有其他需要交流的可以留言哦~!~!
想閱讀作者的更多文章,可以檢視我個人部落格 和公共號:
