1. 程式人生 > >Android更新資原始檔淺思考

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 的溝通方法摘出來放大家看看。我們在初始化系統的資源時呼叫了 AssetManagermakeStringBlocks 方法,最後呼叫了 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層 AssetManagerResTable 提供;
  • ResTable 構造是遍歷 mAssetPaths 中的資源路徑;
  • AndroidL addAssetPath 方法可以直接將資源路新增到 ResTable 中使用;
  • AndroidL之前 addAssetPath 方法只是將資源路徑新增到了mAssetPaths 中;
  • ResTable 構造包含在Java層 AssetManager 的構造中的;

文章到這裡就全部講述完啦,若有其他需要交流的可以留言哦

想閱讀作者的更多文章,可以檢視我 個人部落格 和公共號:
振興書城