Android 动态加载资源

news2024/12/27 10:05:40

资源文件分类

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

public final class R {
	public static final class id {
		public static final int action0 = 0x7f0b006d;
		...
	}
}

访问这种资源比较假单,使用Context的getResources方法得到Resorce对象,进而通过Resources的getXXX方法得到各种资源:

Resources resources = getResources();
String appName = resources.getString(R.string.app_name);   

第二类是assets目录下存放的原始资源文件,apk在编译时不会编译assets下的资源文件,我们通过AssetManager对象来访问,AssetManager又来源于Resources类的getAssets方法:

Resources resources = getResources();
AssetManager am = getResources().getAssets();
InputStream is = getResources().getAssets().open("filename");

Resources是加载资源的重点。Resources内部各种方法其实都是间接调用AssetManager的内部方法,AssetManager负责向系统要资源。
在这里插入图片描述

访问外部资源原理

加载资源的原理推荐查看Android 换肤之资源(Resources)加载源码分析
和Android资源动态加载以及相关原理分析

这里只是简单的说一下

context.getResources().getText()
##Resources
@NonNull public CharSequence getText(@StringRes int id) throws NotFoundException {
        CharSequence res = mResourcesImpl.getAssets().getResourceText(id);
        if (res != null) {
            return res;
        }
        throw new NotFoundException("String resource ID #0x"
                + Integer.toHexString(id));
    }

 ##ResourcesImpl
 public AssetManager getAssets() {
        return mAssets;
    }

内部是调用了mResourcesImpl去访问的,这个对象是ResourcesImpl类型,最后是通过AssetManager去访问资源的。现在可以得出一个结论,AssetManager是真正加载资源的对象,而Resources是app层面API调用的类。

AssetManager
/**
 * Provides access to an application's raw asset files; see {@link Resources}
 * for the way most applications will want to retrieve their resource data.
 * This class presents a lower-level API that allows you to open and read raw
 * files that have been bundled with the application as a simple stream of
 * bytes.
 */
public final class AssetManager implements AutoCloseable {

   /**
     * Add an additional set of assets to the asset manager.  This can be
     * either a directory or ZIP file.  Not for use by applications.  Returns
     * the cookie of the added asset, or 0 on failure.
     * @hide
     */
    @UnsupportedAppUsage
    public int addAssetPath(String path) {
        return addAssetPathInternal(path, false /*overlay*/, false /*appAsLib*/);
    }
}

这里非常的关键,需要解释一下,首先AssetManager是资源管理器,专门负责加载资源的,它内部有个隐藏方法addAssetPath,是用于加载指定路径下的资源文件,也就是说你把apk/jar的路径传给它,它就能把资源数据读到AssetManager,然后就可以访问了。

但是有个问题,虽然实际加载资源的是AssetManager,但是我们通过API访问的确是Resources对象,所以看下Resources对象的构造方法

ResourcesImpl的创建
/**
     * Create a new Resources object on top of an existing set of assets in an
     * AssetManager.
     *
     * @param assets Previously created AssetManager.
     * @param metrics Current display metrics to consider when
     *                selecting/computing resource values.
     * @param config Desired device configuration to consider when
     *               selecting/computing resource values (optional).
     */
    public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
        this(null);
        mResourcesImpl = new ResourcesImpl(assets, metrics, config, new DisplayAdjustments());
    }

看到这个构造方法,有点感觉了吧。可以通过AssetManager对象去构造mResourcesImpl对象,之前也分析过资源访问是通过mResourcesImpl.getAssets().getXXX()方法来完成的,那现在就有办法解决加载外部apk资源的问题了。

创建ResourcesImpl需要4个参数:

  • 参数一: AssetManager 具体资源管理(重要)

  • 参数二: DisplayMetrics 屏幕的一些封装
    通过getResources().getDisplayMetrics().density 获取过屏幕的密度
    通过getResources().getDisplayMetrics().widthPixels 获取过屏幕的宽度等

  • 参数三: Configuration 一些配置信息

  • 参数四: DisplayAdjustments 资源的兼容性等

加载外部apk资源的解决思路

