跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Java大前端java

Android DL 插件化开发步骤与核心注意事项

综述由AI生成Android DL 插件化框架的开发步骤与核心注意事项。内容涵盖从项目获取、Gradle 配置、Java 代码修改到宿主端调用插件 APK 的完整流程。重点阐述了 DL 框架在生命周期管理、SO 库加载及多 APK 管理方面的优势。同时深入分析了开发中的关键坑点,包括主题必须单独设置且仅限系统主题、Context 引用需用 that 替代 this、Activity 启动限制、权限需在宿主声明以及插件合法性校验(MD5 验证)等安全机制。文章旨在帮助开发者规避常见错误,规范插件化开发实践。

Qiny01发布于 2025/2/7更新于 2026/6/220 浏览
Android DL 插件化开发步骤与核心注意事项

Android DL 插件化开发步骤与核心注意事项

DL(Dynamic Load)框架是 Android 插件化技术的一种实现方案,旨在通过动态加载 APK 来实现功能扩展和热更新。本文将详细介绍利用 DL 框架进行开发的完整流程、框架优势以及在实际开发中必须注意的关键问题。

一、利用 DL 框架进行开发的步骤

1. 获取项目代码

首先从官方仓库获取 DL 框架源码。解压后,主要目录结构如下:

  • lib 目录:包含 DL 的插件库文件。
  • sample 目录:包含对应的 Demo 示例工程。

2. 引入 DL 库

宿主工程配置

在宿主工程中,只需将 dl-lib.jar 加入 libs 目录,并在 build.gradle 中引用:

compile fileTree(dir: 'libs', include: ['*.jar'])
插件工程配置

插件工程需要用到 DL 库的类,因此也需要引入 dl-lib.jar。但由于插件最终会加载到宿主程序中,而宿主程序已经引入了该库,如果常规方式导入会导致重复拷贝。解决方案是让插件中的 DL 库仅参与编译,但不打包进 APK。

在 Android Studio 的插件工程中创建一个目录(如 external-jars),将 dl-lib.jar 放入其中,并在 build.gradle 中添加以下配置:

provided files('external-jars/dl-lib.jar')

同理,如果宿主程序使用了 support-v4.jar,插件中原有的 support-v4.jar 也不能被打包进去。需将其放入 external-jars 并追加配置:

provided files('external-jars/android-support-v4.jar')

3. 插件 Java 代码修改

插件中的所有 Activity 必须继承自 DLBasePluginActivity 或 DLBasePluginFragmentActivity。

  • 若原有 Activity 为普通 Activity,改为继承 DLBasePluginActivity。
  • 若原有 Activity 为 FragmentActivity,则继承 DLBasePluginFragmentActivity。

继承示例:

public class MainActivity extends DLBasePluginActivity
public class TestFragmentActivity extends DLBasePluginFragmentActivity

此外,原有 Activity 中所有代表 Context 引用的 this 都必须改写为 。如果要调用另一个 Activity,不能使用标准的 ,而是使用 ,并且 Intent 也要变为 :

that
startActivity()
startPluginActivity
DLIntent
DLIntent intent = new DLIntent(getPackageName(), ListActivity.class);
intent.putExtra(TYPE, item.getNavigationInfo());
startPluginActivity(intent);

4. 调用的插件 APK

在宿主工程中,首先需要获取要调用的插件 APK 对应的 MainActivity。DL 的 Demo 中插件路径默认为 SD 卡上的 DynamicLoadHost 目录,如果没有则需要创建,或者根据实际需求进行修改。

获取插件列表:

String pluginFolder = Environment.getExternalStorageDirectory() + "/DynamicLoadHost";
File file = new File(pluginFolder);
File[] plugins = file.listFiles();
if (plugins == null || plugins.length == 0) {
    mNoPluginTextView.setVisibility(View.VISIBLE);
    return;
}

for (File plugin : plugins) {
    PluginItem item = new PluginItem();
    item.pluginPath = plugin.getAbsolutePath();
    item.packageInfo = DLUtils.getPackageInfo(this, item.pluginPath);
    if (item.packageInfo.activities != null && item.packageInfo.activities.length > 0) {
        item.launcherActivityName = item.packageInfo.activities[0].name;
    }
    mPluginItems.add(item);
}

调起插件 APK: 需要使用 dl-lib.jar 提供的机制:

  1. 通过 Class.forName 的方式获取我们需要调用的插件 APK 中 MainActivity 的 Class 对象。
  2. 判断该对象继承自 DLBasePluginActivity 还是 DLBasePluginFragmentActivity,得到对应的代理 Class 对象。
  3. 使用对应的代理 Class 对象调起插件 APK。
