首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >资源的插件化

资源的插件化

作者头像
用户3112896
发布于 2019-09-26 08:40:22
发布于 2019-09-26 08:40:22
1.2K0
举报
文章被收录于专栏:安卓圈安卓圈

1.android资源文件分为两类:

第一类是res目录下存放的可编译资源文件,编译时,系统会自动在R.java中生成资源文件的十六进制值

代码语言:javascript
AI代码解释
复制
Resources resources = getResources();
String appName = resources.getString(R.string.app_name);           

第二类是assets目录下存放的原始资源文件,apk在编译时不会编译assets下的资源文件

代码语言:javascript
AI代码解释
复制
Resources resources = getResources();
AssetManager am = getResources().getAssets();
InputStream is = getResources().getAssets().open("filename");

2.Resources内部各种方法其实都是间接调用AssetManager的内部方法。AssetManager的addAssetPath方法会在app启动的时候把当前apk的路径传进去,就能访问apk的所有资源了。在这里可以把插件apk的资源塞进去

3.apk打包时会生成一个resource.arsc文件,它就是一个Hash表,存放着每个十六进制值和资源的对应关系

***资源的插件化解决方案***

代码语言:javascript
AI代码解释
复制
public class Dynamic implements IDynamic {

    @Override
    public String getStringForResId(Context context) {
        return context.getResources().getString(R.string.myplugin1_hello_world);
    }
}
代码语言:javascript
AI代码解释
复制
<resources>
    <string name="app_name">Plugin1</string>
    <string name="myplugin1_hello_world">myplugin1_hello_world</string>
</resources>
代码语言:javascript
AI代码解释
复制
public class BaseActivity extends Activity {

    private AssetManager mAssetManager;
    private Resources mResources;
    private Resources.Theme mTheme;
    private String dexpath = null;    //apk文件地址
    private File fileRelease = null;  //释放目录

    protected DexClassLoader classLoader = null;

    private String pluginName = "plugin1.apk";

    TextView tv;

    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(newBase);

        Utils.extractAssets(newBase, pluginName);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //第三步:加载外部的插件,生成插件对应的ClassLoader
        File extractFile = this.getFileStreamPath(pluginName);
        dexpath = extractFile.getPath();

        fileRelease = getDir("dex", 0);

        classLoader = new DexClassLoader(dexpath, fileRelease.getAbsolutePath(), null, getClassLoader());
    }

    /**
     * 第一步:通过反射,创建AssetManager对象,调用addAssetPath方法,把插件Plugin的路径添加到这个AssetManager对象中
     **/
    protected void loadResources() {
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
            addAssetPath.invoke(assetManager, dexpath);
            mAssetManager = assetManager;
        } catch (Exception e) {
            e.printStackTrace();
        }
        Resources superRes = super.getResources();
        superRes.getDisplayMetrics();
        superRes.getConfiguration();
        mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
        mTheme = mResources.newTheme();
        mTheme.setTo(super.getTheme());
    }

    /**
     * 第二步:重写Acitivity的getAsset,getResources和getTheme方法
     **/
    @Override
    public AssetManager getAssets() {
        return mAssetManager == null ? super.getAssets() : mAssetManager;
    }

    @Override
    public Resources getResources() {
        return mResources == null ? super.getResources() : mResources;
    }

    @Override
    public Resources.Theme getTheme() {
        return mTheme == null ? super.getTheme() : mTheme;
    }
}
代码语言:javascript
AI代码解释
复制
public class MainActivity extends BaseActivity {
    TextView tv;

    @SuppressLint("NewApi")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btn_6 = (Button) findViewById(R.id.btn_6);

        tv = (TextView)findViewById(R.id.tv);

        //带资源文件的调用
        btn_6.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View arg0) {
                loadResources();
                Class mLoadClassDynamic = null;
                try {
                    //第四步:通过反射,获取插件中的类,构造出插件类的对象dynamicObject,然后就可以让插件中的类读取插件中的资源了
                    mLoadClassDynamic = classLoader.loadClass("jianqiang.com.plugin1.Dynamic");
                    Object dynamicObject = mLoadClassDynamic.newInstance();

                    IDynamic dynamic = (IDynamic) dynamicObject;
                    String content = dynamic.getStringForResId(MainActivity.this);
                    tv.setText(content);
                    Toast.makeText(getApplicationContext(), content + "", Toast.LENGTH_LONG).show();
                } catch (Exception e) {
                    Log.e("DEMO", "msg:" + e.getMessage());
                }
            }
        });
    }
}

