简介:这是一套面向中医大夫的Android应用源码,实现病历管理、处方记录、中医药知识查询等助理功能,适合希望掌握Android业务系统开发的中级学习者。压缩包共125个文件,大小仅1.55MB,既包含20个Java源文件、49个class字节码、16个XML界面布局与10个SQL数据库脚本,也附带可直接安装的APK文件,便于对照源码快速运行。目前已有172人学习/下载。通过研读项目,可系统梳理Android开发中Activity生命周期、SQLite数据持久化、网络请求、运行时权限管理等核心知识点;同时,项目将中医诊疗场景与移动端信息管理深度融合,从界面设计到业务逻辑分层都有完整实现,对开发医疗健康类应用具有直接借鉴价值。整体包体虽小,但目录结构涵盖UI、数据、工具等模块,适合用于源码剖析与二次开发练习。
1. 拿到 zz-doctor 这个安卓源码包,先别急着解压
“android应用源码zz-doctor中医大夫助理信息系统源码.zip”是一类很典型的交付物:文件名里把技术平台、项目代号、业务定位和打包方式全写明白了,内容是一个面向中医门诊场景的安卓应用工程。zz-doctor 要解决的不是挂号排班,而是大夫日常的高频动作——患者建档、症状录入、辨证记录、参考方剂、复诊追踪。对刚接触安卓源码的开发者来说,它比纯 Demo 多了一层业务数据流;对做过一两年工程的熟手而言,它值得看的反而是数据库设计和构建配置。这类 zip 从下载到跑起来,最容易出问题的环节是解压编码、Gradle 版本和 SDK 平台缺失,而不是代码本身。下面按“从 zip 到 APK”的顺序,把解压校验、工程导入、结构分析、二次改动、命令行构建五段讲清楚,每一步都给出可直接执行的命令和参数。
2. 从zip到可编译工程:校验、解压与 Android Studio 导入
2.1 解压前先列目录,确认文件名编码与完整性
“源码.zip”这种交付方式最大的坑不在代码,而在压缩包本身。常见 zip 是在 Windows 下用 7-Zip 或 Bandizip 打的,文件名编码常用 GBK;而 Android Studio 跑在 macOS 或 Linux 上时,默认 unzip 按 UTF-8 解释,解压出来的中文目录名就会乱码。zz-doctor 的项目路径是 ASCII,风险低一些,但如果 res 目录里出现中文资源名,导入后布局文件可能直接找不到资源。
建议的做法是先列目录,再决定用哪个工具解压,不要双击就解。用命令行可以同时完成查看和完整性校验:
# 先查看压缩包内文件清单,-l 只列目录不解压 unzip -l android应用源码zz-doctor中医大夫助理信息系统源码.zip # 检查是否存在路径穿越(文件名带 ../),摘取第四列后过滤 unzip -l android应用源码zz-doctor中医大夫助理信息系统源码.zip | awk '{print $4}' | grep -E '\.\./|^/' # 解压前做一次完整性与 CRC 校验,-q 表示安静模式只报错 unzip -tq android应用源码zz-doctor中医大夫助理信息系统源码.zip第二段命令的逻辑:unzip -l输出的第四列是文件名,awk '{print $4}'只摘取文件名,grep -E '\.\./|^/'用来找出向上跳目录或以根路径开头的条目。若输出为空,说明包内路径安全。-tq这一步不要省:源码包体积超过 100MB 时,传输过程经常发生后段数据损坏,解压到一半报错还算好的,怕的是解压成功但某个 class 文件缺了尾部字节,Gradle 编译时才会报ClassNotFoundException,排查成本反而更高。
提示:Windows 上也可以用 PowerShell 的
Expand-Archive解压,但它对 GBK 编码文件名的处理并不理想。遇到中文乱码目录时,优先用 Bandizip 或 7-Zip 打开,并在解压选项里把文件名编码切到 ANSI/GBK。
2.2 用 Android Studio 打开前,先判断是完整工程还是裸源码
解压后先看根目录长什么样,再决定怎么导入。区分标准用下表最直接:
| 根目录存在的文件 | 工程形态 | 导入方式 |
|---|---|---|
settings.gradle、gradlew、app/ | 完整 Gradle 工程 | File → Open 选根目录 |
只有src/、res/、AndroidManifest.xml | 裸源码目录 | 新建工程后迁移 |
只有散落的.java/.kt文件 | 示例代码 | 只能建模块引用 |
多数命名为“xxx源码.zip”的交付是第一种,但行业应用的源码包有时只导出app/src/main,漏掉了 Gradle wrapper。此时不要直接双击.java文件去看代码——语法高亮不等于工程可编译。在 Android Studio 里新建一个空 Activity 工程,把java目录、res目录和AndroidManifest.xml拷贝到新工程对应位置,再改包名和依赖,这才是裸源码的正确接法。
Android Studio 的界面语言可以在Settings → Plugins里安装“Chinese (Simplified) Language Pack”插件,重启后变成中文菜单;但 Gradle 日志和代码提示仍是英文,排错时看着原始报错反而更容易搜到解决方案,不必强求全中文。
2.3 Gradle 与 AGP 版本匹配:一半的导入失败发生在这里
第一次 Sync 时最常见的报错是 “Minimum supported Gradle version is X.X.X”。这是因为gradle-wrapper.properties里的 Gradle 版本与build.gradle里声明的 Android Gradle Plugin 版本不匹配。以 zz-doctor 这类较老的中医业务工程为例,如果插件版本是 4.x 或 7.0,而你的 Android Studio 是最新版本,不要直接把插件升到 8.x——老工程里的compile依赖写法、Android配置块在 AGP 8 下会大面积报错。
打开gradle/wrapper/gradle-wrapper.properties,确认 distributionUrl:
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip参数说明:distributionUrl决定 Gradle 运行时版本,build.gradle里的com.android.tools.build:gradle:7.4.2决定 AGP 版本。两者对应关系是固定的,常用组合如下:
| AGP 版本 | 最低 Gradle 版本 | 建议 compileSdk |
|---|---|---|
| 7.1.x | 7.2 | 32 |
| 7.4.x | 7.5 | 33 |
| 8.2.x | 8.2 | 34 |
如果下载 Gradle 发行包时遇到 “invalid zip archive: could not find eocd” 报错,原因通常是~/.gradle/wrapper/dists目录下缓存了损坏的 zip。eocd 是 zip 格式的中央目录结束标记,读不到说明文件不完整。处理办法是删掉本地 wrapper 缓存重新下载:
# macOS / Linux 下删除 Gradle wrapper 与依赖缓存 rm -rf ~/.gradle/wrapper/dists rm -rf ~/.gradle/caches/modules-2删除后回到 Android Studio 点击Sync Project with Gradle Files。如果官方分发地址下载慢,可以把 distributionUrl 换成国内云厂商的 Gradle 镜像地址,这属于公共基础设施加速,不影响工程行为,但要注意镜像路径下必须存在对应版本目录。
2.4 SDK 平台缺失与三处源码联动报错
Gradle 版本对上以后,编译还卡在 SDK 上。老项目的compileSdkVersion往往写的是 28、30,而新安装的 Android Studio 只自带最新平台。打开 SDK Manager,勾选对应版本即可;命令行环境下则用 sdkmanager 安装:
# 安装 compileSdk 对应的平台版本,Y 表示接受许可 sdkmanager "platforms;android-33" "build-tools;33.0.2"SDK 就绪后再看三处源码联动问题:app/libs/里有本地 jar 但build.gradle没写implementation fileTree(dir: 'libs', include: ['*.jar']);applicationId和AndroidManifest.xml里的包名不一致;项目用了 Java 8 语法但没开compileOptions。这三处在源码 zip 里出现频率最高,同步报错时按 dependencies、manifest、compileOptions 的顺序逐个排查,比看堆栈更有效。
工程同步通过之后,先别急着改代码。把项目结构完整读一遍,评估源码包是否值得继续投入,再谈二次开发。
3. 拆解 zz-doctor 的工程结构:中医大夫助理系统的数据流
3.1 从分包反推模块:zz-doctor 的典型 Android 分层
对“大夫助理”这类业务系统,代码组织通常逃不出三层:界面层、数据层、工具层。如果让我重新组织一个同类型项目,我会把包结构设计成下面这样,拿到源码包时也按这个思路去对照:
com.zz.doctor ├── ui # Activity / Fragment / Adapter │ ├── login # 登录与账号切换 │ ├── patient # 患者列表与建档 │ └── prescription # 处方录入与参考方剂 ├── data # 数据层 │ ├── db # SQLiteOpenHelper 与建表语句 │ ├── model # 实体 Bean │ └── repository # 数据仓储 ├── service # 复诊提醒等后台服务 └── utils # 日期、字符串等工具这种分层的核心价值是引用方向单向:UI 依赖 Adapter,Adapter 依赖 Entity,数据不反向流动。读源码时我习惯先在 Android Studio 里用Call Hierarchy(快捷键 Ctrl+Alt+H)追踪一个点击事件,比如“点击患者列表项后发生了什么”,链路短就说明工程结构健康,链路绕了三层 Activity 还没到底层数据,就要警惕后续改动会牵一发动全身。
3.2 患者建档与辨证记录:SQLite 表设计的关键字段
业务系统的重心在数据库。zz-doctor 这类不依赖云端的中医助理应用,用 SQLite 落地的表至少有四张:患者表、病历表、处方表、药材表。如果由我从头设计初版,建表语句会写成这样:
CREATE TABLE patient ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, gender INTEGER DEFAULT 0, birth_date TEXT, phone TEXT, remark TEXT, created_at TEXT DEFAULT (datetime('now','localtime')) ); CREATE TABLE medical_record ( id INTEGER PRIMARY KEY AUTOINCREMENT, patient_id INTEGER NOT NULL, symptom TEXT, -- 主诉症状 tongue TEXT, -- 舌象 pulse TEXT, -- 脉象 diagnosis TEXT, -- 辨证结论 create_time TEXT DEFAULT (datetime('now','localtime')), FOREIGN KEY (patient_id) REFERENCES patient(id) ON DELETE CASCADE ); CREATE TABLE herb ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, property TEXT, -- 药性:寒热温凉平 flavor TEXT, -- 药味:酸苦甘辛咸 category TEXT -- 分类:解表、补气等 );索引策略和普通 CRM 不一样:中医门诊查询量小,但会频繁按patient_id拉取历次病历时间线,所以联合索引建在medical_record(patient_id, create_time)上。herb表的category字段不要建索引——药材表几百行,全表扫描比维护索引 B 树快得多。
数据层和界面层之间建议加 repository 做转换,不让SQLiteCursor直接暴露给 Adapter。Cursor 的列索引可读性差,每个页面都要重复写moveToFirst和getColumnIndex,一旦表结构微调,改起来是全局性的痛苦。
3.3 大夫工作台的状态流转:从待诊列表到处方提交
如果只把 zz-doctor 理解成增删改查,会错过它真正的核心——工作流支持。接诊流程在 UI 上是:登录进入工作台 → 查看今日待诊列表 → 选择患者 → 查看历次辨证记录 → 录入本次症状与舌脉 → 生成参考方剂 → 大夫确认后落库。
这个流程在工程实现里最常见的坑是跨界面传参。Activity A 跳 B 时把整个 PatientBean 对象塞进 Intent,B 再跳 C 时又传一次——对象一旦变大,序列化开销增加,而且 C 界面拿到的是旧数据。我一般只传主键 ID,C 界面在onCreate里通过 ID 重新查一次 SQLite,既保证数据新鲜,也避免大对象序列化的性能问题。
另一个隐蔽点:接诊完成后按返回键回工作台,工作台列表必须重查数据库,而不是复用进入前的数据。没写onResume重新加载的话,刚保存的病历不会出现在列表里,用户会以为保存失败了。
4. 在 zz-doctor 里做二次开发:列表、数据库与业务逻辑
4.1 患者列表加科室筛选:从 BaseAdapter 到增量刷新
相当一部分课程设计源码还在用 ListView 加 BaseAdapter。ListView 不是不能跑,而是状态维护麻烦:列表项复用逻辑写错会闪烁,全量刷新在数据量上来后有明显卡顿。如果 zz-doctor 里已经是 RecyclerView,改造空间在 DiffUtil;如果是 ListView,优先保留原结构,在 Activity 层做筛选反而更稳。
给患者列表加一个“按科室过滤”的按钮,最直接的写法是:
filterButton.setOnClickListener(v -> { List<PatientBean> filtered = new ArrayList<>(); for (PatientBean p : allPatients) { if (selectedDepartment.equals(p.getDepartment())) { filtered.add(p); } } adapter.setData(filtered); // 适配器内部替换数据源 adapter.notifyDataSetChanged(); // 触发列表重绘 });代码逻辑说明:allPatients是 Activity 启动时从 repository 拉取的全量数据,筛选操作不重新查询数据库,而是对内存列表做条件过滤。setData替换 Adapter 持有的引用,notifyDataSetChanged强制列表重绘。这样改的好处是保留原有数据源,清空筛选时把allPatients重新塞回去即可。
如果列表项包含图片,比如患者头像,全量重绘会有明显闪烁。此时可以把 Adapter 换成ListAdapter,配合DiffUtil做差值计算,areItemsTheSame比较 id,areContentsTheSame比较业务字段,列表更新只刷新变化的行。改动量不大,但体验提升明显。
4.2 数据库加字段:改了 CREATE TABLE 别忘 onUpgrade
二次开发中最常见的需求是加字段,比如给patient表增加allergy_history。直接改建表语句重装应用没意义,线上用户的数据会清空。正确姿势是把DBHelper里的DATABASE_VERSION从 1 改成 2,并在onUpgrade里加迁移:
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { if (oldVersion < 2) { // 用户从 v1 升级到 v2:新增过敏史字段 db.execSQL("ALTER TABLE patient ADD COLUMN allergy_history TEXT DEFAULT ''") } // 后续 v3、v4 继续用 if 累加,不能用 else if }参数说明:oldVersion是当前安装版本的数据库版本号,newVersion是代码里定义的新版本号。onUpgrade里的判断必须用连续的if而不是else if,这样用户从 v1 直升 v3 时,v2 的迁移逻辑也会被执行。
注意:SQLite 的
ALTER TABLE ADD COLUMN不支持回滚,上线前先备份旧库。另外它只能加列,不能改列名、删列,这类操作只能“建新表 → 导数据 → 删旧表 → 重命名”四步走。
4.3 处方建议逻辑:把“推荐”和“决策”分开
zz-doctor 作为“助理”系统,处方建议模块最容易写成硬编码。如果代码里是一长串 if-else 拼接常用方剂名,后续调整会很痛苦。我会把这个逻辑抽成独立类,返回候选药材集合而非最终处方:
public class PrescriptionSuggest { public List<HerbBean> suggest(String diagnosis) { if (diagnosis.contains("风寒")) { return herbDao.queryByCategory("解表"); } if (diagnosis.contains("气虚")) { return herbDao.queryByCategory("补气"); } return herbDao.queryDefault(); } }这个方法的参数diagnosis是大夫录入并经辨证后的结论文本,返回值是候选药材集合。界面拿到候选集后展示给大夫勾选,勾选结果才写进prescription表。“推荐”和“决策”分层,是医疗信息系统必须保留的边界:算法只能辅助,最终确认必须交给人。修改时也要注意contains的误命中,比如“气虚”会被“气虚血瘀”命中,如果想精确匹配,可以改成按关键词集合过滤。
5. 用命令行把 zz-doctor 构建成 APK 并做交付前检查
5.1 命令行构建与 APK 输出路径
Android Studio 里点 Run 按钮谁都会,但交付、备份、CI 场景下还是命令行更可控。工程根目录下执行:
# 构建 debug 包,跳过单测和 lint 加速 ./gradlew assembleDebug -x test -x lint # 构建 release 包,需要已配置 signingConfigs ./gradlew assembleRelease构建产物位于app/build/outputs/apk/debug/app-debug.apk或app/build/outputs/apk/release/下。首次构建慢是正常的,主要耗时在依赖下载和 Transform 流程,不代表代码有问题。
5.2 真机运行前检查五个易漏点
AndroidManifest.xml是否声明了INTERNET、READ_EXTERNAL_STORAGE等权限;安卓 11 以上读写公共目录还需要MANAGE_EXTERNAL_STORAGE并引导用户到设置页授权。targetSdkVersion是否过高,高于 30 且未适配分区存储时,文件访问会静默失败,表现为导出病历无反应。- 药材库是否预置。如果运行时在
onCreate里循环插入几百条药材数据,冷启动会很慢;正式交付应把预置库.db文件放到assets/,首次启动时复制到databases/目录。 - 主 Activity 的
exported是否为true,否则桌面点击图标会提示找不到入口 Activity。 - 真机调试时先用
adb devices确认设备被识别,并检查 USB 调试授权弹窗是否被点掉了。
5.3 二次交付:打一个干净的可编译 zip
最后一步是把改动后的源码打包回传。打包前务必要排除构建产物和本地配置文件:
zip -r zz-doctor-v2-src.zip . -x "*/build/*" -x "*/.gradle/*" -x "*.iml" -x ".idea/*"命令解释:-x后跟排除通配符,*/build/*匹配任意层级的 build 目录,.idea/*排除本地 IDE 配置。构建产物目录常有几 GB,排除后源码包能缩到几十 MB;同时接收方解压后不会看到别人的本地 SDK 路径,直接进入 Sync 环节即可重新构建。
本文还有配套的精品资源,点击获取