前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android更新资源文件浅思考

Android更新资源文件浅思考

作者头像
静默加载
发布2020-05-29 11:01:20
1.1K0
发布2020-05-29 11:01:20
举报
文章被收录于专栏:振兴的Android修炼手册

前言

最近在看 《深入探索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 方法的调用顺序引子查找:

代码语言:javascript
复制
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层实现:

代码语言:javascript
复制
//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层实现:

代码语言:javascript
复制
//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的构造

代码语言:javascript
复制
//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的差异

代码语言:javascript
复制
//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的源码:

代码语言:javascript
复制
//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的初始化时机

代码语言:javascript
复制
public final class AssetManager {
    //静态变量
    static AssetManager sSystem = null;
    /***部分代码省略***/
    //构造方法
    public AssetManager() {
        synchronized (this) {
            ......
            init(false);
            ......
            //每个AssetManager实例都会初始化系统的资源
            ensureSystemAssets();
        }
    }
    private static void ensureSystemAssets() {
        synchronized (sSync) {
            //当sSystem不为空时,所以下边这块代码只会走一次
            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层的实现:

代码语言:javascript
复制
//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 构造的时候。

看代码注释的朋友,都看到了重要的部分。并非所有的 AssetManager 的构造都会调用 ResTable ,只有第一个 AssetManager 对象产生的时候会调用,所以如果我们想要添加自己资源的时候可以自己创建 AssetManager 然后调用 addAssetPath 这个时候无论是在 AndroidL 之前还是之后都没有问题。

结论

以下结论需要大家结合文章来看,因为涉及到的 Android 不同版本的代码:

  • Android中进行资源管理的是 AssetManager
  • 资源由C层 AssetManagerResTable 提供;
  • ResTable 构造是遍历 mAssetPaths 中的资源路径;
  • AndroidL addAssetPath 方法可以直接将资源路添加到 ResTable 中使用;
  • AndroidL之前 addAssetPath 方法只是将资源路径添加到了mAssetPaths 中;
  • ResTable 构造包含在Java层 AssetManager 的构造中的(PS:只有第一个AssetManager 的构造会执行);
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • FTSC
    • 跟踪getResourceText
      • ResTable的构造
        • addAssetPath的差异
          • ResTable的初始化时机
          • 结论
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档