下面举个例子,就是类似QQ换皮肤

fork了强哥的github代码到自己的github下 https://github.com/king1039/Dynamic3

将代码跑起来,Plugin1和Plugin2打成apk放到HostApp的assets下

贴下主要代码

代码语言:javascript
AI代码解释
复制
public class BaseActivity extends Activity {

    private AssetManager mAssetManager;
    private Resources mResources;
    private Resources.Theme mTheme;

    protected HashMap<String, PluginInfo> plugins = new HashMap<String, PluginInfo>();

    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(newBase);

        Utils.extractAssets(newBase, "plugin1.apk");
        Utils.extractAssets(newBase, "plugin2.apk");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        genegatePluginInfo("plugin1.apk");
        genegatePluginInfo("plugin2.apk");
    }

    protected void genegatePluginInfo(String pluginName) {
        File extractFile = this.getFileStreamPath(pluginName);
        File fileRelease = getDir("dex", 0);
        String dexpath = extractFile.getPath();
        DexClassLoader classLoader = new DexClassLoader(dexpath, fileRelease.getAbsolutePath(), null, getClassLoader());

        plugins.put(pluginName, new PluginInfo(dexpath, classLoader));
    }

    protected void loadResources(String dexPath) {
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
            addAssetPath.invoke(assetManager, dexPath);
            mAssetManager = assetManager;
        } catch (Exception e) {
            e.printStackTrace();
        }
        Resources superRes = super.getResources();
        mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
        mTheme = mResources.newTheme();
        mTheme.setTo(super.getTheme());
    }

    @Override
    public AssetManager getAssets() {
        return mAssetManager == null ? super.getAssets() : mAssetManager;
    }

    @Override
    public Resources getResources() {
        return mResources == null ? super.getResources() : mResources;
    }

    @Override
    public Resources.Theme getTheme() {
        return mTheme == null ? super.getTheme() : mTheme;
    }
}
代码语言:javascript
AI代码解释
复制
public class ResourceActivity extends BaseActivity {

    /**
     * 需要替换主题的控件
     * 这里就列举三个:TextView,ImageView,LinearLayout
     */
    private TextView textV;
    private ImageView imgV;
    private LinearLayout layout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_resource);

        textV = (TextView) findViewById(R.id.text);
        imgV = (ImageView) findViewById(R.id.imageview);
        layout = (LinearLayout) findViewById(R.id.layout);

        findViewById(R.id.btn1).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View arg0) {
                PluginInfo pluginInfo = plugins.get("plugin1.apk");

                loadResources(pluginInfo.getDexPath());

                doSomething(pluginInfo.getClassLoader());
            }
        });

        findViewById(R.id.btn2).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                PluginInfo pluginInfo = plugins.get("plugin2.apk");

                loadResources(pluginInfo.getDexPath());

                doSomething(pluginInfo.getClassLoader());
            }
        });
    }

    private void doSomething(ClassLoader cl) {
        try {
            Class clazz = cl.loadClass("jianqiang.com.plugin1.UIUtil");

            String str = (String) RefInvoke.invokeStaticMethod(clazz, "getTextString", Context.class, this);
            textV.setText(str);

            Drawable drawable = (Drawable) RefInvoke.invokeStaticMethod(clazz, "getImageDrawable", Context.class, this);
            imgV.setBackground(drawable);

            layout.removeAllViews();
            View view = (View) RefInvoke.invokeStaticMethod(clazz, "getLayout", Context.class, this);
            layout.addView(view);

        } catch (Exception e) {
            Log.e("DEMO", "msg:" + e.getMessage());
        }
    }
}

简单来说,就是针对不同的apk生成不同的ClassLoader,然后通过反射框架取出相应的资源,最终加载显示

doSomething还有另外一个种写法,直接访问R.java的内部类drawable/string/layout中的相应字段对应的十六进制值(这个好屌)