PluginItem item = mPluginItems.get(position);
Class<?> proxyCls = null;

try {
    Class<?> cls = Class.forName(item.launcherActivityName, false,
            DLClassLoader.getClassLoader(item.pluginPath, getApplicationContext(), getClassLoader()));
    if (cls.asSubclass(DLBasePluginActivity.class) != null) {
        proxyCls = DLProxyActivity.class;
    }
} catch (ClassNotFoundException e) {
    e.printStackTrace();
    Toast.makeText(this,
            "load plugin apk failed, load class " + item.launcherActivityName + " failed.",
            Toast.LENGTH_SHORT).show();
} catch (ClassCastException e) {
    // ignored
} finally {
    if (proxyCls == null) {
        proxyCls = DLProxyFragmentActivity.class;
    }
    Intent intent = new Intent(this, proxyCls);
    intent.putExtra(DLConstants.EXTRA_DEX_PATH,
            mPluginItems.get(position).pluginPath);
    startActivity(intent);
}

二、DL 框架优秀之处

Dynamic Load APK 向我们展示了许多优秀的处理方法,主要体现在以下几个方面:

  1. 生命周期抽象:把 Activity 关键的生命周期方法抽象成 DLPlugin 接口,ProxyActivity 通过 DLPlugin 代理调用插件 Activity 的生命周期。
  2. 基础基类设计:设计一个基础的 BasePluginActivity 类,插件项目里使用这些基类进行开发,可以以接近常规 Android 开发的方式开发插件项目。
  3. Service 处理:以类似的方式处理 Service 的问题,确保服务组件也能正常运行。
  4. 兼容性处理:处理了大量常见的兼容性问题,比如使用 Theme 资源时出现的问题。
  5. SO 库加载:处理了插件项目里的 so 库的加载问题,确保原生代码能正确执行。
  6. APK 管理:使用 PluginPackage 管理插件 APK,从而可以方便地管理多个插件项目。

处理插件项目里的 SO 库的加载

这里需要把插件 APK 里面的 SO 库文件解压释放出来,然后根据当前设备 CPU 的型号选择对应的 SO 库,并使用 System.load 方法加载到当前内存中来。

多插件 APK 的管理

动态加载一个插件 APK 需要三个对应的 DexClassLoader、AssetManager、Resources 实例。可以用组合的方式创建一个 PluginPackage 类存放这三个变量,再创建一个管理类 PluginManager,用成员变量 HashMap 来维护这些包的信息,实现高效的查找和管理。

三、DL 插件开发注意事项

1. 主题设置

DL 的插件必须每个 Activity 都单独设置主题。虽然文档说明也可以在 Application 上设置主题,但实际测试表明,即使 Application 设置了主题,也必须每个 Activity 都单独设置主题才能生效。

错误的做法(仅在 Application 设置):

<application
    android:allowBackup="true"
    android:theme="@android:style/Theme.Holo.Light"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name" >
    <activity
        android:name=".SampleActivity"
        android:label="@string/app_name" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>

正确的做法(Activity 单独设置):

<application
    android:allowBackup="true"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name" >
    <activity
        android:name=".SampleActivity"
        android:theme="@android:style/Theme.Holo.Light.DarkActionBar"
        android:label="@string/app_name" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>

注意:插件只能用系统主题,不能直接定义自定义主题。例如不能这样写:

android:theme="@style/AppTheme"

只能使用系统内置样式,如:

android:theme="@android:style/Theme.Light"

虽然在某些特定插件上可能不按照此规则也可以正确运行,但绝大多数情况下都需要满足此条件以避免样式错乱。

2. 权限声明

插件所需要的所有权限必须在宿主工程的 Manifest 中进行声明。插件自身无法独立申请权限,必须依赖宿主授予。

3. 使用 DL 进行插件 APK 的开发规范

  • 慎用 this:因为 this 指向的是当前对象,即 APK 中的 Activity。但是由于 Activity 已经不是常规意义上的 Activity,所以 this 是没有意义的。但是如果 this 表示的是一个接口而不是 Context,比如 Activity 实现了一个接口,那么 this 继续有效。
  • 使用 that:既然 this 不能用,那就用 that。that 是 APK 中 Activity 的基类 BaseActivity 中的一个成员,它在 APK 安装运行的时候指向 this,而在未安装的时候指向宿主程序中的代理 Activity。总之,that is better than this。
  • Activity 的成员方法调用问题:原则上需要通过 that 来调用成员方法,但是由于大部分常用的 API 已经被重写,所以仅仅是针对部分 API 才需要通过 that 去调用。同时,APK 安装以后仍然可以正常运行。
  • 启动新 Activity 的约束:启动外部 Activity 不受限制,启动 APK 内部的 Activity 有限制。首先由于 APK 中的 Activity 没注册,所以不支持隐式调用;其次必须通过 BaseActivity 中定义的新方法 startActivityByProxy 和 startActivityForResultByProxy;还有就是不支持 LaunchMode。
  • 组件支持:目前暂不支持 Service、BroadcastReceiver 等需要注册才能使用的组件,但广播可以采用代码动态注册。

