Android应用程序插件化研究之AssetManager

本文深入探讨了Android应用程序中如何加载外部APK的资源,包括图片和字符串等,并提供了具体的实现步骤和代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

文章首发:Android应用程序插件化研究之DexClassLoader|大利猫

最近在研究Android应用的插件化开发,看了好几个相关的开源项目。插件化都是在解决以下几个问题:

就这几个问题,我开始研究插件化开发实现的相关技术。在上篇文章中我讲了如何把插件apk中的class加载到当前进程的问题,本篇文章主要讲第一点的第二点:如何加载另一个apk中的资源到当前应用中。


AssetManager介绍

当我们在组件中获取资源时使用getResource获得Resource对象,通过这个对象我们可以访问相关资源,比如文本、图片、颜色等。通过跟踪源码发现,其实 getResource 方法是Context的一个抽象方法, getResource的实现是在ContextImp中实现的。获取的Resource对象是应用的全局变量,然后继续跟踪源码,发现 Resource 中有一个AssetManager的全局变量,在Resource的构造函数中传入的,所以最终获取资源都是通过 AssertManager 获取的,于是我们把注意力放到AssertManager上。我们要解决下面两个问题。
一、如何获取 AssetManager 对象。
二、如何通过 AssetManager 对象获取插件中apk的资源。
通过对 AssetManager 的相关源码跟踪,我们找到答案。
一、AssetManager 的构造函数没有对 api 公开,不能使用 new 创建;context .getAssets() 可用获取当前上下文环境的 AssetManager;利用反射 AssetManager.class.newInstance() 这样可用获取对象。
二、如何获取插件 apk 中的资源。我们发现 AssetManager 中有个重要的方法。



/** 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}
*/
public native final int addAssetPath(String path);



我们可以把一个包含资源的文件包添加到assets中。这就是AssetManager查找资源的第一个路径。这个方法是一个隐藏方法,我们可以通过反射调用。



AssetManager assetManager = AssetManager.class.newInstance() ; // context .getAssets()?
AssetManager.class.getDeclaredMethod("addAssetPath", String.class).invoke(assetManager, apkPath);



上文提到,我们可以获取当前上下文的 AssetManager,也可以通过反射创建一个 AssetManager。我们这里本文分析的是后一种。第一种能不能呢,这个问题留给读者先去思考,后续文章会单独讨论。 详细了解可以参考老罗的文章 《Android应用程序资源管理器(Asset Manager)的创建过程分析》 。下面我们来写一个demo:获取插件的文本资源和图片资源。



创建一个插件apk工程module:apkbeloaded

我把插件的包名命名为:laodresource.demo.com.loadresourcedemo。
第一步:
这个工程中我们可以不用写任何代码。在drawable目录下放一张图片:icon_be_load.png。
在string中定义字符串:<string name="text_beload">I am from apkBeLoaded</string>。
第二步:
打包生成apk:apkbeloaded-debug.apk。

拷贝到测试机文件路径下:$ adb push <你的路径>/apkbeloaded-debug.apk sdcard/


创建一个宿主apk工程


在布局文件中定义一个文本控件和一个图片控件,分别用来显示插件中获取的文本和图片。



<ImageView android:layout_width="wrap_content"
           android:src="@mipmap/ic_launcher"
           android:id="@+id/icon"
           android:layout_height="wrap_content"/>
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/text"
    android:text="Hello World!"
    android:layout_below="@+id/icon"
    android:layout_centerInParent="true"
    />



MainActivity.java
onCrease:
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ImageView imageView = (ImageView) findViewById(R.id.icon);
    TextView textView = (TextView) findViewById(R.id.text);

    /**
     *  插件apk路径
     */
    String apkPath = Environment.getExternalStorageDirectory()+"/apkbeloaded-debug.apk";
    /**
     *  插件资源对象
     */
    Resources resources = getBundleResource(this,apkPath);
    /**
     *获取图片资源
     */
    Drawable drawable = resources.getDrawable(resources.getIdentifier("icon_be_load", "drawable",
            "laodresource.demo.com.apkbeloaded"));
    /**
     *  获取文本资源
     */
    String text = resources.getString(resources.getIdentifier("text_beload","string",
            "laodresource.demo.com.apkbeloaded"));

    imageView.setImageDrawable(drawable);
    textView.setText(text);

}


创建Resource对象:


public Resources getBundleResource(Context context, String apkPath){
    AssetManager assetManager = createAssetManager(apkPath);
    return new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
}



创建AssetManager对象:


private AssetManager createAssetManager(String apkPath) {
    try {
        AssetManager assetManager = AssetManager.class.newInstance();
        AssetManager.class.getDeclaredMethod("addAssetPath", String.class).invoke(
                assetManager, apkPath);
        return assetManager;
    } catch (Throwable th) {
        th.printStackTrace();
    }
    return null;
}

到这里,我们完成了插件研究的第一个问题:加载插件apk的class和资源。下一篇文章开始研究插件中的组件注册问题。

markdown

