Android更新資原始檔淺思考
前言
最近在看 《深入探索Android熱修復技術原理7.3Q.pdf》 時,遇到一個之前沒有注意過的問題:關於資源修更新的Android的版本相容?作為程式設計師我們需要非常嚴謹的思路,是什麼導致了資源的修復更新需要做版本相容?
這個問題是使我寫下這邊文章的原因,下邊我們帶著問題來找答案!!~!
AndroidL之後資源在初始化之後可以載入,而在AndroidL之前是不可以的。因為在Android KK及以下版本,addAssetPath只是把補丁包的路徑新增到了mAssetPath中,而真正解析的資源包的邏輯是在app第一次執行AssetManager::getResTable的時候。
FTSC
跟蹤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
的構造中的;
文章到這裡就全部講述完啦,若有其他需要交流的可以留言哦!!
想閱讀作者的更多文章,可以檢視我 個人部落格 和公共號: