Android模块化工具箱源码解析:注册表驱动与二次开发实践

发布时间:2026/10/19 4:07:03
Android模块化工具箱源码解析:注册表驱动与二次开发实践 简介一个工具箱APP的完整Android源码项目面向初中级开发者适合作为综合性应用实战参考。压缩包共169个文件含56个xml布局、54个java逻辑代码、44个png图标素材另有gradle构建脚本与配置文件整体约857KB结构清晰便于逐模块阅读。已有1211人学习下载。源码覆盖多模块工具箱的典型实现通过Intent组织页面跳转、Service处理后台任务、BroadcastReceiver响应系统广播、运行时权限申请适配高版本系统UI层使用常见布局与自定义View技巧并涉及SQLite、SharedPreferences的数据存储方式以及Adapter与RecyclerView的列表展示和事件监听处理。对图片加载、网络请求等常见需求也能从依赖或代码中找到借鉴。这份资源把Android开发中分散的知识点串联成完整应用实例无论学习架构设计还是复用具体功能代码都能提供直接参考。1. 一个工具箱为什么我用它替代了十几个独立APP我手机里曾装过一堆零散小工具查快递一个、算房贷一个、转时间戳一个、生成二维码又一个用一次装一个最后占了三个桌面页。后来我拿到这套 Android 工具箱 APP 源码把它跑起来之后才发现真正该做的不是收集工具而是做一个能装工具的容器。这个项目把常用小工具统一收进一个 APP通过注册表动态加载新增功能不用动主页面写一个类、加一行配置就行。对想练 Android 模块化开发的新手或者需要一个可复用工具框架的开发者来说它比东拼西凑下载十几个 Demo 值得多。下面我把它从结构、跑通到改造逐层拆开。2. 项目结构拆解模块化工具箱的代码组织方式拿到源码第一件事不是点开 Android Studio而是先把目录结构过一遍。这个工具箱的做法是“壳”和“工具”分离壳负责界面、导航、主题工具类只负责具体功能。这样一来工具之间互不干扰删掉某个工具不影响其他部分。我拆这个项目时最关心的就是它的注册机制是怎么做到“不碰主界面也能加功能”的。2.1 入口与主界面MainActivity 如何加载工具列表先看整体目录结构这个项目用的是单 Activity 多工具页的写法主界面只是一个列表容器app/src/main/ ├── java/com/example/box/ │ ├── MainActivity.java │ ├── model/ │ │ └── ToolItem.java │ ├── tool/ │ │ ├── TimestampTool.java │ │ ├── QrCodeTool.java │ │ ├── BaseTool.java │ │ └── ToolRegistry.java │ ├── util/ │ │ └── ThemeUtil.java ├── res/ │ ├── layout/ │ │ ├── activity_main.xml │ │ └── item_tool.xml │ └── values/ │ ├── colors.xml │ └── strings.xmlMainActivity 负责绑定 RecyclerView从 ToolRegistry 里拿到工具数据渲染出列表。首次启动时它还会检查主题缓存确保用户上次选的主题直接生效。整个主界面代码不到一百行没有把任何工具逻辑写进去。public class MainActivity extends AppCompatActivity { private ToolAdapter adapter; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 主题必须放在 setContentView 之前否则会闪一下默认主题 ThemeUtil.applyTheme(this); setContentView(R.layout.activity_main); RecyclerView recyclerView findViewById(R.id.recycler_view); LinearLayoutManager manager new LinearLayoutManager(this); recyclerView.setLayoutManager(manager); ListToolItem tools ToolRegistry.getTools(); adapter new ToolAdapter(tools, item - { // 点击后根据 toolId 找到对应工具页面 Intent intent new Intent(this, ToolActivity.class); intent.putExtra(tool_id, item.getId()); startActivity(intent); }); recyclerView.setAdapter(adapter); } }这里最值得学习的是 Loader 与列表解耦的思路。MainActivity 只依赖 ToolRegistry.getTools() 的返回值至于这些工具是写死还是扫描出来的主界面完全不用关心。item - {} 这个 Lambda 是点击回调通过 tool_id 跳转到一个通用 ToolActivityToolActivity 再根据 id 去 ToolRegistry 查具体实现。也就是说以后你新增十个工具MainActivity 一行都不用改。布局方面activity_main.xml 就是一个普通的 RecyclerView没什么特别。item_tool.xml 我拆开看了左边一个图标中间工具名右边一个小箭头整体用 LinearLayout 写的没有嵌套权重渲染性能还可以。如果要做动态排序可以把这个 item 改成 SwipeRefreshLayout 配合拖拽这是后话。2.2 工具注册机制用配置表驱动工具列表这个项目没有用插件化也没有用注解扫描而是老老实实用一个静态注册表。ToolRegistry 里面维护了一个工具集合每个工具对应一个 ToolItem 元数据和一个类入口。这种写法虽然不如动态加载酷但胜在稳定、好排查很适合工具类 APP。public class ToolRegistry { private static final ListToolItem TOOLS Arrays.asList( new ToolItem(timestamp, 时间戳转换, 毫秒/秒互转支持当前时间快速填充, R.drawable.ic_time), new ToolItem(qrcode, 二维码生成器, 文本转二维码可调整尺寸和容错率, R.drawable.ic_qrcode) ); public static ListToolItem getTools() { return TOOLS; } public static BaseTool createTool(String id) { switch (id) { case timestamp: return new TimestampTool(); case qrcode: return new QrCodeTool(); default: return null; } } }ToolItem 是一个普通 POJO包含 id、名称、描述和图标资源。createTool(String id) 是工厂方法按 id 返回对应的 BaseTool 实例。BaseTool 是一个抽象类定义了 init()、getContentView()、onDestroy() 这几个生命周期方法。每个具体工具只需要继承 BaseTool实现自己的界面和逻辑然后交给 ToolActivity 去挂载。这种注册表的好处是工具列表的排序、增删都在一处修改一目了然。缺点是不支持运行时加载不太可能做热插拔但桌面工具 APP 要热插拔本来就有点玄学没必要。我自己在做重构时会把 id 抽成常量类避免字符串写死到处飘。2.3 核心工具实现以时间戳转换和二维码生成器为例时间戳工具是所有工具箱里最高频的功能我看这个项目实现得很干净。它把输入框、单选按钮、结果展示放在一个 Fragment 里但不是必须用 Fragment直接放 View 也行。public class TimestampTool extends BaseTool { private EditText inputView; private TextView resultView; private RadioGroup typeGroup; Override public View onCreateView(LayoutInflater inflater, ViewGroup parent) { View view inflater.inflate(R.layout.tool_timestamp, parent, false); inputView view.findViewById(R.id.input); resultView view.findViewById(R.id.result); typeGroup view.findViewById(R.id.type_group); Button convertBtn view.findViewById(R.id.convert_btn); convertBtn.setOnClickListener(v - convert()); return view; } private void convert() { String text inputView.getText().toString().trim(); if (text.isEmpty()) { resultView.setText(请输入时间戳或日期); return; } int type typeGroup.getCheckedRadioButtonId(); String result parseTimestamp(text, type); resultView.setText(result); } private String parseTimestamp(String text, int type) { try { long timestamp Long.parseLong(text); // 当前默认按秒处理毫秒需要除以1000 SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd HH:mm:ss, Locale.CHINA); if (type R.id.type_second) { return sdf.format(new Date(timestamp * 1000)); } else { return sdf.format(new Date(timestamp)); } } catch (NumberFormatException e) { // 不是数字时按日期转时间戳处理 SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd HH:mm:ss, Locale.CHINA); try { return String.valueOf(sdf.parse(text).getTime() / 1000); } catch (ParseException ex) { return 无法识别的格式请用时间戳或 yyyy-MM-dd HH:mm:ss; } } } }这段代码核心在 parseTimestamp 的双向转换逻辑。当输入是纯数字就按时间戳转日期否则按日期转时间戳。有一个细节要提醒工具箱多数日期输入不是毫秒用户贴的往往是秒所以默认乘 1000。如果你要支持毫秒就加一个选项不要默认不然习惯了秒的用户会翻车。二维码生成器实现也很短用 ZXing 的 QRCodeWriter 生成 BitMatrix再转成 Bitmap 显示在 ImageView 上。我认为这里值得学的是它把生成逻辑放进了工具类而不是写在 Activity 里这样后面换生成库也只需要动一个文件。public class QrCodeTool extends BaseTool { private ImageView qrView; Override public View onCreateView(LayoutInflater inflater, ViewGroup parent) { View view inflater.inflate(R.layout.tool_qrcode, parent, false); qrView view.findViewById(R.id.qr_image); EditText input view.findViewById(R.id.qr_text); Button generateBtn view.findViewById(R.id.generate_btn); generateBtn.setOnClickListener(v - { String content input.getText().toString().trim(); if (content.isEmpty()) { Toast.makeText(getContext(), 内容不能为空, Toast.LENGTH_SHORT).show(); return; } Bitmap bitmap generateQr(content, 512); qrView.setImageBitmap(bitmap); }); return view; } private Bitmap generateQr(String content, int size) { try { MapEncodeHintType, Object hints new HashMap(); // 纠错级别选 H容错率更高 hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); hints.put(EncodeHintType.CHARACTER_SET, UTF-8); QRCodeWriter writer new QRCodeWriter(); BitMatrix matrix writer.encode(content, BarcodeFormat.QR_CODE, size, size, hints); int width matrix.getWidth(); Bitmap bmp Bitmap.createBitmap(width, width, Bitmap.Config.RGB_565); for (int x 0; x width; x) { for (int y 0; y width; y) { // 黑色设为 ARGB 不透明黑白色用透明背景 bmp.setPixel(x, y, matrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF); } } return bmp; } catch (WriterException e) { return Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565); } } }生成二维码时我一般会固定 UTF-8 编码否则中文内容到部分扫码枪上会变成乱码。容错率这里选了 H虽然二维码密度会高一些但扫码识别率明显更好尤其是用户打印后可能出现折痕和污渍的场景。size 传 512 是权衡值太小会在密集内容时糊掉太大内存翻倍512 在多数中低端机上够用。3. 编译运行全流程从源码到 APK 的落地步骤源码拿到手最重要的一步是让它真正跑起来。很多人在这一步被 Gradle 版本、SDK 版本卡住然后放弃。我建议先按项目原始配置跑通一次再根据自己的环境去升级不要一开始就动依赖版本。3.1 环境要求与依赖配置这个工具箱项目是基于 Android 11 编译的也就是 API 30最低支持到 API 21基本上覆盖市面上还在用的设备。构建工具用的是 Gradle 7.0.2Android Gradle Plugin 版本是 7.0.0Java 版本要求 8 以上。我在实际编译时用的 JDK 11完全没问题。android { compileSdk 30 defaultConfig { applicationId com.example.box minSdk 21 targetSdk 30 versionCode 1 versionName 1.0 } } dependencies { implementation androidx.appcompat:appcompat:1.3.0 implementation com.google.android.material:material:1.4.0 implementation androidx.constraintlayout:constraintlayout:2.0.4 implementation com.google.zxing:core:3.4.1 }compileSdk 和 targetSdk 都用 30 是最稳妥的因为项目里用到的 Material 控件在低版本上会有行为差异。如果你本地只装了 SDK 34也不要急着改 compileSdk可以先在 SDK Manager 里下载 30这样能避免一些 API 变更导致的报错。常踩的坑是依赖版本冲突appcompat 和 material 都依赖 annotation 库如果你手动加了旧版 annotation会导致编译时 duplicate class。这个项目默认依赖没冲突不需要额外处理。3.2 构建命令与常见编译问题跑构建我推荐直接用命令行比 Android Studio 里来回点按钮更能看清楚错误原因。在项目根目录执行./gradlew assembleDebug第一次构建会下载 Gradle 和依赖通常需要几分钟。如果下载卡住检查是不是网络镜像问题国内一般会配置阿里云的 maven 镜像。项目里的 build.gradle 如果有仓库地址直接替换成国内的allprojects { repositories { maven { url https://maven.aliyun.com/repository/google } maven { url https://maven.aliyun.com/repository/public } maven { url https://maven.aliyun.com/repository/gradle-plugin } } }常见编译失败有这么几类一是 Gradle 版本不匹配报错类似“Minimum supported Gradle version is X”解决方式是打开 gradle-wrapper.properties把 distributionUrl 改成项目要求的版本二是 res 资源命名下划线开头编译器会报 Invalid resource name改名即可三是 Java 版本太高导致某些旧库访问出错把 JDK 切到 11 一般就能过。遇到这些问题别急着改功能先把环境对齐。3.3 安装到真机的注意事项生成 debug APK 后如果用的是 USB 调试安装需要先开启开发者选项有些国产 ROM 还要在弹窗里选择“允许通过 USB 安装应用”。我最初在这上面吃了不少苦头以为代码有问题其实只是没在手机上确认安装授权。adb install app/build/outputs/apk/debug/app-debug.apk如果你用 adb install 报 INSTALL_FAILED_UPDATE_INCOMPATIBLE是因为旧版本签名不一致先执行 adb uninstall com.example.box 再重装。另外这个项目没有做网络请求所以不需要在 AndroidManifest 里加网络权限但若你在真机上拔插 USB 导致 apk 路径变化重新执行一次 install 就行。安装完成后打开 APP看到工具列表就说明跑通了。4. 工具箱常见避坑指南我调试三天总结的五个问题这章集中写我实际调试这个项目时踩过的坑。有些坑不跑真机根本发现不了还有些是代码里埋的雷写出来省得你重复走一遍。4.1 问题一工具页面刷新空白现象点击某个工具进入 ToolActivity页面只有一个标题栏内容区域是空白的控制台没有报错。原因ToolActivity 在创建工具时拿错了 id 对应的工具实例。我在 ToolRegistry.createTool 里用到 switch 语句但项目里多了一个测试工具最后 case 漏写了 return null 的兜底。更隐蔽的是有些工具类的 onCreateView 里没有 inflate 布局就直接返回了空 View。解决在 ToolActivity 里加一个空判断工具为空时显示友好的提示而不是崩溃。同时检查 BaseTool.onCreateView 是否真的把布局 inflate 出来我建议你在 createTool 里打一行日志输出 id 和返回类名定位很快。4.2 问题二扫码功能在部分机型上无法对焦现象二维码生成正常但用手机自带相机扫码时在真机上对焦很慢近一点就糊。原因这个问题不在这个项目里而是 ZXing 生成二维码时没有加静默区白边为 0扫码枪在找定位图案时容易被边缘干扰。解决生成二维码时增加 margin。ZXing 的 encode 方法支持 MARGIN 参数设置为 4 或者 8二维码四周就会有白边。改完后再打印出来试试识别率明显上升。4.3 问题三ProGuard 混淆导致工具类失效现象用 release 包运行时点击工具没有反应logcat 里报 ClassNotFoundException。原因混淆规则没有把工具类保留ToolRegistry 里的 switch 引用的类被删除了。因为 BaseTool 的子类是通过字符串映射创建的ProGuard 认为没有直接引用就把它优化掉了。解决在 proguard-rules.pro 里添加 keep 规则把 tool 包和 model 包都保留。这个坑很经典只要是用反射或工厂方式创建对象都要额外加 keep 规则。4.4 问题四主题切换后返回键颜色异常现象在深色主题下状态栏图标和返回键变成深色看不清。原因ThemeUtil 里只处理了 setStatusBarColor没有处理系统栏图标模式。深色背景还是浅色图标自然看不见。解决给状态栏和导航栏按主题类型判断深色主题用浅色图标浅色主题用深色图标。代码里用 WindowInsetsControllerCompat 来控制这是 Android 11 的标准做法。4.5 问题五内存泄漏导致工具切换卡顿现象在工具箱里反复切换三四个工具之后滑动列表越来越卡。原因工具类在 onDestroy 里没有释放监听器和 Bitmap。Zen 模式打开后生成二维码的 Bitmap 一直被静态引用导致每次进入都重新加载一个对象。解决BaseTool 增加 onDestroy 抽象方法具体工具把 ImageView 置空Bitmap 调用 recycle。ToolActivity 在 onDestroy 里调用工具的 onDestroy并把实例置为 null。5. 二次开发新增一个工具的完整改造流程这部分是重点因为会拿这个项目来用的多半都需要自己加工具。我这里以新增一个“JSON 格式化工具”为例完整跑一遍流程。5.1 定义工具元信息ToolRegistry 的 TOOLS 列表里加一项元信息包含 id、名称、描述和图标new ToolItem(json_formatter, JSON 格式化, 对 JSON 字符串进行缩进和校验, R.drawable.ic_json)图标放在 res/drawable 下没有现成的可以先随便用一个系统图标顶上。5.2 注册到工具列表createTool 方法里加一个分支返回 JSONFormatterTool 的实例。case json_formatter: return new JSONFormatterTool();然后新建 JSONFormatterTool 类继承 BaseTool实现一个带输入和输出的界面。核心逻辑是使用 org.json 库解析和重排public class JSONFormatterTool extends BaseTool { Override public View onCreateView(LayoutInflater inflater, ViewGroup parent) { View view inflater.inflate(R.layout.tool_json_formatter, parent, false); EditText input view.findViewById(R.id.json_input); TextView output view.findViewById(R.id.json_output); Button formatBtn view.findViewById(R.id.format_btn); formatBtn.setOnClickListener(v - { String source input.getText().toString().trim(); if (source.isEmpty()) { output.setText(); return; } try { String formatted new JSONObject(source).toString(4); output.setText(formatted); } catch (JSONException e) { output.setText(JSON 格式错误 e.getMessage()); } }); return view; } }5.3 验证与打包代码写完后先编译 debug 包进入 APP 确认列表里出现新工具点击能正常格式化。注意测试一下非法 JSON 的提示是否友好。之后按顺序跑 assembleRelease 和混淆确认 release 包也能正常打开。如果发现有类被混淆删掉回到第 4 章加 keep 规则。做完这个功能我最大的感受是这个项目真正的价值不是它自带的几个工具而是它提供了一套“加工具不用动壳”的范式。从那以后我每次设计新模块都会先问一句新增功能能不能不改主界面如果可以我就沿用这种注册表驱动的思路如果不可以我会重新考虑模块边界。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询