拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Android健康助手工程实践:Retrofit+Room+图表可视化全链路

简介这是一份面向计算机专业本科生的Android毕业设计完整项目聚焦健康饮食管理场景涵盖移动端App与后台管理系统的双端开发实践。资源提供从需求分析、UI设计到功能实现的全流程参考特别适合Java安卓开发入门者巩固MVC架构、SQLite本地存储、HTTP网络请求及用户健康数据可视化等核心技能。压缩包共1156个文件主体为333个Java业务逻辑与Activity类、298个XML布局与资源定义、219个HTML/JS/CSS构成的管理端Web界面辅以PNG/GIF图标、JSON配置及Gradle构建脚本整体6.78MB结构清晰、模块解耦明确。已有313人学习下载读者可直接导入Android Studio运行调试获取含管理员权限控制、饮食搭配推送、健康指标动态记录与趋势图表展示的可执行源码同时参考完整目录组织方式与前后端交互设计思路。1. 这不是又一个“健康打卡App”Android健康饮食小助手的工程边界在哪很多同学拿到“健康饮食小助手”毕设题目第一反应是套用现成模板——首页Banner轮播几个Fragment切换SQLite存点数据。但真正跑通这个项目会发现它本质是一个轻量级双端协同系统不是单机App。管理端Web后台和移动端Android App之间必须通过标准HTTP接口完成用户状态同步、饮食方案下发、健康指标回传三类核心交互而移动端内部又需在离线场景下保障血压/心率/BMI录入的本地一致性并支持趋势图表渲染。这意味着你不能只写Activity跳转逻辑必须处理 Retrofit 网络层异常重试策略、Room 数据库迁移版本控制、MPAndroidChart 的动态Y轴缩放适配甚至要考虑 Android 12 对后台服务的限制导致定时推送失效后的降级方案。适合大三下到大四上、已掌握 Java 基础与 Android 四大组件、正准备从“能跑通”迈向“可交付”的开发者——它不考算法深度但全面检验工程落地能力。2. 架构选型与模块拆解为什么用 Retrofit Room MPAndroidChart 而不是其他组合2.1 双端通信协议设计RESTful 接口定义决定开发效率上限管理端假设为 Spring Boot 实现暴露的 API 必须严格遵循 REST 规范且字段命名与移动端 Java Bean 保持零映射成本。例如用户健康指标上传接口/api/v1/users/{userId}/health-metrics接收 POST 请求请求体为 JSON{ bloodPressureSystolic: 120, bloodPressureDiastolic: 80, heartRate: 72, weightKg: 68.5, recordedAt: 2024-06-15T08:30:0008:00 }提示recordedAt字段必须使用 ISO 8601 格式并带时区偏移避免 Android 端SimpleDateFormat解析失败。若后端返回createdTime字段为时间戳毫秒则 Retrofit 需配合JsonAdapter注册LongToDateTimeAdapter否则 Gson 默认解析为Double导致类型转换异常。移动端 Retrofit 初始化代码如下// HttpLoggingInterceptor 仅用于调试发布前必须移除 HttpLoggingInterceptor logging new HttpLoggingInterceptor(); logging.setLevel(HttpLoggingInterceptor.Level.BODY); OkHttpClient client new OkHttpClient.Builder() .addInterceptor(logging) .connectTimeout(15, TimeUnit.SECONDS) .readTimeout(20, TimeUnit.SECONDS) .build(); Retrofit retrofit new Retrofit.Builder() .baseUrl(https://your-backend-domain.com/api/v1/) .client(client) .addConverterFactory(GsonConverterFactory.create( new GsonBuilder() .setDateFormat(yyyy-MM-ddTHH:mm:ss.SSSZ) // 统一时间格式 .create() )) .build(); ApiService apiService retrofit.create(ApiService.class);2.1.1 ApiService 接口定义关键字段说明方法名HTTP 动词路径用途注意事项login()POST/auth/login用户登录获取 JWT TokenToken 必须存入EncryptedSharedPreferences禁用SharedPreferences明文存储getHealthMetrics(Path(userId) long userId)GET/users/{userId}/health-metrics?from2024-06-01to2024-06-15拉取指定日期范围健康指标from/to参数需 URL 编码避免/被截断submitDietRecord()POST/users/{userId}/diet-records提交当日饮食记录含食物ID、克重、热量后端需校验食物ID是否存在否则返回400 Bad Request2.2 本地数据持久化Room 数据库如何支撑离线优先体验健康指标录入必须支持无网环境操作因此 Room 不仅存储缓存数据还需承担业务主数据角色。实体类HealthMetricEntity定义如下Entity(tableName health_metrics) public class HealthMetricEntity { PrimaryKey(autoGenerate true) public long id; public long userId; public int bloodPressureSystolic; public int bloodPressureDiastolic; public int heartRate; public double weightKg; public long recordedAt; // 毫秒时间戳便于排序与图表X轴计算 public boolean isSynced; // 标记是否已同步至服务器 }DAO 接口需提供批量插入与条件查询能力Dao public interface HealthMetricDao { Insert(onConflict OnConflictStrategy.REPLACE) void insertAll(ListHealthMetricEntity metrics); Query(SELECT * FROM health_metrics WHERE userId :userId AND recordedAt BETWEEN :start AND :end ORDER BY recordedAt ASC) ListHealthMetricEntity getMetricsInRange(long userId, long start, long end); Query(UPDATE health_metrics SET isSynced 1 WHERE id IN (:ids)) void markAsSynced(ListLong ids); Query(SELECT * FROM health_metrics WHERE isSynced 0 LIMIT 100) ListHealthMetricEntity getUnsyncedMetrics(); }注意isSynced字段是离线同步的核心开关。网络恢复后App 启动WorkManager定时任务扫描getUnsyncedMetrics()逐条调用 API 提交并执行markAsSynced()。若某次提交失败如 Token 过期需捕获HttpException并触发重新登录流程而非简单丢弃数据。2.3 图表可视化MPAndroidChart 动态渲染健康趋势图的实操细节血压趋势图需同时显示收缩压红色、舒张压蓝色双折线并支持手势缩放。初始化代码需规避常见内存泄漏// 在 Activity onDestroy() 中必须调用 lineChart.getDescription().setEnabled(false); lineChart.setTouchEnabled(true); lineChart.setDragEnabled(true); lineChart.setScaleEnabled(true); lineChart.setPinchZoom(true); // X轴日期格式化避免默认显示毫秒 XAxis xAxis lineChart.getXAxis(); xAxis.setPosition(XAxis.XAxisPosition.BOTTOM); xAxis.setValueFormatter(new IAxisValueFormatter() { Override public String getFormattedValue(float value, AxisBase axis) { return new SimpleDateFormat(MM-dd, Locale.getDefault()).format(new Date((long) value)); } }); // Y轴自动适配血压范围80~160mmHg YAxis leftAxis lineChart.getAxisLeft(); leftAxis.setAxisMinimum(60f); leftAxis.setAxisMaximum(180f); leftAxis.setLabelCount(7, true); // 强制显示7个刻度构建数据集时需注意Entry的 X 值必须为毫秒时间戳非日历序号否则缩放后坐标错乱ListEntry systolicEntries new ArrayList(); ListEntry diastolicEntries new ArrayList(); for (HealthMetricEntity metric : metrics) { // X值为recordedAt时间戳Y值为血压数值 systolicEntries.add(new Entry(metric.recordedAt, metric.bloodPressureSystolic)); diastolicEntries.add(new Entry(metric.recordedAt, metric.bloodPressureDiastolic)); } LineDataSet set1 new LineDataSet(systolicEntries, 收缩压); set1.setColor(Color.RED); set1.setLineWidth(2f); set1.setCircleRadius(4f); LineDataSet set2 new LineDataSet(diastolicEntries, 舒张压); set2.setColor(Color.BLUE); set2.setLineWidth(2f); set2.setCircleRadius(4f); LineData data new LineData(set1, set2); lineChart.setData(data); lineChart.invalidate(); // 强制重绘3. 关键功能实现从注册登录到健康趋势图的完整链路3.1 用户认证流程JWT Token 的安全存储与自动刷新机制登录成功后后端返回 JSON 包含token和expiresIn单位秒。移动端需将 Token 存入加密 SharedPreference并设置过期监听// 使用 AndroidX Security 库加密存储 String encryptedToken EncryptedSharedPreferences .create(auth_prefs, auth_key, masterKey, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM) .getString(jwt_token, ); // Token 刷新逻辑在每次网络请求前检查有效期 long expiresAt System.currentTimeMillis() expiresIn * 1000L; if (expiresAt - System.currentTimeMillis() 300_000) { // 提前5分钟刷新 apiService.refreshToken(refreshToken).enqueue(new CallbackTokenResponse() { Override public void onResponse(CallTokenResponse call, ResponseTokenResponse response) { if (response.isSuccessful()) { saveToken(response.body().getToken(), response.body().getExpiresIn()); } } Override public void onFailure(CallTokenResponse call, Throwable t) { // 清空Token跳转登录页 clearAuthState(); } }); }3.1.1 Retrofit 拦截器注入 Token 的正确姿势public class AuthInterceptor implements Interceptor { Override public Response intercept(Chain chain) throws IOException { String token getStoredToken(); // 从 EncryptedSharedPreferences 读取 if (token ! null !token.isEmpty()) { Request original chain.request(); Request request original.newBuilder() .header(Authorization, Bearer token) .method(original.method(), original.body()) .build(); return chain.proceed(request); } return chain.proceed(chain.request()); } }注意AuthInterceptor必须在OkHttpClient.Builder()中addInterceptor()而非addNetworkInterceptor()否则无法拦截重定向请求导致 401 错误未被拦截。3.2 饮食搭配与推荐数据加载分页缓存策略降低服务器压力管理端推送的饮食搭配数据量较大每条含食物图片URL、营养成分表、烹饪步骤移动端需实现两级缓存内存缓存LruCache缓存最近访问的 20 条饮食详情避免重复解析 JSON磁盘缓存Room存储已加载的饮食 ID 列表及更新时间戳启动时比对lastModified决定是否拉取新数据。关键代码片段// Room Entity 定义 Entity(tableName diet_recommendations) public class DietRecommendationEntity { PrimaryKey public long id; // 后端分配的唯一ID public String title; public String imageUrl; public String nutrients; // JSON字符串如{calories:1200,protein:45} public long lastModified; // 服务器最后更新时间戳 public boolean isFavorite; // 用户收藏状态 } // 分页加载逻辑RecyclerView Paging3 PagerInteger, DietRecommendationEntity pager new Pager( config new PagingConfig(pageSize 10, enablePlaceholders false), pagingSourceFactory () - new DietPagingSource(apiService, database.dietDao()) ); FlowPagingDataDietRecommendationEntity flow pager.flow.cachedIn(viewModelScope);3.3 健康指标趋势图交互增强长按标记关键事件点用户常需标注某次测量异常如“感冒期间血压升高”需在图表上支持长按添加注释lineChart.setOnChartGestureListener(new OnChartGestureListener() { Override public void onLongPress(MotionEvent me) { MPPointD point lineChart.getTransformer(YAxis.AxisDependency.LEFT) .getPixelForValues(me.getX(), me.getY()); Entry entry lineChart.getData().getEntryForViewPort(point.x, point.y); if (entry ! null) { // 弹出对话框输入备注 showNoteDialog((int) entry.getX(), (int) entry.getY()); } } // 其他方法留空 });保存注释到本地数据库后需在图表渲染时叠加MarkerViewpublic class CustomMarkerView extends MarkerView { private final TextView tvContent; public CustomMarkerView(Context context, int layoutResource) { super(context, layoutResource); tvContent findViewById(R.id.tvContent); } Override public void refreshContent(Entry e, Highlight highlight) { // 查询该时间戳对应的备注 String note database.noteDao().getNoteByTimestamp((long) e.getX()); tvContent.setText(note ! null ? note : 无备注); super.refreshContent(e, highlight); } }4. 构建与调试避坑指南Gradle 配置、ADB 日志定位与真机调试要点4.1 Gradle 构建常见错误与修复方案项目根目录gradle.properties必须启用 AndroidX 和 Jetifierandroid.useAndroidXtrue android.enableJetifiertrue org.gradle.jvmargs-Xmx2048m -Dfile.encodingUTF-8模块级build.gradle中依赖版本需统一dependencies { implementation androidx.appcompat:appcompat:1.6.1 implementation com.google.android.material:material:1.10.0 implementation androidx.constraintlayout:constraintlayout:2.1.4 implementation androidx.room:room-runtime:2.6.1 implementation androidx.room:room-ktx:2.6.1 implementation com.squareup.retrofit2:retrofit:2.9.0 implementation com.squareup.retrofit2:converter-gson:2.9.0 implementation com.github.PhilJay:MPAndroidChart:v3.1.0 // 注意MPAndroidChart v3.1.0 已兼容 AndroidX无需额外适配 }提示若出现Duplicate class android.support.v4.app.Fragment错误说明某依赖仍引用旧 support 库。执行./gradlew app:dependencies --configuration debugCompileClasspath查看依赖树定位问题库并强制排除implementation(com.some.library:old-version) { exclude group: com.android.support, module: support-v4 }4.2 ADB 日志精准过滤技巧健康指标同步失败时需快速定位是网络层还是数据库层问题# 过滤 Retrofit 相关日志含请求URL与响应码 adb logcat | grep -i retrofit\|okhttp # 查看 Room 执行的 SQL 语句需开启 DEBUG 模式 adb logcat | grep -i room\|database # 监控 WorkManager 同步任务状态 adb logcat | grep -i workmanager\|healthsync更高效的方式是在 Android Studio Logcat 中设置过滤器过滤器名称正则表达式用途NetworkDebugretrofit.okhttp.DBDebugRoom|Database|INSERT|SELECT查看数据库操作SyncTraceHealthSyncWorker|markAsSynced|insertAll跟踪同步逻辑流4.3 真机调试关键配置项小米/华为等厂商手机需手动开启 USB 调试高级选项连接手机后在「开发者选项」中找到USB调试安全设置并启用若adb devices显示unauthorized需在手机弹窗中点击「始终允许」针对 Android 12 设备WorkManager默认使用ConstraintManager需在AndroidManifest.xml中声明provider android:nameandroidx.startup.InitializationProvider android:authorities${applicationId}.androidx-startup android:exportedfalse android:grantUriPermissionstrue meta-data android:nameandroidx.work.impl.WorkManagerInitializer android:valueandroidx.startup / /provider否则PeriodicWorkRequest将无法注册。5. 毕业答辩高频问题预判与源码验证技巧5.1 评审老师最可能追问的 3 类技术细节问题类型典型提问应答要点需结合源码路径说明架构合理性“为什么不用 Firebase Realtime Database 替代自建 REST API”指出Firebase在国内访问稳定性不可控且毕业设计要求体现完整前后端协作能力展示ApiService.java中对 401/404/500 的分级处理逻辑onFailure()中区分网络异常与业务异常数据一致性“离线录入多条健康数据后网络恢复如何保证不重复提交”演示HealthMetricDao.getUnsyncedMetrics()返回结果被WorkManager逐条处理且markAsSynced()在Call.enqueue()的onResponse()中执行确保仅成功后才更新状态性能优化“MPAndroidChart 渲染百条数据是否卡顿做了哪些优化”展示lineChart.setVisibleXRangeMaximum(30)限制可见X轴数量指出setData()前调用clearValues()避免内存累积强调Entry构造时直接传入原始时间戳而非格式化字符串5.2 源码包结构验证清单答辩前必查打开 ZIP 源码包后立即确认以下目录与文件存在且内容合理路径必含内容验证方式app/src/main/java/com/example/healthassistant/network/ApiService.java包含POST(/auth/login)等至少 5 个接口定义检查Headers(Content-Type: application/json)是否存在app/src/main/java/com/example/healthassistant/database/HealthMetricDao.javaQuery(SELECT * FROM health_metrics WHERE isSynced 0)方法确认isSynced字段在 Entity 中定义为booleanapp/src/main/res/layout/activity_health_trend.xmlcom.github.mikephil.charting.charts.LineChart标签检查android:idid/lineChart是否存在app/src/main/java/com/example/healthassistant/work/HealthSyncWorker.javaNonNull Override public Result doWork()方法确认其中调用apiService.submitHealthMetric()并处理Result.success()注意若答辩现场演示 APK务必提前在真机安装并完成一次完整流程注册→录入3天指标→查看趋势图→退出重进验证数据留存。避免使用模拟器因其传感器模拟精度不足血压/心率录入易被质疑真实性。使用adb shell dumpsys battery检查设备电量是否 20%防止演示中途关机用adb shell input keyevent KEYCODE_HOME快速切回桌面展现操作熟练度。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门