# Speed Tools
Android 本地插件化 + 动态换肤 + 字体切换 一体化框架
中文 · English
> **设计目标**:低侵入接入、多业务解耦、避免 Google Play 动态交付依赖。 > > **接入方式**:当前推荐 **源码依赖**,不再推荐旧 Maven 坐标 `com.liyihangjson:speed_tools:1.1.1`。 --- ## 📋 目录 - [特性概览](#-特性概览) - [架构原理](#-架构原理) - [工程结构](#-工程结构) - [环境要求](#-环境要求) - [快速开始:10 分钟跑通 Demo](#-快速开始10-分钟跑通-demo) - [插件化接入手册](#-插件化接入手册) - [换肤与字体切换手册](#-换肤与字体切换手册) - [API 速查表](#-api-速查表) - [常见问题与排障](#-常见问题与排障) - [生产落地建议](#-生产落地建议) - [版本与兼容说明](#-版本与兼容说明) - [相关文档](#-相关文档) --- ## ✨ 特性概览 | 特性 | 说明 | 典型场景 | |---|---|---| | **插件化** | 宿主动态加载未安装的 APK,代理启动插件页面 | 多业务模块独立演进、按插件解耦 | | **动态换肤** | 运行时加载皮肤包 APK,替换颜色/图片/背景资源 | 夜间模式、节日主题、品牌定制 | | **字体调节** | 运行时全局调整字体大小,支持用户偏好持久化 | 无障碍适配、老年模式 | --- ## 🏗️ 架构原理 ``` ┌─────────────────────────────────────────────────────────────┐ │ 宿主 APK │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Assets │ │ SpeedUtils │ │ 代理 Activity │ │ │ │ (插件APK) │───▶│ (拷贝/加载) │───▶│(生命周期转发)│ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ SpeedApkManager (单例) │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ 类加载器 │ │ Resources │ │ PackageInfo │ │ │ │ │ │(内存/文件) │ │ (资源桥接) │ │ (元数据) │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 插件 APK (未安装) │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Plugin页面 │ │ res/ │ │ AndroidManifest│ │ │ │ (实现类) │◀───│ (资源文件) │ │ (入口声明) │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` **核心流程**: 1. 插件 APK 置于宿主 `assets/`(或外部下载目录); 2. 运行时拷贝到宿主私有目录(开发调试用 `assets`,生产建议下载+校验); 3. **类加载**:Android 8.0+ 使用 `InMemoryDexClassLoader`(内存加载,规避 Android 14+ 文件权限限制),低版本回退 `DexClassLoader`; 4. **资源桥接**:反射创建 `AssetManager`,建立插件 `Resources` 上下文; 5. **代理转发**:宿主通过统一接口跳转,由代理 `Activity` 将生命周期转发给插件实现类。 --- ## 📁 工程结构 ``` 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` | Library | 核心能力(插件加载、代理、换肤、字体) | | `module_host_main` | App | 宿主示例(加载并启动插件) | | `module_client_one` | App | 插件示例 1 | | `module_client_two` | App | 插件示例 2 | | `theme_demo` | App | 换肤与字体切换示例 | | `black_theme` | App | 皮肤包示例 | | `lib_img_utils` | Library | 第三方库测试模块 | --- ## 🛠️ 环境要求 - **JDK**:17+ - **Android Studio**:最新稳定版(推荐 Koala 及以上) - **compileSdk**:35 - **minSdk**:21(Android 5.0) - **targetSdk**:35 - **AGP**:8.8.2 - **Gradle**:8.10+ - **必须启用**:`android.useAndroidX=true` --- ## 🚀 快速开始:10 分钟跑通 Demo > 以下步骤可直接在本仓库验证,无需额外新建工程。 ### Step 1:编译插件 APK ```bash # 编译两个插件示例 ./gradlew :module_client_one:assembleDebug ./gradlew :module_client_two:assembleDebug # 编译皮肤包 ./gradlew :black_theme:assembleDebug ``` 编译产物路径: - `module_client_one/build/outputs/apk/debug/module_client_one-debug.apk` - `module_client_two/build/outputs/apk/debug/module_client_two-debug.apk` - `black_theme/build/outputs/apk/debug/black_theme-debug.apk` ### Step 2:放置插件到宿主 将上面编译出的两个插件 APK 复制到宿主 assets 目录: ``` module_host_main/src/main/assets/ ├── module_client_one-debug.apk └── module_client_two-debug.apk ``` 将皮肤包 APK 复制到 theme_demo assets 目录: ``` theme_demo/src/main/assets/ └── black_theme-debug.apk ``` ### Step 3:运行宿主 在 Android Studio 中选择运行配置 **`module_host_main`**,点击运行。 启动后宿主会自动在后台加载插件,加载成功后界面会显示两个按钮,分别点击进入两个插件页面。 ### Step 4:运行换肤演示 选择运行配置 **`theme_demo`**,点击运行。 按界面提示依次体验:切换黑色主题 → 恢复默认 → 放大字体 → 恢复字体。 --- ## 🔌 插件化接入手册 ### 总体流程 ``` 宿主准备 APK (assets / 下载) │ ▼ 拷贝到私有目录(resolvePluginApk) │ ▼ 加载插件(SpeedApkManager.loadApk) │ ▼ 创建插件 ClassLoader + Resources │ ▼ 跳转插件页面(SpeedUtils.goActivity) │ ▼ 代理 Activity 转发生命周期 ``` ### 1. 宿主依赖 **`settings.gradle`** ```gradle include ':lib_speed_tools' ``` **宿主 `build.gradle`** ```gradle dependencies { implementation project(':lib_speed_tools') } ``` ### 2. 宿主加载插件(推荐模板) ```java private static final String EXTERNAL_APK_DIR = "/sdcard/Download"; private static final String FIRST_PLUGIN_APK = "module_client_one-debug.apk"; private static final String FIRST_APK_KEY = "first_apk"; private boolean loadPlugin(String key, String assetFileName, String dexOutKey) { // 优先从外部目录查找,fallback 到 assets 拷贝 File apkFile = SpeedUtils.resolvePluginApk( getApplicationContext(), EXTERNAL_APK_DIR, assetFileName); if (apkFile == null) { Log.e(TAG, "Plugin APK not found: " + assetFileName); return false; } return SpeedApkManager.getInstance().loadApk( key, apkFile.getAbsolutePath(), dexOutKey, getApplicationContext()); } ``` **参数说明**: | 参数 | 含义 | 建议 | |---|---|---| | `key` | 插件唯一标识 | 按业务域命名,如 `biz_order_v1` | | `apkPath` | APK 绝对路径 | 确保可读 | | `dexOutKey` | dex 优化目录名 | 每个插件独立目录,避免冲突 | ### 3. 跳转插件页面 ```java SpeedUtils.goActivity(this, "first_apk", null); ``` | 参数 | 说明 | |---|---| | `activity` | 宿主 Activity | | `"first_apk"` | 加载时的插件 key | | `null` | 类标识,为空时走默认入口(需在插件 Manifest meta-data 中声明) | ### 4. 插件工程规范 **目录结构建议**: ``` 插件模块/ ├── src/main/java/ │ └── PluginMainActivity.java # 页面实现 │ └── PluginEntry.java # 入口逻辑(供代理调用) ├── src/main/res/ # 插件资源 └── src/main/AndroidManifest.xml # 声明入口 meta-data ``` **入口声明示例**(`AndroidManifest.xml`): ```xml