首先,我们需要有3个工程:一个是宿主工程,用来加载外部资源;另一个是插件工程,用来提供外部资源。还有一个是公共库,定义了获取资源的接口方法。宿主工程和插件工程都引入该公共库。引入的方法:

File => Project Structure =>
在这里插入图片描述

插件工程

  1. 字符串资源定义
<string name="hello_message">Hello</string>
  1. 图片资源定义
    在drawable文件夹里放一个名为ic_baseline_train_24.png的图片

创建读取资源的类:

public class UIUtils implements IDynamic {
    public String getTextString(Context context){
        return context.getResources().getString(R.string.hello_message);
    }

    public Drawable getImageDrawable(Context ctx){
        return ctx.getResources().getDrawable(R.drawable.ic_baseline_train_24);
    }

    public View getLayout(Context ctx){
        LayoutInflater layoutInflater = LayoutInflater.from(ctx);
        View view = layoutInflater.inflate(R.layout.activity_main,null);
        return view;
    }
}

编译好该插件工程后,我们将生成的apk文件命名为plugin1.apk,将该apk文件复制到宿主文件的assets目录下:

#build.gradle

assemble.doLast {
    android.applicationVariants.all { variant ->
        // Copy Release artifact to HostApp's assets and rename
        if (variant.name == "release") {
            variant.outputs.each { output ->
                File originFile = output.outputFile
                println originFile.absolutePath
                copy {
                    from originFile
                    into "$rootDir/app/src/main/assets"
                    rename(originFile.name, "plugin1.apk")
                }
            }
        }
    }
}

宿主工程

我们创建一个宿主工程,并在应用启动的时候将assets下的插件apk复制到sd卡下 /data/data/包名/files的路径下面,然后加载插件工程生成的apk文件,并显示出插件里的资源。

public class BaseActivity extends Activity {
    private AssetManager mAssetManager;
    public Resources mResources;
    private Resources.Theme mTheme;

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

    private String dexPath1,dexPath2;   //apk文件地址
    private String fullReleaseFilePath; //释放目录
    private String plugin1name = "plugin1.apk";
    private String plugin2name = "plugin2.apk";

    public ClassLoader classLoader1,classLoader2;
    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(newBase);
        Utils.extractAssets(newBase,plugin1name);
        Utils.extractAssets(newBase,plugin2name);
    }

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

        genegatePluginInfo(plugin1name);
        genegatePluginInfo(plugin2name);

        fullReleaseFilePath = getDir("dex",0).getAbsolutePath();
        dexPath1 = this.getFileStreamPath(plugin1name).getPath();
        dexPath2 = this.getFileStreamPath(plugin2name).getPath();

        classLoader1 = new DexClassLoader(dexPath1,
                fullReleaseFilePath,null,getClassLoader());
        classLoader2 = new DexClassLoader(dexPath2,
                fullReleaseFilePath,null,getClassLoader());
    }

/**
     * 加载外部的插件,生成插件对应的ClassLoader
     * @param pluginName
     */
    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));
    }

 /**
     * 重要
     * 通过反射,创建AssetManager对象,调用addAssetPath方法,把插件Plugin的路径添加到这个AssetManager对象中
     * @param dexPath
     */
    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());
    }
 }
 
 /**
     * 重要
     * 重写Acitivity的getAsset,getResources和getTheme方法
     * mAssetManager是指向插件的,如果这个对象为空,就调用父类ContextImpl的getAssets方法,
     * 这个时候得到的AssetManager对象就指向宿主HostApp,读取的资源也就是HostApp中的资源
     * @return
     */
 @Override
    public AssetManager getAssets() {
        if(mAssetManager == null){
            return super.getAssets();
        }
        return mAssetManager;
    }

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

    @Override
    public Resources.Theme getTheme() {
        if(mTheme == null){
            return super.getTheme();
        }
        return mTheme;
    }

这里创建了一个基类BaseActivity来做 加载APK资源之前的准备工作。真正的加载APK资源是在MainActivity中:

public class MainActivity extends BaseActivity {

