如何进行资源的热修复

本文探讨了Android应用中资源的热修复问题,包括资源替换的可能性和如何避免资源ID冲突。通过创建独立的AssetManager来加载Patch APK,文章详细解释了资源加载流程,涉及到AssetManager的初始化、资源索引构建和资源加载。文中还提供了Demo,展示了如何在运行时修复资源,包括反射获取目标组件和使用DexClassLoader加载补丁APK。
摘要由CSDN通过智能技术生成

热修复中可能会涉及到资源文件的替换,有两个问题:
一、到底能不能替换?这里是将主APP中的资源替换成Patch apk中的资源,可以实现么?
二、怎么替换,会不会有资源id冲突的问题?

本文会来研究这两个问题,并给出Demo来展现如何替换资源。

加载patch apk时,和加载插件类似,可以参考我之前的两篇文章:

http://blog.csdn.net/dingjikerbo/article/details/47757511
http://blog.csdn.net/dingjikerbo/article/details/47783411

如下

DexClassLoader dexClassLoader = createDexClassLoader(pluginId, packageId, packagePath);
AssetManager assetManager = createAssetManager(packagePath);
Resources resources = createResources(assetManager);

private AssetManager createAssetManager(String dexPath) {
    try {
        AssetManager assetManager = AssetManager.class.newInstance();
        Method addAssetPath = assetManager.getClass().getMethod(
                "addAssetPath", String.class);
        addAssetPath.invoke(assetManager, dexPath);
        return assetManager;
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }

}

private Resources createResources(AssetManager assetManager) {
    Resources superRes = mContext.getResources();
    Resources resources = new Resources(assetManager,
            superRes.getDisplayMetrics(), superRes.getConfiguration());
    return resources;
}

可见这里专门为Patch apk创建了独立的AssetManager,并将这个apk的路径添加到AssetManager的AssetPath路径集合中。

这里有两个问题:
1. 主APP和Patch加载资源用的是不同的AssetManager和Resources,如果主APP和Patch Apk中的资源id有冲突,那么加载资源时会不会串了?
2. 同一个AssetManager下可以添加多个资源包,假如这些资源包之间存在相同的资源id,加载资源时会不会串呢?

要研究这两个问题,我们需要先了解一下AssetManager的实现以及资源加载机制。

每个AssetManager下可以添加多个资源包,而且为了查找资源时更快,一定会先解析这些资源包,然后生成索引便于之后的资源加载。这个索引的key也就是资源的id应该包含资源的package id,资源type id以及资源的entry id。为保证key的唯一性,这三者不能同时冲突。除了资源type id通常是固定的之外,资源的package id 和entry id应该是打包apk的时候生成的,所以我们重点要确认一下打包apk时是如何生成资源的package id和entry id的。不过我们可以设想一下,假如两个资源包中的资源非常多,多到可以填满所有的资源entry id,那么就一定会有entry id的冲突,那么为保证key的唯一性的最后一道关卡就只剩下资源的package id了。然而实践中发现,编译时生成的R.java中所有的资源都是以0x7F开头,这个就是package id了,可见打包时默认的package id都是0x7F,除非修改aapt为每个包指定不同的package id,否则很有可能资源冲突。

当然如果采用的是不同的AssetManager去加载资源包里的资源就不会有这种问题了,因为索引表都不一样,即便key一样也没有关系。

接下来,我们将过一下AssetManager的代码,这里只是为了了解资源加载的大致流程,所以会略去一些细节,如果想更深入了解,可以参考如下几篇文章:
Android应用程序资源的编译和打包过程分析
Android应用程序资源管理器(Asset Manager)的创建过程分析
Android应用程序资源的查找过程分析

public AssetManager() {
    init();
}

private native final void init();

可见,直接调用了native的init函数进行初始化

static void android_content_AssetManager_init(JNIEnv* env, jobject clazz)
{
    AssetManager* am = new AssetManager();

    am->addDefaultAssets();

    env->SetIntField(clazz, gAssetManagerOffsets.mObject, (jint)am);
}

