Speed Tools:一套低侵入的 Android 插件化 + 动态换肤 + 字体切换框架

简介: Speed Tools 是一套低侵入 Android 框架,支持插件化(内存加载 dex,兼容 Android 14)、动态换肤与运行时字体切换。基于 AndroidX + AGP 8.8.2,开箱即用,无需改造现有工程。

Speed Tools:一套低侵入的 Android 插件化 + 动态换肤 + 字体切换框架

作者:一航
GitHub:jasonliyihang/speed_tools
博客首发于 CSDN,本文基于 2026 年最新代码重构整理。


一、前言

几年前我在 CSDN 写过一篇 [《android 插件化框架 speed-tools》],那时候框架只解决了"不安装 APK 就能加载页面"这一个核心诉求。几年过去,Android 系统经历了多次大版本迭代,插件化方案也面临新的挑战:

  • Android 10 收紧了私有目录访问;
  • Android 14 彻底禁止了加载可写 dex 文件
  • 业务侧对"动态换肤"和"全局字体调节"的需求越来越强烈。

于是我把整个项目从里到外升级了一遍:

  • 构建基线从 Support Library + compileSdk 28 升级到 AndroidX + compileSdk 35 + AGP 8.8.2
  • 类加载策略在 Android 8.0+ 切换为 InMemoryDexClassLoader(内存加载),天然规避 Android 14+ 的文件权限限制;
  • 新增 运行时换肤运行时字体切换两大能力。

本文会带你从 0 到 1 跑通整个 Demo,并理解背后的核心原理。


二、Speed Tools 能做什么?

一句话概括:一套面向 Android 的本地插件化框架,同时附带换肤和字体调节能力

能力 一句话说明 典型场景
插件化 宿主加载未安装的 APK,代理启动插件页面 多业务独立演进、按插件解耦
动态换肤 运行时加载皮肤包 APK,替换颜色/图片/背景 夜间模式、节日主题、品牌定制
字体调节 运行时全局调整字体大小,支持用户偏好持久化 无障碍适配、老年模式

2.1 为什么不用 Google Play Dynamic Delivery?

  • 国内应用商店生态复杂,很多渠道不支持 PAD;
  • 需要完全本地可控,不依赖外部服务;
  • 希望低侵入接入现有工程,而非改造成 Dynamic Feature Module 结构。

Speed Tools 的定位就是:本地可控、低侵入、开箱即用


三、工程结构一览

speed_tools/
├── lib_speed_tools/          # 核心库(插件加载、代理、换肤、字体)
├── module_host_main/         # 宿主示例 App
├── module_client_one/        # 插件示例 1
├── module_client_two/        # 插件示例 2
├── theme_demo/               # 换肤与字体切换演示 App
├── black_theme/              # 皮肤包示例(纯资源,无业务代码)
└── lib_img_utils/            # 第三方图片库测试模块
  • lib_speed_tools 是唯一的依赖入口,宿主和插件都只依赖它;
  • module_host_main 演示了如何加载插件并跳转;
  • theme_demo 演示了换肤和字体切换的完整链路。

四、10 分钟跑通 Demo

4.1 环境要求

  • Android Studio(推荐最新稳定版)
  • JDK 17
  • compileSdk 35 / minSdk 21 / targetSdk 35

4.2 编译插件和皮肤包

# 编译插件 APK
./gradlew :module_client_one:assembleDebug
./gradlew :module_client_two:assembleDebug

# 编译皮肤包
./gradlew :black_theme:assembleDebug

4.3 放置 APK 到 assets

把编译产物复制到对应目录:

module_host_main/src/main/assets/
    ├── module_client_one-debug.apk
    └── module_client_two-debug.apk

theme_demo/src/main/assets/
    └── black_theme-debug.apk

4.4 运行

  1. 选择运行配置 module_host_main → 启动后自动加载插件,点击按钮进入插件页面;
  2. 选择运行配置 theme_demo → 依次体验:切换黑色主题 → 恢复默认 → 放大字体 → 恢复字体。

到这里,你已经亲眼见证了框架的三项核心能力。


五、插件化核心原理

5.1 整体架构