Demo源码: https://github.com/liuguangli/LoadResourceDemo
<think>好的,我现在需要处理用户关于在Android API 15上安装应用插件的问题。首先,我得回顾一下Android API 15(即Android 4.0.3)的相关知识,因为它的系统比较老旧,可能和现代Android版本有很大不同。用户提到的插件机制,可能指的是动态加载代码或资源,比如使用DexClassLoader或者资源的热部署。 首先,要确定用户所说的“插件”具体指什么。通常,插件化开发在Android中涉及到动态加载APK或JAR文件,使得主应用可以扩展功能而无需重新安装。在早期的Android版本中,由于安全限制较少,可能更容易实现,但同时也存在兼容性和稳定性问题。 接下来,我需要考虑Android API 15的支持情况。这个版本属于Ice Cream Sandwich,发布于2011年,所以一些现代插件化框架可能不再支持。因此,可能需要使用传统的动态加载方法,比如DexClassLoader。需要确认DexClassLoader在API 15是否可用,我记得DexClassLoader自API level 1就存在,所以应该没问题。 然后,步骤方面,用户需要知道如何将插件APK放入指定目录,使用DexClassLoader加载,并通过反射调用相关类。这可能涉及到文件读写权限,以及存储路径的选择,比如内部存储或外部存储。此外,还需要考虑资源加载的问题,因为单纯加载类可能不足以使用插件中的资源,可能需要使用AssetManager等方法来合并资源。 安全问题也是一个重点。动态加载代码可能导致安全漏洞,特别是如果插件来源不可信的话。需要提醒用户验证插件的安全性,比如签名验证,或者确保插件来自可信源。 另外,兼容性问题需要注意。不同版本的Android在处理类加载和资源管理上可能有差异,尤其是在较旧的系统上。可能需要测试不同的设备,或者处理可能的异常情况。 还有,用户可能需要具体的代码示例,比如如何创建DexClassLoader实例,如何反射调用方法。这时候需要提供一个简单的示例,但要注意代码的兼容性,确保在API 15上可以运行。 最后,应该建议用户考虑升级目标API版本,因为API 15已经非常老旧,很多现代库和框架可能不再支持,同时Google Play也可能有最低API要求。如果用户必须支持API 15,可能需要寻找旧的文档或社区资源,比如Apache基金会的一些遗留项目,或者参考早期的博客文章。 总结步骤应该是:准备插件APK、动态加载类、访问插件资源、安全处理,然后给出注意事项和替代方案。同时,需要引用官方文档或可靠的来源来支持回答,比如Android开发者文档中关于DexClassLoader的部分,或者Apache的DynamicAPK项目。 现在需要确保回答结构清晰,分步骤说明,提供代码示例,并涵盖安全和兼容性问题。同时生成相关问题,可能涉及插件化框架的选择、动态加载资源的方法,或升级API的建议。</think>[用户可见层] ### 如何在 Android API 15 上安装应用插件(开发者指南) 在 Android API 15(Android 4.0.3)中实现插件化功能,主要通过动态加载代码和资源来实现。以下是关键步骤和注意事项: --- #### **1. 动态加载插件APK** - **原理**:使用 `DexClassLoader` 加载插件APK中的类文件。 - **代码示例**: ```java // 插件APK路径(需提前将插件文件放入应用目录) String pluginPath = "/sdcard/plugin.apk"; // 加载目标类 DexClassLoader classLoader = new DexClassLoader( pluginPath, getDir("dex", 0).getAbsolutePath(), null, getClass().getClassLoader() ); // 反射调用插件类 try { Class<?> pluginClass = classLoader.loadClass("com.example.PluginImpl"); Object pluginInstance = pluginClass.newInstance(); Method runMethod = pluginClass.getMethod("run"); runMethod.invoke(pluginInstance); } catch (Exception e) { e.printStackTrace(); } ``` - **注意**:需申请外部存储权限 `<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>`[^1]。 --- #### **2. 加载插件资源** 若插件包含布局、图片等资源,需合并 `AssetManager`: ```java // 获取插件AssetManager AssetManager assetManager = AssetManager.class.newInstance(); Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class); addAssetPath.invoke(assetManager, pluginPath); // 创建插件的Resources对象 Resources pluginRes = new Resources( assetManager, getResources().getDisplayMetrics(), getResources().getConfiguration() ); ``` - **限制**:资源ID可能与宿主应用冲突,需通过命名约定或动态ID映射解决[^2]。 --- #### **3. 安全性建议** - **签名验证**:确保插件APK与宿主应用使用相同签名。 - **沙箱隔离**:在独立进程中运行插件,避免内存泄漏或崩溃影响主应用[^3]。 --- #### **4. 替代方案与兼容性** - **推荐框架**:旧版插件化框架如 [**DynamicAPK**](https://github.com/CtripMobile/DynamicAPK)(Apache 开源)支持低版本 Android。 - **升级建议**:考虑将最低API升级至19+以使用更现代的插件化方案(如 `App Bundle` 或 `Dynamic Feature Modules`)[^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值