Android插件化的一种简单实现-资源的动态加载

在多年的迭代和升级工作中,组件化项目越来越庞大(几十个模块,近10个第三方播放SDK),直接导致发版困难、方法数超标、工作效率大大降低,质量问题频发等等。项目迫切需要一套方案来解决这些问题。由于我们是自行研发的系统和主板,如果直接使用第三方框架,可能会引起相关的适配问题而不好解决,所以需要实现一套自己的插件化框架,也便于后期进行更多的定制。于是进行了下面粗浅的研究。

项目是影视类项目,引进了很多第三方播放SDK,实际上用户在单次启动时并不会用到这么多的SDK,所以直接加载全部的SDK是很浪费性能的。同时,各个公司的SDK是相互不干扰的,按照以往统一升级的做法,在遇到一家有任何问题的时候只能整体应用升级,非常不方便。且在日常版本迭代中任何一个模块出问题都会直接导致项目延期。。。综合以上情况我们需要做到以下几点:

1、插件的动态加载,使用到再加载,且插件不能安装,不能修改第三方SDK的内容;

2、无差别加载插件,实现在不修改主工程的情况下直接接入新的SDK;

3、插件版本控制和独立升级

4、资源的动态加载

在进行插件化研究的过程中发现,直接简单粗暴的合并插件宿主的dex,因为插件的资源文件并没有被加载到当前虚拟机,所以并不能正常使用插件的资源文件。于是进行了下面粗浅的研究。

一:首先我们设想,是不是跟dex的处理办法一样,直接将插件的资源注入到宿主中就可以正常使用了?

答案肯定是不行的,为什么?这里我们首先要了解下Android使用和打包资源的逻辑,Android在打包资源的时候会产生自己的 resources.arsc,并生成R.java文件,大家可以看一下宿主apk的R.java文件和插件apk的R.java文件,会发现两个apk的资源id都是0x7F开头的,0x7F地址头其实是资源打包工具aapt内写死的,所以不同的apk里面的资源id必定是会存在冲突的,这个时候我们直接合并冲突的资源文件,肯定是不行的,因此我们首先要解决资源冲突的问题,怎么解决呢?这里我们使用比较大众的方案,修改资源打包工具aapt,将插件的资源整体迁移到一个新的地址段,比如0x6F,这样将宿主和插件的资源id完全隔离开来,怎么修改aapt文件网上有很多大神解释过了,大家可以自行百度,这里我不在啰嗦,直接给出打包完aapt之后的使用方法,aapt文件可以从我分享的内容里下载。

aapt下载地址:https://download.csdn.net/download/qq_22117359/11180515

使用方法:

1、将aapt文件放到Android studio使用的sdk路径下面build-tools/你使用的版本号/aapt,替换掉原来的aapt文件。

2、在app工程的build.gradle里面添加:

android {
    compileSdkVersion gradle.ext.api
    buildToolsVersion gradle.ext.buildTools

//添加如下配置,指定插件的资源id其实地址,不要使用0x01 0x00
    aaptOptions {
        aaptOptions.additionalParameters '--PLUG-resoure-id', '0x61'
    }

}

3、编译apk,编译完成之后查看生成的R.java文件,如果id是以你上面配置定义的地址开始的,证明资源已经按你的地址打包成功了。

二:以上已经完成了资源id的分离,由于没有资源的冲突问题,接下来我们只要把插件apk的资源注入到宿主的资源路径就可以正常使用了。

注入的方法很简单,研究源码可以发现,资源加载到虚拟机最终都是通过AssetManager的addAssetPath方法来添加的,那就简单了,我们先拿到宿主的AssetManager对象,然后hook该对象的addAssetPath方法,通过addAssetPath方法将插件内的资源路径添加进来,就可以让插件内的资源被正常调用了。具体实现如下:

/**
 * 注入插件资源到当前虚拟机
 * 资源合并(一定要保证插件的资源id和宿主不冲突(修改插件aaptOptions,并修改aapt执行文件))
 * @param context 宿主上下文
 * @param apkPaths 插件apk路径
 * @return
 */
public static boolean injectResource(Context context, String apkPaths)
{
    Log.i(TAG, "start inject resource from : "+apkPaths);
    AssetManager assetManager = context.getAssets();
    try
    {
        Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
        int ret = (Integer) addAssetPath.invoke(assetManager, apkPaths);
        Log.d(TAG, "inject resource success path = "+ apkPaths + ", ret=" + ret);
        return true;
    } catch (IllegalAccessException e)
    {
        e.printStackTrace();
    } catch (NoSuchMethodException e)
    {
        e.printStackTrace();
    } catch (IllegalArgumentException e)
    {
        e.printStackTrace();
    } catch (InvocationTargetException e)
    {
        e.printStackTrace();
    }
    Log.i(TAG, "inject resource failed : "+apkPaths);
    return false;
}

通过以上方法完成了资源的注入,到此我们已经解决了插件化的资源冲突与使用问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值