┌─────────────────────────────────────────┐
│              宿主 APK                    │
│  ┌─────────┐   ┌─────────────────────┐  │
│  │ Assets  │──▶│ SpeedApkManager     │  │
│  │ (插件)  │   │ (类加载 + 资源桥接)  │  │
│  └─────────┘   └─────────────────────┘  │
│                     │                    │
│                     ▼                    │
│  ┌───────────────────────────────────┐  │
│  │       代理 Activity                │  │
│  │  (转发 onCreate/onResume/onDestroy)│  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘
                    │
                    ▼
┌─────────────────────────────────────────┐
│            插件 APK(未安装)              │
│  ┌──────────────┐   ┌──────────────┐   │
│  │ 业务实现类    │   │    res/      │   │
│  │(继承接口)    │◀──│  (资源文件)  │   │
│  └──────────────┘   └──────────────┘   │
└─────────────────────────────────────────┘

5.2 类加载:InMemoryDexClassLoader 如何规避 Android 14 限制

Android 14 引入了一条硬性限制:禁止加载可写的 dex 文件。旧方案使用 DexClassLoader 直接加载 APK 路径,如果文件权限是可写的,就会抛出:

java.lang.SecurityException: Writable dex file ... is not allowed.

Speed Tools 的解决思路很直接:

  • Android 8.0+(API 26):从 APK 中解压出 classes.dex,读取到内存 ByteBuffer,通过 InMemoryDexClassLoader 加载。dex 数据完全在内存中,不走文件系统,自然不受"可写"限制。
  • Android 5.0~7.1(API 21~25):回退到传统的 DexClassLoader,这些旧版本没有此限制。

核心代码片段(SpeedUtils.java):

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
   
    ByteBuffer[] dexBuffers = extractDexBuffersFromApk(apkPath);
    return new InMemoryDexClassLoader(dexBuffers, appContext.getClassLoader());
} else {
   
    return new DexClassLoader(apkPath, optimizedDir, nativeLibDir, parent);
}

5.3 资源桥接

插件的 res/ 资源如何被宿主识别?答案是反射创建 AssetManager

AssetManager assetManager = AssetManager.class.getDeclaredConstructor().newInstance();
Method addAssetPath = assetManager.getClass().getDeclaredMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, apkPath);

Resources pluginRes = new Resources(assetManager, hostMetrics, hostConfig);

宿主代理 Activity 持有这份 pluginRes,插件页面里的 setContentView(R.layout.xxx) 就能正确找到插件自己的布局了。

5.4 生命周期转发

插件业务类不直接继承 Activity,而是继承 SpeedBaseInterfaceImp,实现一套与 Activity 对应的生命周期接口。宿主端的 SpeedHostBaseActivity 作为"壳",在 onCreateonResumeonDestroy 等节点调用插件实现类的对应方法,完成生命周期转发。


六、插件化接入实战

6.1 宿主侧:加载插件

// 优先从外部目录查找,fallback 到 assets 拷贝
File apkFile = SpeedUtils.resolvePluginApk(
        context, "/sdcard/Download", 
        "module_client_one-debug.apk"
);

SpeedApkManager.getInstance().loadApk(
        "first_apk",           // 插件 key
        apkFile.getAbsolutePath(), 
        "dex_output2",         // dex 优化目录(每个插件独立)
        context
);

6.2 宿主侧:跳转插件

SpeedUtils.goActivity(this, "first_apk", null);

第三个参数 classTag 对应插件 AndroidManifest.xmlmeta-dataname,为空时走默认入口。

6.3 插件侧:声明入口

<application>
    <meta-data
        android:name="root_class"
        android:value="com.example.clientdome.ClientMainActivity" />
</application>

插件业务类需要实现 SpeedBaseInterface,代理层会通过反射实例化这个类。


七、换肤与字体切换

7.1 核心设计

换肤和字体调节的本质是资源替换

  • 换肤:拦截 LayoutInflater 创建 View 的过程,把颜色/图片资源替换为皮肤包中的同名资源;
  • 字体:拦截 textSize 属性的读取,在基础值上叠加用户设置的偏移量。

为了框架能识别哪些资源需要被替换,规定了强制前缀:

类型 前缀 示例
颜色/背景/图片 cxt_ @color/cxt_primary
字体维度 cxf_ @dimen/cxf_normal

7.2 三步接入

Step 1:Application 初始化