4. 插件 APK 的管理后台

使用动态加载的目的,就是希望可以绕过 APK 的安装过程升级应用的功能。如果插件 APK 是打包在主项目内部的那动态加载纯粹是多次一举。更多的时候我们希望可以在线下载插件 APK,并且在插件 APK 有新版本的时候,主项目要从下载最新的插件替换本地已经存在的旧插件。为此,我们应该有一个管理后台,它大概有以下功能:

  1. 上传不同版本的插件 APK,并向 APP 主项目提供插件 APK 信息查询功能和下载功能。
  2. 管理在线的插件 APK,并能向不同版本号的 APP 主项目提供最合适的插件 APK。
  3. 万一最新的插件 APK 出现紧急 BUG,要提供旧版本回滚功能。
  4. 出于安全考虑应该对 APP 项目的请求信息做一些安全性校验。

5. 插件 APK 合法性校验

加载外部的可执行代码,一个逃不开的问题就是要确保外部代码的安全性。我们可不希望加载一些来历不明的插件 APK,因为这些插件有的时候能访问主项目的关键数据。

最简单可靠的做法就是校验插件 APK 的 MD5 值。如果插件 APK 的 MD5 与我们服务器预置的数值不同,就认为插件被改动过,弃用。具体流程如下:

  1. 服务端存储已发布插件的 MD5 值。
  2. 客户端下载插件后计算本地文件的 MD5。
  3. 对比两者是否一致,不一致则拒绝加载并提示用户。

四、总结

DL 框架为 Android 插件化提供了一种相对成熟的解决方案,通过代理模式、类加载器隔离等技术实现了功能的动态扩展。开发者在使用时需注意主题设置、Context 引用、权限声明及安全性校验等关键点。合理运用该框架可以有效提升应用的灵活性和可维护性,但也需要严格遵循其开发规范以避免潜在的运行错误。

目录

  1. Android DL 插件化开发步骤与核心注意事项
  2. 一、利用 DL 框架进行开发的步骤
  3. 1. 获取项目代码
  4. 2. 引入 DL 库
  5. 宿主工程配置
  6. 插件工程配置
  7. 3. 插件 Java 代码修改
  8. 4. 调用的插件 APK
  9. 二、DL 框架优秀之处
  10. 处理插件项目里的 SO 库的加载
  11. 多插件 APK 的管理
  12. 三、DL 插件开发注意事项
  13. 1. 主题设置
  14. 2. 权限声明
  15. 3. 使用 DL 进行插件 APK 的开发规范
  16. 4. 插件 APK 的管理后台
  17. 5. 插件 APK 合法性校验
  18. 四、总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 基于 Java 的家政服务管理系统的设计与实现
  • Flutter 三方库 xpath_selector 的鸿蒙适配与 HTML 解析实战
  • llama.cpp 新增 Web UI,本地大模型部署性能与体验升级
  • Java 随机数生成实战:解析范围字符串与动态区间控制
  • 2025 年全球 AI 大模型格局:技术突破、开源崛起与未来趋势
  • RAG 进阶指南:15 种高级技术优化检索与生成
  • 纯 LLM、多模态大模型与 AIGC 就业方向对比分析
  • Rust 实现二维码艺术生成器:从原理到代码
  • Go2 机器人 ROS2 与 Gazebo 仿真环境搭建指南
  • FLUX.1-dev FP8 模型低显存部署实战指南
  • C++ 智能指针的使用及其原理
  • 在 macOS 上安装 OpenClaw 并实现 Chrome 网站自动化测试
  • OpenClaw 多飞书机器人绑定配置实战指南
  • Python 自动化文件整理与分类脚本实战指南
  • 从三年前端到 CS 硕士:韩国留学读研的得失复盘
  • Lada v0.11.0 更新:AI 视频去马赛克工具支持 Nvidia 与 Intel Arc
  • Llama 3.1 开源发布:LLM 新里程碑与部署指南
  • OpenHarmony 使用 shelf_web_socket 构建 WebSocket 服务端实战指南
  • EasyOCR Python 开源 OCR 工具使用指南
  • Docker 存储卷深度剖析:从创建到实战,掌握容器数据持久化

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online