简介:这是一套面向高校计算机相关专业学生的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 并调用 Repository,Repository 统一封装 Room DAO 的调用。这样做的实际好处是:当你要把数据源从本地 Room 换成远程 API 时,只改 Repository 一层,UI 代码不动。
// Repository 层示例 class HealthRepository(private val dao: HealthDao) { val allRecords: LiveData<List<HealthRecord>> = dao.getAllRecords() suspend fun insert(record: HealthRecord) { dao.insert(record) // 挂起函数,需在协程中调用 } }逻辑说明:dao.getAllRecords() 返回 LiveData,Room 会在数据表变化时自动推送新值,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 24(Android 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 用 Int,sleepHours 和 weight 用 Float,避免整数除法丢精度。@ColumnInfo 显式指定列名,防止 Kotlin 属性名和 SQLite 保留字冲突。
DAO 接口里至少要有插入、按时间范围查询、删除三条:
@Dao interface HealthDao { @Query("SELECT * FROM health_record ORDER BY record_date DESC") fun getAllRecords(): LiveData<List<HealthRecord>> @Query("SELECT * FROM health_record WHERE record_date BETWEEN :start AND :end") fun getRecordsBetween(start: Long, end: Long): LiveData<List<HealthRecord>> @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 = findViewById<LineChart>(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-kapt,Room 版本和 Kotlin 版本兼容。如果还不行,在 gradle.properties 里加 kapt.use.worker.api=false 试试。
6. 进阶技巧:用 Room 的 Migration 保住用户数据不丢
毕业设计答辩时,如果评委问「你应用升级后用户数据会不会丢」,能答上 Migration 就是加分项。Room 默认在版本号变化时销毁重建数据库,数据全没。正确做法是写 Migration:
val 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 跑一次,确认不崩。这个习惯帮我省过好几次答辩现场的尴尬。希望帮到你。
本文还有配套的精品资源,点击获取