
简介这是一套面向高校计算机相关专业学生的Android个人健康管理应用完整项目包含全部源代码与配套毕业设计文档适合正在准备毕业设计、课程设计或期末综合作业的同学参考使用。项目在导师指导下完成并获得较高评分经过全面测试运行稳定可直接用于毕业设计提交。资源包共253个文件约14.31MB以xml布局文件、java业务代码、zbak备份文件、png与jpg图片资源为主另含so动态库、jar依赖包、gradle构建脚本及properties配置等覆盖界面、逻辑、资源与构建各环节。目前已有41人学习关注。通过该资源读者可获得一套结构完整的安卓健康管理项目源码理解Activity与布局组织方式、第三方SDK集成思路及Gradle工程配置并借助配套文档梳理设计流程与实现要点为毕业项目开发与技能提升提供可复用的实践案例。1. 健康管家系统从一份毕业设计源码到能跑通的 Android 应用很多同学拿到「Android平台健康管家系统」这个题目时第一反应是去搜一份现成源码改改包名、换换图标就交差。但真正动手才会发现一份能编译通过的源码和一份能答辩、能演示、能讲清楚技术选型的工程之间差着十万八千里。健康管家系统本质上是一个围绕用户体征数据做记录、统计与提醒的移动端应用核心模块通常包括用户账户、健康数据录入步数、心率、睡眠、体重、数据可视化、定时提醒以及个人中心。它适合两类人一是正在做 Android 课程设计或毕业设计的学生需要一套结构清晰、能讲明白的参考实现二是想快速上手 Android 本地数据存储与图表绘制的开发者拿它当练手项目。这一章先把边界划清楚后面几章再拆实现路径和踩坑点。2. 健康管家系统的技术选型为什么用 Room MPAndroidChart 而不是别的2.1 本地数据存储Room 比 SQLiteOpenHelper 省多少事健康数据的特点是结构固定、查询频繁、单机为主。常见做法是用 SQLite 直接建表但裸写 SQLiteOpenHelper 的代价是每次增删字段都要手动改版本号、写迁移脚本而且 Cursor 的关闭时机稍不注意就泄漏。Room 作为 Jetpack 组件在编译期校验 SQL 语句把实体类直接映射成表DAO 接口用注解就能生成实现。对于健康管家这种「一张用户表 一张体征记录表 一张提醒表」的结构Room 能把建表、查询、迁移的代码量压到裸 SQLite 的三分之一左右。选型理由很直接毕业设计答辩时评委大概率会问「你怎么做数据持久化」。答「用 Room 做 ORM 映射配合 LiveData 做数据观察」比答「用 SQLiteDatabase 写 SQL」更能体现对 Android 架构的理解。而且 Room 和 ViewModel、LiveData 的配合是官方推荐路线后续想加数据导出、云同步也有扩展余地。2.2 图表绘制MPAndroidChart 的引入与版本注意健康数据不画图等于白记。步数趋势、体重变化、睡眠时长分布这些都需要折线图或柱状图。MPAndroidChart 是目前 Android 端最成熟的图表库之一支持折线、柱状、饼图、雷达图且能响应触摸手势。引入方式是在模块级 build.gradle 里加依赖// 模块级 build.gradle dependencies { implementation com.github.PhilJay:MPAndroidChart:v3.1.0 // 其他依赖... }注意MPAndroidChart 托管在 JitPack 上所以项目根目录的 settings.gradle 或 build.gradle 里必须配置 JitPack 仓库否则同步阶段就会报「Could not find com.github.PhilJay」。很多同学卡在这一步以为是网络问题其实是仓库没加。// 项目根目录 build.gradle旧版或 settings.gradle新版 dependencyResolutionManagement { repositories { google() mavenCentral() maven { url https://jitpack.io } // 必须加这一行 } }参数说明v3.1.0 是当前稳定版API 与 v3.0.x 基本兼容。如果项目用的 Gradle 版本较新注意 JitPack 的 URL 要写在 dependencyResolutionManagement 块里而不是 allprojects 里否则会提示仓库配置冲突。2.3 架构分层ViewModel Repository 的最小闭环健康管家系统不需要上 Dagger/Hilt 这种重框架但 ViewModel Repository 的两层结构足够把 UI 和数据处理分开。Activity/Fragment 只负责渲染和用户交互ViewModel 持有 LiveData 并调用 RepositoryRepository 统一封装 Room DAO 的调用。这样做的实际好处是当你要把数据源从本地 Room 换成远程 API 时只改 Repository 一层UI 代码不动。// Repository 层示例 class HealthRepository(private val dao: HealthDao) { val allRecords: LiveDataListHealthRecord dao.getAllRecords() suspend fun insert(record: HealthRecord) { dao.insert(record) // 挂起函数需在协程中调用 } }逻辑说明dao.getAllRecords() 返回 LiveDataRoom 会在数据表变化时自动推送新值UI 层 observe 即可刷新。insert 用 suspend 修饰调用方必须放在 viewModelScope.launch 里否则编译不过。参数上HealthRecord 实体类里用 PrimaryKey(autoGenerate true) 标注主键ColumnInfo 指定列名避免字段名和 SQL 关键字冲突。3. 从零搭建健康管家系统环境、建表与数据录入的完整步骤3.1 Android Studio 环境准备与项目初始化第一步是装 Android Studio。下载地址在开发者官网版本选当前稳定版即可。安装时勾选 Android SDK、Android SDK Platform 和 Android Virtual Device。装完后打开 SDK Manager确认至少装了 API 34 的 Platform 和对应的 Build-Tools。如果用的是国内网络SDK 下载可能慢可以在 Settings 里配置代理镜像但注意不要用任何来路不明的加速工具。新建项目时选「Empty Views Activity」而不是「Empty Activity」因为后者默认用 Compose而健康管家系统的参考实现大多基于 View 体系用 XML 布局。语言选 Kotlin最低 SDK 选 API 24Android 7.0这样覆盖设备够广又能用 Room 和 LiveData 的完整功能。项目建好后先在 build.gradle 里加 Room 的依赖和 kapt 插件plugins { id com.android.application id org.jetbrains.kotlin.android id kotlin-kapt // Room 注解处理需要 } dependencies { def room_version 2.6.1 implementation androidx.room:room-runtime:$room_version implementation androidx.room:room-ktx:$room_version kapt androidx.room:room-compiler:$room_version }参数说明room-ktx 提供协程和 LiveData 支持必须加kapt 是 Kotlin 注解处理工具Room 的 Entity、Dao 都靠它生成代码。如果同步后报「kapt 未找到」检查插件是否写在 plugins 块里而不是 apply plugin 的老写法。3.2 建三张表用户、体征记录、提醒健康管家系统的数据模型不复杂但字段设计要考虑后续统计。用户表存账号、昵称、身高、目标体重体征记录表存时间戳、步数、心率、睡眠时长、体重提醒表存提醒类型、触发时间、是否重复。Entity(tableName health_record) data class HealthRecord( PrimaryKey(autoGenerate true) val id: Long 0, ColumnInfo(name record_date) val recordDate: Long, // 时间戳 val steps: Int 0, ColumnInfo(name heart_rate) val heartRate: Int 0, ColumnInfo(name sleep_hours) val sleepHours: Float 0f, val weight: Float 0f )逻辑说明recordDate 用 Long 存时间戳方便按天、按周做范围查询。steps 和 heartRate 用 IntsleepHours 和 weight 用 Float避免整数除法丢精度。ColumnInfo 显式指定列名防止 Kotlin 属性名和 SQLite 保留字冲突。DAO 接口里至少要有插入、按时间范围查询、删除三条Dao interface HealthDao { Query(SELECT * FROM health_record ORDER BY record_date DESC) fun getAllRecords(): LiveDataListHealthRecord Query(SELECT * FROM health_record WHERE record_date BETWEEN :start AND :end) fun getRecordsBetween(start: Long, end: Long): LiveDataListHealthRecord Insert(onConflict OnConflictStrategy.REPLACE) suspend fun insert(record: HealthRecord) Delete suspend fun delete(record: HealthRecord) }参数说明onConflict REPLACE 表示主键冲突时覆盖旧数据适合「同一天重复录入」的场景。getRecordsBetween 的 start 和 end 传当天 0 点和 23:59:59 的时间戳就能拿到当日数据。3.3 数据录入界面与 ViewModel 的对接录入界面用 XML 布局放几个 EditText 和一个保存按钮。Activity 里拿到 ViewModel 实例点击保存时构造 HealthRecord 对象并调用 viewModel.insert()。class RecordViewModel(private val repository: HealthRepository) : ViewModel() { fun insert(record: HealthRecord) { viewModelScope.launch { repository.insert(record) } } }逻辑说明viewModelScope 是 ViewModel 自带的协程作用域Activity 销毁时自动取消不会造成内存泄漏。insert 是挂起函数必须在协程里调。如果直接在 Activity 里调 repository.insert()编译器会报「Suspend function should be called only from a coroutine」。参数上构造 HealthRecord 时 recordDate 用 System.currentTimeMillis()steps 等字段从 EditText 里取文本后 toIntOrNull() ?: 0避免空输入崩溃。4. 图表与提醒让健康数据真正「活」起来4.1 用 MPAndroidChart 画步数趋势折线图数据存下来不画图用户没有感知。折线图是最直观的展示方式。在布局里放一个 LineChart然后在 Fragment 或 Activity 里配置val chart findViewByIdLineChart(R.id.stepsChart) val entries records.map { Entry(it.recordDate.toFloat(), it.steps.toFloat()) } val dataSet LineDataSet(entries, 步数).apply { color Color.BLUE valueTextColor Color.BLACK lineWidth 2f } chart.data LineData(dataSet) chart.invalidate() // 刷新图表逻辑说明Entry 的 x 轴用时间戳y 轴用步数。LineDataSet 的 color 控制线条颜色valueTextColor 控制数值标签颜色。chart.invalidate() 是必须的否则数据更新后图表不刷新。参数上如果 x 轴时间戳跨度大需要自定义 XAxis 的 ValueFormatter把时间戳格式化成「MM-dd」显示否则默认显示一长串数字。4.2 定时提醒AlarmManager 还是 WorkManager健康管家系统通常有「喝水提醒」「久坐提醒」这类功能。常见做法有两种AlarmManager 适合精确到分钟的定时任务WorkManager 适合有约束条件如充电时、联网时的延迟任务。对于提醒场景AlarmManager 更直接。val alarmManager getSystemService(Context.ALARM_SERVICE) as AlarmManager val intent Intent(this, ReminderReceiver::class.java) val pendingIntent PendingIntent.getBroadcast( this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) alarmManager.setRepeating( AlarmManager.RTC_WAKEUP, triggerAtMillis, AlarmManager.INTERVAL_HOUR, pendingIntent )参数说明RTC_WAKEUP 表示用真实时间并在休眠时唤醒设备FLAG_IMMUTABLE 是 Android 12 以上必须加的否则会抛异常。setRepeating 的第三个参数是间隔INTERVAL_HOUR 表示每小时一次。注意从 Android 6.0 开始Doze 模式会延迟非精确闹钟如果要求精确触发需要用 setExactAndAllowWhileIdle但这样耗电会增加毕业设计里用 setRepeating 足够演示。4.3 数据导出与备份的简单实现答辩时经常被问「数据能不能导出」。最简单的做法是把 Room 里的记录查出来拼成 CSV 字符串写到应用外部存储的 Documents 目录。val csv records.joinToString(\n) { ${it.recordDate},${it.steps},${it.heartRate},${it.sleepHours},${it.weight} } val file File(getExternalFilesDir(null), health_data.csv) file.writeText(csv)逻辑说明getExternalFilesDir(null) 返回应用专属的外部存储目录不需要申请存储权限Android 10 以上。joinToString 把每条记录拼成一行逗号分隔。参数上如果要在表头加列名在 csv 字符串最前面拼一行「时间戳,步数,心率,睡眠,体重」即可。5. 避坑与排查健康管家系统开发中最容易翻车的 5 个点5.1 现象Room 编译报错「Cannot figure out how to save this field into database」原因实体类里用了 Room 不支持的类型比如 Date 对象或自定义类但没有加 TypeConverter。解决要么把字段改成 Long 或 String要么写一个 TypeConverter 类用 TypeConverter 标注转换方法并在 Database 注解里注册。5.2 现象图表显示空白Logcat 无报错原因LineData 设置后没有调用 invalidate()或者 entries 列表为空。解决先检查 records 是否为空再确认 invalidate() 在数据赋值之后调用。另外如果 x 轴值重复MPAndroidChart 会去重导致点消失确保时间戳唯一。5.3 现象AlarmManager 在 Android 12 以上不触发原因Android 12 要求 PendingIntent 必须指定 FLAG_IMMUTABLE 或 FLAG_MUTABLE否则直接抛 IllegalArgumentException。解决在 PendingIntent.getBroadcast 的 flags 参数里加上 FLAG_IMMUTABLE。5.4 现象应用在模拟器上跑得好好的真机上闪退原因真机 Android 版本可能低于 minSdk或者用了模拟器支持但真机不支持的 API。解决检查 build.gradle 里的 minSdk 是否低于真机版本用 adb logcat 抓崩溃堆栈定位到具体行。5.5 现象kapt 报「Execution failed for task :app:kaptDebugKotlin」原因Room 的注解处理器和 Kotlin 版本不匹配或者 kapt 插件没加。解决确认 plugins 块里有 kotlin-kaptRoom 版本和 Kotlin 版本兼容。如果还不行在 gradle.properties 里加 kapt.use.worker.apifalse 试试。6. 进阶技巧用 Room 的 Migration 保住用户数据不丢毕业设计答辩时如果评委问「你应用升级后用户数据会不会丢」能答上 Migration 就是加分项。Room 默认在版本号变化时销毁重建数据库数据全没。正确做法是写 Migrationval MIGRATION_1_2 object : Migration(1, 2) { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL(ALTER TABLE health_record ADD COLUMN blood_pressure INTEGER NOT NULL DEFAULT 0) } }然后在 Room.databaseBuilder 里 addMigrations(MIGRATION_1_2)。逻辑说明Migration 的 startVersion 和 endVersion 对应 Database 的 version。execSQL 里写标准 SQLite 语句新增列必须给 DEFAULT 值否则已有行会报错。参数上如果一次跳多个版本比如 1 到 3需要写 MIGRATION_1_2 和 MIGRATION_2_3 两个Room 会自动按顺序执行。验证 Migration 是否生效可以在升级前插一条数据升级后查出来看新列是否有默认值。我一般会在开发阶段把 version 改成 2跑一次再改回 1 跑一次确认不崩。这个习惯帮我省过好几次答辩现场的尴尬。希望帮到你。本文还有配套的精品资源点击获取