这个init主要做了三件事,首先在Native层New了一个AssetManager,然后向其中addDefaultAssets,最后将这个AssetManager的指针设置到Java中的AssetManager类的mObject。我们先来看看AssetManager 的构造函数。

class AssetManager : public AAssetManager {
public:
    ..........
    Vector<asset_path> mAssetPaths;
    mutable ResTable* mResources;
    ResTable_config* mConfig;

    CacheMode       mCacheMode;         // is the cache enabled?
    SortedVector<AssetDir::FileInfo> mCache;
    AssetManager(CacheMode cacheMode = CACHE_OFF);
    ..........
}

这个AssetManager中比较重要的是mAssetPaths和mResources。mAssetPaths中保存的是这个AssetManager维护的所有资源apk的路径。mResources就是一个Resource Table,相当于资源的索引表。再来看addDefaultAssets:

bool AssetManager::addDefaultAssets()
{
    const char* root = getenv("ANDROID_ROOT");

    String8 path(root);
    path.appendPath(kSystemAssets);

    return addAssetPath(path, NULL);
}

static const char* kSystemAssets = "framework/framework-res.apk";

可见这里所谓的defaultAssets就是${ANDROID_ROOT}/framework/framework-res.apk,是系统资源路径。

接下来重点讲解这个addAssetPath函数,在分析这个函数代码之前,我们先来看看那些地方调用了它:

一、addDefaultAssets时调用了addAssetPath(path, NULL)

二、Java层的AssetManager调用addAssetPath时调到了native层的android_content_AssetManager_addAssetPath如下

static jint android_content_AssetManager_addAssetPath(JNIEnv* env, jobject clazz,
                                                       jstring path)
{
    ScopedUtfChars path8(env, path);

    AssetManager* am = assetManagerForJavaObject(env, clazz);

    void* cookie;
    bool res = am->addAssetPath(String8(path8.c_str()), &cookie);

    return (res) ? (jint)cookie : 0;
}

这两个地方调用方式略有不同,前者第二个参数传NULL,后者添加用户自定义的path时传入了&cookie,是个void *,看来要返回一个指针,至于这个指针是做什么的,我们只能在看了addAssetPath代码后才能知道。

bool AssetManager::addAssetPath(const String8& path, void** cookie) {
    for (size_t i=0;
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
修复(Hotfix)是一种在应用程序运行时修复Bug或添加新功能的技术,而不需要重新发布整个应用程序。资源修复是指在应用程序中修复或更新资源文件,如图片、布局文件等。 修复的原理主要是通过动态加载和替换代码来实现。在应用程序中,通常会有一个基础的代码框架,我们将其称为主App。当需要修复Bug或添加新功能时,开发者会将修复的代码打包成一个补丁(Patch)文件。在运行时,主App会下载并加载这个补丁文件,然后使用动态加载技术将补丁中的代码合并到主App中,从而实现Bug修复或功能更新。 对于资源修复,原理类似。当需要修复或更新资源文件时,开发者会将更新后的资源文件打包成一个资源补丁文件。在应用程序运行时,主App会下载并加载这个资源补丁文件,然后使用动态加载技术将补丁中的资源文件与主App中的对应资源进行替换。 至于资源冲突的问题,确实可能存在。资源冲突可能发生在不同版本的资源文件之间,例如主App中使用的资源与补丁中的资源存在命名冲突。为了避免冲突,开发者可以采用一些策略,如给资源文件添加前缀或命名空间,以确保资源文件的唯一性。另外,在进行资源修复时,开发者也需要注意资源文件的版本管理,确保补丁中的资源与主App中的资源版本兼容。 总之,修复的原理是通过动态加载和替换代码或资源文件来实现Bug修复或功能更新。资源冲突可能存在,但可以通过合理的命名和版本管理策略来避免。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风语

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值