@Override
public void onCreate() {
   
    super.onCreate();
    SPFontManager.getInstance().init(this);
    SPThemeManager.getInstance().init(this);
}

Step 2:Activity 注册监听

public class BaseActivity extends AppCompatActivity implements SPUpdateUIListener {
   

    @Override
    protected void onCreate(Bundle savedInstanceState) {
   
        SPThemeManager.getInstance().registerUpdateUI(this);
        super.onCreate(savedInstanceState);
    }

    @Override
    protected void onDestroy() {
   
        SPThemeManager.getInstance().unRegisterUpdateUI(this);
        super.onDestroy();
    }

    @Override
    public void updateUI(boolean isFistLoading) {
   
        // 自定义控件手动刷新
    }
}

Step 3:触发切换

// 换肤
SPThemeManager.getInstance()
        .changeTheme("black_theme-debug.apk")
        .sendUpdateUIAction();

// 字体放大
SPFontManager.getInstance().changeConfig(40).updateUI();

7.3 皮肤包怎么制作?

皮肤包就是一个只包含资源、不含业务代码的普通 Android App 模块:

  1. 新建 Android App 模块;
  2. res/ 中放置与主工程同名cxt_* / cxf_* 资源;
  3. 打包生成 APK。

主工程和皮肤包的资源名必须完全一致,值可以不同。运行时 SPThemeManager 会读取皮肤包 APK 的资源,通过 AssetManager.addAssetPath 建立资源上下文,完成替换。


八、踩坑记录

8.1 Android 14 上插件加载失败

现象SecurityException: Writable dex file is not allowed
根因:Android 14 禁止加载可写 dex 文件
解决:框架已在 Android 8.0+ 自动切换 InMemoryDexClassLoader,无需业务侧改动。如果仍在维护旧版本框架,可参考本文 5.2 节的实现思路自行迁移。

8.2 换肤后页面没有变化

按这个清单逐条排查:

  1. Application 是否初始化了 SPThemeManagerSPFontManager
  2. Activity 是否 registerUpdateUI / unRegisterUpdateUI
  3. 资源名是否用了 cxt_ / cxf_ 前缀;
  4. 是否调用了 sendUpdateUIAction() 触发刷新。

8.3 插件页面白屏

  • 检查插件 AndroidManifest.xmlmeta-data 入口声明是否正确;
  • 检查插件资源是否齐全;
  • 检查宿主和插件依赖的 lib_speed_tools 版本是否一致。

九、从旧版本迁移

如果你还在使用早期的 com.liyihangjson:speed_tools:1.1.1 Maven 依赖,建议迁移到源码依赖

// settings.gradle
include ':lib_speed_tools'

// app/build.gradle
dependencies {
    implementation project(':lib_speed_tools')
}

迁移收益:

  • 直接获得 Android 14 兼容性修复;
  • 获得换肤和字体切换能力;
  • 构建基线同步升级到 AndroidX + AGP 8.x。

十、总结

Speed Tools 从最早的"纯插件化"框架,逐步演变成了插件化 + 换肤 + 字体调节的三位一体工具集。本次升级的核心是适配新时代 Android 系统的安全限制(Android 14 的 dex 文件权限),同时响应业务侧对动态 UI 的诉求。

项目完全开源,欢迎 Star 和 PR。


如有问题,欢迎在 GitHub Issues 交流。