代码语言:javascript
AI代码解释
复制
private void doSomething(ClassLoader cl) {
    try {
        Class stringClass = cl.loadClass("jianqiang.com.plugin1.R$string");
        int resId1 = (int) RefInvoke.getStaticFieldObject(stringClass, "hello_message");
        textV.setText(getResources().getString(resId1));


        Class drawableClass = cl.loadClass("jianqiang.com.plugin1.R$drawable");
        int resId2 = (int) RefInvoke.getStaticFieldObject(drawableClass, "robert");
        imgV.setBackground(getResources().getDrawable(resId2));

        Class layoutClazz = cl.loadClass("jianqiang.com.plugin1.R$layout");
        int resId3 = (int) RefInvoke.getStaticFieldObject(layoutClazz, "main_activity");
        View view = (View) LayoutInflater.from(this).inflate(resId3, null);
        layout.removeAllViews();
        layout.addView(view);

    } catch (Exception e) {
        Log.e("DEMO", "msg:" + e.getMessage());
    }
}

总的来说,资源插件化就是通过反射AssetManager和addAssetPath来加载插件资源

--摘自《android插件化开发指南》

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-06-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 安卓圈 微信公众号,前往查看

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

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

评论
登录后参与评论
暂无评论
推荐阅读
Docker环境的持续部署优化实践
那年公司快速成长,频繁上线新项目,每上线一个项目,就需要新申请一批机器,初始化,部署依赖的服务环境,一个脚本行天下
37丫37
2019/03/14
9710
Docker环境的持续部署优化实践
Docker容器与容器云
2.容器云:以容器为资源分割和调度的基本单位,封装整个软件运行时环境,为开发者和系统管理员提供用于构建、发布和运行分布式应用的平台
硬核项目经理
2019/08/06
3.4K0
【Docker】容器化应用程序的配置管理策略与实践
Docker是一种开源的容器化平台,简化应用程序的打包、交付和运行过程。基于Linux容器技术,通过提供一个轻量级、可移植和自包含的容器来实现应用程序的隔离和部署。
DS小龙哥
2023/09/09
9960
【Docker】容器化应用程序的配置管理策略与实践
京东mPaaS平台之Android组件化系统私有化部署改造实践
系统上云是科技企业及传统企业降本增效、协同办公等有效的助力方式。本文将京东mPaaS平台下的Aura平台(Android组件平台)在T-PaaS环境进行私有化部署改造的历程记录下来,分享给大家。
京东技术
2021/03/16
1.1K0
ofo 基于 K8S 容器云平台的实践
| 为 | 容 | 器 | 技 | 术 | 而 | 生 |
CNCF
2019/12/06
1.5K0
Kubernetes在宜信落地实践
伴随着微服务的架构的普及,结合开源的Dubbo和Spring Cloud等微服务框架,宜信内部很多业务线逐渐了从原来的单体架构逐渐转移到微服务架构。应用从有状态到无状态,具体来说将业务状态数据如:会话、用户数据等存储到中间件中服务中。
宜信技术学院
2019/06/28
1K0
OpenShift总体架构设计
OpenShift被其供应商Red Hat称为“ 企业级Kubernetes”。其实Kubernetes是OpenShift不可或缺的一部分,并围绕它构建了更多功能。
网络安全观
2021/03/01
1.6K0
普元容器云关键设计和实践之路
目前,DevOps,微服务与容器云,可以说是炙手可热的三大话题,甚至可以说它们是云时代企业新一代IT架构的三大基石也不为过。微服务主要解决的是开发期的设计问题,DevOps则是解决开发,测试与运维之间的衔接问题,容器云则是重点在于简化部署与解放运维。
yuanyi928
2018/07/26
1.1K0
普元容器云关键设计和实践之路
持续交付:云原生应用的“十二要素”
“独立系统的架构原则”(https://isa-principles.org/)与“十二要素应用”密切相关,但前者更注重架构方面。这些原则基于微服务,尤其是自包含系统(SCS)的经验,是一组最佳实践的集合。
用户1682855
2019/11/05
1.5K0
持续交付:云原生应用的“十二要素”
【笔记】Enjoy Docker
容器核心技术,包括容器规范,容器runtime,容器管理工具,容器定义工具,容器OS,Registries。
于顾而言SASE
2024/10/28
2340
【笔记】Enjoy Docker
Kubernetes容器云平台实践
Kubernetes是Google开源的一个容器编排引擎,它支持自动化部署、大规模可伸缩、应用容器化管理。伴随着云原生技术的迅速崛起,如今Kubernetes 事实上已经成为应用容器化平台的标准,越来越受到企业的青睐,在生产中也应用的也越来越广泛。 我们的容器平台建设从2016年开始,大致经历了探索预研、体系建设和平台落地这样三个阶段。
孙杰
2019/10/29
3.2K0
Kubernetes容器云平台实践
构建一个高可用及自动发现的 Docker 基础架构
本文介绍了如何使用Docker、HAProxy和Consul构建高可用性且可扩展的微服务架构。通过详细阐述Docker的部署、HAProxy的配置以及Consul的配置和使用,使读者对微服务架构的高可用性需求有深入的理解。
刘天斯
2017/06/27
3.3K0
构建一个高可用及自动发现的 Docker 基础架构
利用Docker开启持续交付之路
持续交付即Continuous Delivery,简称CD,随着DevOps的流行正越来越被传统企业所重视。持续交付讲求以短周期、小细粒度,自动化的方式频繁的交付软件,在这个过 程中要求开发、测试、用户体验等角色紧密合作,快速收集反馈,从而不断改善软件质量并减少浪费。然而,在我所接触的传统企业中,对于持续交付实践的实施都 还非常初级,坦白说,大部分还停留的手工生成发布包,手工替换文件进行部署的阶段,这样做无疑缺乏管理且容易出错。如果究其原因,我想主要是因为构建一个 可实际运行且适合企业自身环境的持续发布
小小科
2018/05/02
1.8K0
利用Docker开启持续交付之路
DevOps最佳实践-处理好敏捷研发,持续集成和容器云三者集成
今天准备谈下DevOps过程最佳实践以及DevOps支撑平台建设中的一些思考。在前面文章里面我就已经谈到了传统企业IT架构转型或企业数字化建设需要解决两个方面问题。
人月聊IT
2025/06/24
2920
DevOps最佳实践-处理好敏捷研发,持续集成和容器云三者集成
Jenkins 配合 Kubernetes 实现服务持续集成的实践和建议
当你在网上搜索 Jenkins 持续集成 dockers/kubernetes 时,80% 答案是在Kubernetes集群中容器化 Jenkins,在我看来,对于业务服务数量有限的互联网公司,前期的话,不是特别建议把Jenkins直接安装到kubernetes集群当中,特别是在没有使用 Kubernetes 容器云平台之前已经有了自动化构建工具,有以下原因:
用户5166556
2020/04/22
1.9K1
Jenkins 配合 Kubernetes 实现服务持续集成的实践和建议
多环境下的微服务持续交付实践
本文通过部署一个基于Dubbo的微服务项目——Q云书城(QCBM)(图1-1),介绍如何在多环境下部署微服务持续交付项目。通过使用Zadig持续部署工具,展示多环境配置、微服务构建、工作流交付及运行时管理的完整过程,提供一种多环境下持续集成、持续交付及云原生微服务管理能力的解决方案。(图1-2)
远远小七宝
2022/12/01
2.4K0
多环境下的微服务持续交付实践
从Docker Machine到K8S:容器管理为啥有这么多工具?
首先,我们当然可以在单台ESXi主机上通过CLI命令行或者Vmware Host Client可视化工具来创建和管理虚拟机;可以在这台主机上创建多个虚拟机等。
嘉为蓝鲸
2018/12/21
1.4K0
Kubernetes+Docker+Istio 容器云实践
随着社会的进步与技术的发展,人们对资源的高效利用有了更为迫切的需求。近年来,互联网、移动互联网的高速发展与成熟,大应用的微服务化也引起了企业的热情关注,而基于Kubernetes+Docker的容器云方案也随之进入了大众的视野。开普勒云是一个基于Kubernetes+Docker+Istio的微服务治理解决方案。
宜信技术学院
2019/10/17
1.1K0
Kubernetes(一) - Docker管理工具
Kubernetes(一) - Docker管理工具 虽然Docker已经很强大了,但是在实际使用上还是有诸多不便,比如集群管理,资源调度文件管理等等,那么在这样一个百花齐放的容器时代涌现出了很多解决
喵了个咪233
2018/07/06
8320
容器云服务是什么?
容器云是一种以容器技术为核心的云计算服务形态,其核心目标是通过标准化封装应用及其依赖环境,实现跨平台快速部署和高效管理。容器技术将应用程序与所需的运行库、配置等资源打包为轻量化的独立单元(容器),确保应用在不同计算环境中保持行为一致性。
云惑雨问
2025/03/10
7140
推荐阅读
相关推荐
Docker环境的持续部署优化实践
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
首页
学习
活动
专区
圈层
工具
MCP广场
首页
学习
活动
专区
圈层
工具
MCP广场