    private TextView textView;
    private ImageView imageView;
    private LinearLayout layout;
    private Button btn1,btn2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.text);
        imageView = findViewById(R.id.imageview);
        layout = findViewById(R.id.layout);

        btn1 = findViewById(R.id.btn1);
        btn2 = findViewById(R.id.btn2);

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

                loadResources(pluginInfo.getDexPath());

//                doSomething(pluginInfo.getClassLoader(),"com.chinatsp.plugin1");
                doSomethingOther(pluginInfo.getClassLoader(),"com.chinatsp.plugin1");
//                doSomethingAnother(pluginInfo.getClassLoader(),"com.chinatsp.plugin1");
            }
        });

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

                loadResources(pluginInfo.getDexPath());

//                doSomething(pluginInfo.getClassLoader(),"com.chinatsp.plugin2");
//                doSomethingOther(pluginInfo.getClassLoader(),"com.chinatsp.plugin2");
                doSomethingAnother(pluginInfo.getClassLoader(),"com.chinatsp.plugin2");
            }
        });

        System.out.println(getString(R.string.hello));
    }

/**
     * 通过反射,获取插件中的类,构造出插件类的对象uiUtils,再反射调用插件类对象UIUtils中的方法
     * @param cl
     * @param uiUtilsPkgName
     */
    private void doSomething(ClassLoader cl,String uiUtilsPkgName) {
        try {
            Class clazz = cl.loadClass(uiUtilsPkgName + ".UIUtils");
            Object uiUtils = RefInvoke.createObject(clazz);
            String str = (String) RefInvoke.invokeInstanceMethod(uiUtils, "getTextString", Context.class, this);
            textView.setText(str);

            Drawable drawable = (Drawable) RefInvoke.invokeInstanceMethod(uiUtils, "getImageDrawable", Context.class, this);
            imageView.setBackground(drawable);

            layout.removeAllViews();

            View view = (View) RefInvoke.invokeInstanceMethod(uiUtils, "getLayout",Context.class,this);
            layout.addView(view);

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

 /**
     * 直接反射获取插件类中的R文件R.java的内部类,获取内部类中资源文件对应生成的16进制的值,也就R.string.xxx
     * R.drawable.xxx对应的值,通过getResources方法的getxxx方法来获取资源文件
     * @param cl
     * @param uiUtilsPkgName
     */
    private void doSomethingOther(ClassLoader cl,String uiUtilsPkgName) {
        try {
            Class stringClass = cl.loadClass(uiUtilsPkgName + ".R$string");
            int resId1 = (int) RefInvoke.getStaticFieldObject(stringClass,"hello_message");
            textView.setText(getResources().getString(resId1));

            Class drawableClass = cl.loadClass(uiUtilsPkgName + ".R$drawable");
            int resId2 = (int) RefInvoke.getStaticFieldObject(drawableClass,"ic_baseline_train_24");
            imageView.setBackground(getResources().getDrawable(resId2));

            Class layoutClass = cl.loadClass(uiUtilsPkgName + ".R$layout");
            int resId3 = (int) RefInvoke.getStaticFieldObject(layoutClass,"activity_main");
            View view = LayoutInflater.from(this).inflate(resId3,null);

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

 /**
     * 通过反射,获取插件中的类,构造出插件类的对象dynamicObject,再直接调用插件类对象UIUtils中的方法
     * @param cl
     * @param uiUtilsPkgName
     */
    private void doSomethingAnother(ClassLoader cl,String uiUtilsPkgName) {
        Class mLoadClassDynamic = null;
        try {
            mLoadClassDynamic = cl.loadClass(uiUtilsPkgName + ".UIUtils");
            Object dynamicObject = mLoadClassDynamic.newInstance();
            IDynamic dynamic = (IDynamic) dynamicObject;
            String str = dynamic.getTextString(this);
            textView.setText(str);

            Drawable drawable = dynamic.getImageDrawable(this);
            imageView.setBackground(drawable);

            layout.removeAllViews();
            View view = dynamic.getLayout(this);
            layout.addView(view);

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

}

Source Code

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/559234.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从注解@EventListener和@TransactionalEventListener掌握Spring的事件机制原理

文章目录 Spring事件监听机制Spring事件监听机制概述Spring事件监听机制介绍Spring事件相关的几个类使用硬编码简单还原Spring事件机制 Spring事件机制正确的使用方式Spring事件创建Spring事件发布方式Spring事件监听方式面向接口的方式面向注解的方式EventListenerTransaction…

SpringBoot开发实用篇2---与数据层技术有关的替换和整合

四、数据层解决方案 1.SQL 现有数据层解决方案技术选型&#xff1a;DruidMyBatis-plusMySQL 数据源&#xff1a;DruidDataSource 持久化技术&#xff1a;MyBatis-plus/MyBatis 数据库&#xff1a;MySql 内置数据源&#xff1a; SpringBoot提供了3种内嵌的数据源对象供开发者选…

2023年数据治理企业老板为啥都让员工考CDGA/CDGP证书?

企业老板让员工考取CDGA证书一般有以下几个原因: 提升数据治理能力 CDGA认证是一种全球通用的数据治理认证&#xff0c;可以帮助员工提升数据治理的技能和能力更好地管理、保护和分析企业的数据。 增强员工竞争力 随着数据治理在企业中的重要性越来越高&#xff0c;拥有CDGA…

day01_单元测试_配置文件

一、软件的生命周期 **软件的可行性分析:**分析该软件是否值的研发,会消耗多少成本,能带来多少的利益等分析 **需求分析:**分析该软件具体该具备有那些功能,产品经理与客户一起讨论 **软件设计:**该软件应该使用什么样的架构,用什么样的数据库,每个模块的具体功能 **程序编…

Github Copilot 的补强工具Github Copilot Labs的常用功能介绍

一、什么是Github Copilot Labs Github Copilot Labs是由GitHub推出的一款基于人工智能技术的代码协作工具&#xff0c;旨在协助开发者更加快速、高效地编写代码。该工具使用了机器学习技术&#xff0c;通过学习大量的开源代码和编写实践&#xff0c;提供了对于代码变量、函数…

物理删除与逻辑删除

目录 一、物理删除与逻辑删除 二、逻辑删除实现 三、API使用方法 四、全局配置参数 一、物理删除与逻辑删除 物理删除&#xff1a;指文件存储所用到的磁存储区域被真正的擦除或清零&#xff0c;这样删除的文件是不可以恢复的&#xff0c;物理删除是计算机处理数据时的一个概…

怎样的年轻化法则,让这个品牌四年净利润复合增速达30%

年轻世代消费者的崛起&#xff0c;从消费层面讲&#xff0c;为市场带来活跃的同时&#xff0c;给品牌带来的是如何转型升级的问题&#xff0c;在众多转型的品牌中&#xff0c;年轻化策略与方式不尽相同。 在2019年至2022年期间&#xff0c;报喜鸟营收复合增速达10%&#xff0c…

iptables防火墙(2)

iptables防火墙&#xff08;2&#xff09; 一、SNATSNAT应用环境SNAT原理SNAT转换前条件扩展 二、DNATDNAT应用环境DNAT原理DNAT转换前提条件扩展 三、防火墙规则的备份和还原导出&#xff08;备份&#xff09;所有表的规则导入&#xff08;还原&#xff09;规则 一、SNAT SNA…

线性回归和预测

目录 1、线性回归 2、R-Squared 1、线性回归 在机器学习和统计建模中&#xff0c;这种关系用于预测未来事件的结果 线性回归使用数据点之间的关系在所有数据点之间画一条直线 这条线可以用来预测未来的值 在机器学习中&#xff0c;预测未来非常重要。比如房价、股票等预测 …

分布式全局唯一id实现-4 springCloud-MyBatis-Plus集成美团分布式全局id(leaf)

前言&#xff1a;美团的leaf集成了db分段生成id和雪花算法生成分布式id&#xff0c;本文对其实现部分细节展开讨论&#xff0c;leaf 的具体实现请参考&#xff1a;https://tech.meituan.com/MT_Leaf.html&#xff1b; 1 使用db分段id&#xff1a; leaf 的分段id本质上是使用了…

5。STM32裸机开发(5)

嵌入式软件开发学习过程记录&#xff0c;本部分结合本人的学习经验撰写&#xff0c;系统描述各类基础例程的程序撰写逻辑。构建裸机开发的思维&#xff0c;为RTOS做铺垫&#xff08;本部分基于库函数版实现&#xff09;&#xff0c;如有不足之处&#xff0c;敬请批评指正。 &…

二本4年测试经验,3面阿里艰苦经历(定薪25K),上岸那天我哭了...

前言 4月准备跳槽&#xff0c;先后面试了各大小公司&#xff0c;拿了一些小offer&#xff0c;面试的公司大部分都能过&#xff0c;但是做人总是要用梦想吧&#xff0c;没有梦想和咸鱼有什么区别&#xff0c;最终把目标放在了阿里&#xff0c;准备了大概3个月的时间&#xff0c…

mysql45讲笔记

不一定要都学&#xff0c;有些感觉用不到&#xff0c;有选择的学&#xff01;&#xff01;&#xff01; 文章目录 mysql45讲1.mysql基础架构2.mysql日志系统3.事务隔离4.索引类型1.哈希表2.有序数组3.二叉搜索树4.B 树 5.索引重点概念覆盖索引索引下推最左前缀原则 6.全局锁表级…

ERP系统是什么?ERP实施顾问怎么做?

ERP实施顾问怎么做&#xff1f; 首先想要从事相关行业&#xff0c;必须先了解什么是ERP&#xff0c;ERP系统功能模块是怎样的&#xff0c;而后才能进行ERP实施顾问的工作。 一、ERP是什么 ERP系统主要是干什么的&#xff1f;ERP系统&#xff0c;简单理解就是一套记账、做账软…

“全球金融科技大会——中国金融业开源技术应用与发展论坛”在北京举行

3月28日&#xff0c;“全球金融科技大会——中国金融业开源技术应用与发展论坛”在北京新动力金融科技中心举行。 会议现场 人民银行科技司二级巡视员杨富玉&#xff0c;开放原子开源基金会理事长孙文龙&#xff0c;中国金电党委书记、董事长周逢民为大会致辞。北京市西城区区…

(转载)MATLAB智能算法30个案例分析(3)——基于遗传算法的BP神经网络优化算法

1 理论基础 1.1 BP神经网络概述 BP网络是一类多层的前馈神经网络。它的名字源于在网络训练的过程中&#xff0c;调整网络的权值的算法是误差的反向传播的学习算法&#xff0c;即为BP学习算法。BP算法是Rumelhart等人在1986年提出来的。由于它的结构简单&#xff0c;可调整的…

docker+redis哨兵模式(一主二从三哨兵)- docker-compose

一、docker-compose 安装&#xff1a; sudo apt-get update #安装最新的docke-ce sudo apt-get install docker-ce # 下载最新的docker-compose curl -L https://github.com/docker/compose/releases/download/1.25.0-rc4/docker-compose-uname -s-uname -m -o /usr/local…

面试字节,过关斩将直接干到 3 面,结果被吊打了?

人人都有大厂梦&#xff0c;对于软件测试员来说&#xff0c;BAT 为首的一线互联网公司肯定是自己的心仪对象&#xff0c;毕竟能到这些大厂工作&#xff0c;不仅薪资高待遇好&#xff0c;而且能力技术都能够得到提升&#xff0c;最关键的是还能够给自己镀上一层金&#xff0c;让…

网络通信概述 -了解网络编程,什么是ip和端口,url

网络&#xff1a;网络就是一种辅助双方或者多方能够连接到一起的工具。 左&#xff1a;单机游戏&#xff08;无网络&#xff09; 右&#xff1a;网络游戏 网络编程&#xff1a;网络编程就是&#xff0c;让在不同的电脑上的软件能够进行数据传递&#xff0c;即进程之间的通信。…

一名8年测试工程师,因为偷偷接私活被····

接私活 对程序员这个圈子来说是一个既公开又隐私的话题&#xff0c;不说全部&#xff0c;应该大多数程序员都有过想要接私活的想法&#xff0c;当然&#xff0c;也有部分得道成仙的不主张接私活。但是很少有人在公开场合讨论私活的问题&#xff0c;似乎都在避嫌。就跟有人下班后…