相关文章
|
1月前
|
人工智能 JSON 机器人
构建 Gerrit AI 代码审查机器人:从零到自动部署
本文介绍如何从零构建Gerrit AI代码审查机器人:通过SSH监听Gerrit事件流,自动获取变更文件,调用大模型进行结构化代码审查(识别高/中/低风险问题),并以Markdown评论+Code-Review打分形式自动提交结果。含断线重连、Base64解码、JSON严格解析等关键实现,支持手动测试与7×24自动部署。(239字)
|
1月前
|
机器学习/深度学习 人工智能 编解码
140.逆向提示工程深度解析:理解反推提示词PRE技术核心逻辑,轻松还原AI提示词.140
反推提示词技术(RPE)是一种逆向工程方法:给定AI生成的优质内容,通过解构其结构、风格、格式等特征,反向还原最可能生成该结果的提示词。它涵盖文本、图像、代码等多模态,核心流程为“样本分析→特征拆解→约束识别→提示构建→验证迭代”,旨在提炼高质量提示范式,提升正向提示工程能力与模型可控性。
255 2
|
1月前
|
人工智能 API 网络安全
阿里云无影云电脑部署Hermes Agent 配置百炼 Token Plan 保姆级教程
部署无影云电脑、Hermes Agent并对接百炼Token Plan,首要前提是拥有阿里云账号并完成实名认证。个人用户可通过支付宝刷脸快速完成认证,全程仅需10秒;企业用户需上传营业执照及法人信息,审核周期1-3个工作日。未完成实名认证的账号无法购买无影云电脑、开通百炼大模型服务,这是所有操作的基础门槛。
253 0
|
1月前
|
机器学习/深度学习 自然语言处理 安全
从零构建车载语音对话系统:NLU → DST → Policy → NLG → TTS 全链路工程实践
本文详解车载语音助手全链路工程实践,涵盖NLU(意图识别+槽位抽取)、DST(多轮状态追踪)、Policy(安全驱动决策)、NLG(模板化自然语言生成)与TTS(双引擎语音合成)五大模块,基于Pipeline架构实现高可解释、可调试、强安全的工业级Demo,代码开源、开箱即用。(239字)
|
移动开发 ARouter 开发工具
开源最佳实践:Android平台页面路由框架ARouter
为了更好地让开发者们更加深入了解阿里开源,阿里云云栖社区在3月1号了举办“阿里开源项目最佳实践”在线技术峰会,直播讲述了当前阿里新兴和经典开源项目实战经验以及背后的开发思路,在本次在线技术峰会上,阿里云资深开发工程师刘志龙分享了Android平台页面路由框架ARouter的技术方案、解决的问题以及在实际场景中的最佳实践。
48430 2
|
1月前
|
存储 人工智能 弹性计算
阿里云服务器2核2G、2核4G、4核8G配置最低价格与选购参考:配置、价格、使用场景解析
本文介绍了阿里云五款高性价比云服务器:①轻量应用服务器2核2G,38元/年(新用户抢购),200M带宽,适合个人建站与AI应用尝鲜;②轻量应用服务器2核4G,199元/年,9.9元/月可选,预装OpenClaw镜像,可快速部署AI助理;③经济型e实例2核2G,99元/年且续费同价,适合个人开发者与小微企业;④通用算力型u1实例2核4G,199元/年,企业独享算力,适合Web应用与办公系统;⑤通用算力型u2i实例4核8G,约1620元/年,适合中小型企业核心业务。五款产品定位清晰,从入门到企业级全覆盖,用户可按需选购。
|
编解码 安全 Android开发
如何修复 Android 和 Windows 不支持视频编解码器的问题?
视频播放时遇到“编解码器不支持”错误(如0xc00d36c4或0xc00d5212)是常见问题,即使文件格式为MP4或MKV。编解码器是编码和解码数据的工具,不同设备和版本支持不同的编解码器。解决方法包括:1) 安装所需编解码器,如K-Lite Codec Pack;2) 使用自带编解码器的第三方播放器,如VLC、KMPlayer等。这些方法能帮助你顺利播放视频。
|
存储 索引
【数据结构】HashSet的底层数据结构
【数据结构】HashSet的底层数据结构
849 2
|
人工智能 数据可视化 API
自动查文献+写代码+跑数据+出报告!港大开源 Auto Deep Research 搞定科研全流程
Auto-Deep-Research 是一款由香港大学开源的个人 AI 助理,基于模块化多 Agent 架构,专注于深度研究任务,兼容多种大语言模型,并提供一键启动和文件解析等强大功能。
1712 4
自动查文献+写代码+跑数据+出报告!港大开源 Auto Deep Research 搞定科研全流程
|
机器学习/深度学习 数据挖掘 PyTorch
图像分类经典神经网络大总结(AlexNet、VGG 、GoogLeNet 、ResNet、 DenseNet、SENet、ResNeXt )
图像分类经典神经网络大总结(AlexNet、VGG 、GoogLeNet 、ResNet、 DenseNet、SENet、ResNeXt )
14915 1
图像分类经典神经网络大总结(AlexNet、VGG 、GoogLeNet 、ResNet、 DenseNet、SENet、ResNeXt )