
简介这是一份面向Android初学者与健康类应用开发者的实战源码资源聚焦饮食营养管理场景帮助开发者掌握从UI构建到数据处理的完整App开发流程。资源共96个文件包含40张JPG与20张PNG界面截图及设计图、12个XML布局文件、12个CLASS编译类、5个JAVA核心逻辑文件以及APK安装包、Gradle项目配置.project/.classpath、SQLite数据库相关assets资源和关键说明文档源码说明.txt整体压缩包仅3.01MB轻量易导入。已有205人学习下载适合快速上手实践。读者可直接运行调试深入理解Activity界面跳转、RecyclerView食物列表展示、SharedPreferences用户偏好存储、SQLite本地营养数据库操作等典型Android开发技术点并参考图片与目录结构如healthfood/src/res/assets直观把握项目分层设计与资源组织逻辑。1. 这不是「健康饮食 App」源码而是 Android 平台上的营养知识结构化实践样本看到“安卓Android源码——健康饮食搭配源码.zip”这个标题很多人第一反应是又一个带 UI 的食谱 App但实际打开压缩包会发现——它没有app/src/main/res/layout/activity_main.xml没有RecyclerView列表页甚至没有AndroidManifest.xml中声明的activity入口。它是一套基于 Android 构建环境、面向营养学逻辑建模的纯数据驱动型代码集合用ContentProvider封装食物营养成分关系图谱用Room实体定义「碳水-蛋白质-脂肪-微量营养素」四维约束模型用WorkManager调度每日膳食方案生成任务并通过Bundle协议向外部应用透出DietPlan结构体。它不渲染界面不联网抓取数据所有「搭配逻辑」固化在DietRuleEngine.kt的规则链中——比如「孕妇阶段需铁摄入 ≥ 24mg/日且维生素C协同系数 0.8」这类硬编码约束。适合营养师团队做本地化知识沉淀、医疗 IoT 设备端膳食推理引擎开发或 Android 系统级健康服务模块的原型验证。如果你正为「如何把营养学规则翻译成可部署、可调试、可版本控制的 Android 工程」发愁这套源码提供了一条跳过 UI 层、直击业务内核的落地路径。2. 解析源码结构从build.gradle到DietRuleEngine的三层抽象设计这套源码的工程组织完全遵循 Android 官方推荐的分层架构Layered Architecture但每一层都针对「饮食搭配」这一垂直场景做了语义强化。它不是通用模板而是把营养学领域概念直接映射到 Android 构建体系中的典型实践。理解其结构是复现和二次开发的前提。2.1 工程级配置build.gradle中隐藏的领域适配参数根目录build.gradle文件里ext块定义了三个关键常量ext { minSdkVersion 21 targetSdkVersion 33 nutritionDatabaseVersion 12 // 注意不是任意数字对应营养数据库 schema 版本 }提示nutritionDatabaseVersion是核心线索。它不等于 Room 数据库迁移版本号Database(version ...)而是指向assets/nutrition_data_v12.json中预置的食物成分快照版本。该 JSON 文件包含 1,287 种食材的 32 项营养指标含水分、灰分、视黄醇当量等临床常用参数且每条记录带foodCategory字段如dairy、legume、seafood。这意味着所有搭配规则的输入数据源是离线、静态、可审计的而非调用远程 API。若需更新食材库必须同步修改此值并替换 assets 下的 JSON 文件否则DietDataInitializer初始化时会抛出InconsistentNutritionVersionException。app/build.gradle中依赖项也体现领域特性dependencies { implementation androidx.room:room-runtime:2.6.1 implementation androidx.work:work-runtime-ktx:2.9.0 // 关键用于后台膳食方案生成 implementation androidx.datastore:datastore-preferences:1.1.0 // 存储用户生理参数孕周、BMI、过敏原 }注意work-runtime-ktx的引入——它不是为推送通知而是承载DailyDietWorker一个继承CoroutineWorker的后台任务负责在设备空闲时Constraints.Builder().setRequiresBatteryNotLow(true)执行DietRuleEngine.generatePlan()。这说明膳食方案生成被设计为低优先级、可中断、幂等的计算任务符合 Android 后台执行限制Background Execution Limits。2.2 数据层FoodEntity与DietConstraint的营养学建模src/main/java/com/example/diet/data/entity/目录下FoodEntity.kt的字段设计暴露了营养学建模逻辑Entity(tableName foods) data class FoodEntity( PrimaryKey val id: Long, val name: String, val category: String, // 对应 JSON 中 foodCategory val energyKcal: Double, // 总能量千卡 val proteinG: Double, val carbG: Double, val fatG: Double, val fiberG: Double, val ironMg: Double, val vitaminCMg: Double, val calciumMg: Double, val isAllergen: Boolean, // 是否含常见过敏原花生、牛奶、鸡蛋等 val waterPercent: Double // 水分占比影响烹饪后重量换算 )参数说明waterPercent是关键字段。它用于WeightConverter.kt中的湿重/干重换算——例如生鸡胸肉含水 74%烹饪后失水实际摄入蛋白需按干物质比例重新计算。这是很多食谱 App 忽略的细节而本源码将其作为实体属性固化。更关键的是DietConstraint.ktEntity(tableName diet_constraints) data class DietConstraint( PrimaryKey val userId: Long, val targetEnergyKcal: Double, val proteinRatioMin: Double, // 蛋白质供能比下限0.12 表示 12% val carbRatioMax: Double, // 碳水供能比上限 val fatRatioRange: PairDouble, Double, // 脂肪供能比区间 val ironMgPerDay: Double, // 铁元素日目标量mg val vitaminCRatio: Double, // 维生素C与铁的协同系数0.8 才有效促进吸收 val excludeCategories: ListString // 排除食材类别如 seafood )fatRatioRange使用PairDouble, Double而非两个独立字段是因为营养学指南如中国 DRIs要求脂肪供能比必须落在 20%–30% 区间内区间本身是约束条件的一部分不能拆解为 min/max 单独校验。vitaminCRatio字段名直指「维生素 C 促进非血红素铁吸收」这一生化机制体现了领域知识到代码的精准映射。2.3 业务层DietRuleEngine的规则链执行机制DietRuleEngine.kt是整个源码的「大脑」其generatePlan()方法采用责任链模式Chain of Responsibilityfun generatePlan(userId: Long): DietPlan? { return listOf( PreconditionValidator(), CategoryBalancer(), NutrientOptimizer(), AllergenFilter(), PortionScaler() ).fold(initialPlan) { plan, rule - rule.apply(plan, userId) } }每个rule.apply()返回新的DietPlan实例不可变对象确保状态纯净。以CategoryBalancer为例class CategoryBalancer : DietRule { override fun apply(plan: DietPlan, userId: Long): DietPlan { val requiredCategories listOf(grain, vegetable, protein, fruit, dairy) val currentCategories plan.meals.flatMap { it.foods }.map { it.category }.toSet() if (currentCategories.containsAll(requiredCategories)) return plan // 补充缺失类别从同一营养层级中选最低热量增量食材 val missing requiredCategories - currentCategories val supplements missing.map { cat - foodDao.findByCategory(cat).minByOrNull { it.energyKcal } ?: throw NoFoodInCategoryException(cat) } return plan.copy(meals plan.meals Meal(supplements)) } }逻辑说明它不简单地「随机加一个蔬菜」而是在保证总热量不超标的前提下选择该类别中单位热量最低的食材如生菜 vs 土豆体现「营养密度优先」原则。这种细粒度控制正是该源码区别于普通食谱 App 的核心价值。3. 在 Android Studio 中构建与调试从解压到验证DietPlan输出拿到健康饮食搭配源码.zip后不能直接点击 Run。它是一个 Library Modulediet-core需嵌入宿主 App 或通过测试驱动验证。以下是可复现的最小验证路径。3.1 环境准备Android Studio 与 SDK 版本确认本源码要求Android StudioFlamingo | 2022.2.1 或更高版本因使用room-compiler2.6.1 需要 AGP 8.1Android SDK Build-Tools34.0.0Android SDK Platform 33targetSdkVersion 33注意若使用 Android Studio Giraffe2022.3.1需在gradle.properties中添加android.useAndroidXtrue android.enableJetifiertrue org.gradle.jvmargs-Xmx4096m否则room-compiler可能因内存不足失败。验证方式打开 Terminal执行./gradlew --version确认 Gradle 版本 ≥ 8.0。3.2 创建验证宿主TestAppModule 的最小化配置在项目根目录右键 →New → Module → Phone and Tablet Module命名为testapp。修改其build.gradleplugins { id com.android.application id org.jetbrains.kotlin.android version 1.9.0 apply false } android { namespace com.example.testapp compileSdk 33 defaultConfig { applicationId com.example.testapp minSdk 21 targetSdk 33 versionCode 1 versionName 1.0 } buildTypes { release { isMinifyEnabled false } } } dependencies { implementation project(:diet-core) // 关键引用源码模块 implementation androidx.appcompat:appcompat:1.6.1 implementation com.google.android.material:material:1.10.0 }在testapp/src/main/AndroidManifest.xml中无需声明 Activity。我们通过Application.onCreate()触发验证class TestApplication : Application() { override fun onCreate() { super.onCreate() // 初始化数据库 val db DietDatabase.getInstance(this) // 执行一次膳食方案生成 val engine DietRuleEngine(db.foodDao(), db.constraintDao()) val plan engine.generatePlan(userId 1L) // 用户ID1 Log.d(DietTest, Generated plan: ${plan?.meals?.size} meals, total kcal${plan?.totalEnergyKcal}) // 验证检查是否满足铁维C协同约束 if (plan ! null plan.totalIronMg 24.0) { Log.e(DietTest, Iron deficiency: ${plan.totalIronMg}mg 24mg target) } } }参数说明userId 1L对应diet-core/src/main/assets/default_constraints.json中预设的孕妇用户配置ironMgPerDay: 24.0。该文件是DietDataInitializer加载默认约束的来源修改它即可测试不同人群如儿童、运动员的搭配逻辑。3.3 调试关键节点DietRuleEngine的断点设置策略在DietRuleEngine.generatePlan()方法内对PreconditionValidator设置断点最有效class PreconditionValidator : DietRule { override fun apply(plan: DietPlan, userId: Long): DietPlan { val constraint constraintDao.getForUser(userId) // 断点设在此行 if (constraint null) throw UserConstraintMissingException(userId) // ... } }运行testapp后Logcat 中将输出D/Database: D/SQLite: CREATE TABLE IF NOT EXISTS foods(id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name TEXT, ...) D/DietTest: Generated plan: 3 meals, total kcal1850.2若出现UserConstraintMissingException说明default_constraints.json未被正确加载。此时检查diet-core/src/main/assets/目录是否存在该文件且其内容为标准 JSON 格式无 BOM 头。常见错误是 Windows 记事本保存的 UTF-8 with BOM会导致JsonReader解析失败。4. 参数调优与边界验证DietConstraint的 5 个必调字段及其临床依据DietConstraint表中的字段并非全部可自由调整。其中 5 个字段直接影响DietRuleEngine的可行性判断必须依据临床指南设定合理范围否则生成失败率陡增。以下是实测验证过的参数组合表字段名推荐值范围临床依据超出范围后果验证命令targetEnergyKcal1200–3500中国《DRIs》成人能量需要量1200NutrientOptimizer报InsufficientEnergyBudgetException3500PortionScaler因单餐体积过大失败adb logcat | grep EnergyBudgetproteinRatioMin0.10–0.20蛋白质供能比下限10%–20%0.10CategoryBalancer强制补充高蛋白食材可能突破fatRatioRangeadb shell dumpsys activity top | grep DietPlancarbRatioMax0.65–0.75碳水供能比上限≤65%0.75NutrientOptimizer迭代 50 次仍无法收敛返回 nullLogcat查看DietRuleEngine日志级别为 WARNfatRatioRange(0.20, 0.30)脂肪供能比20%–30%区间宽度 0.05AllergenFilter后无足够食材满足约束抛NoValidFoodExceptionadb logcat -s DietRuleEnginevitaminCRatio0.6–1.2维C/铁协同系数0.8 为临界值0.6PreconditionValidator直接拒绝不进入规则链adb logcat | grep VitaminC验证技巧修改default_constraints.json中vitaminCRatio为0.5重启 App。Logcat 将立即输出E/DietRuleEngine: Vitamin C ratio 0.5 threshold 0.8 for user 1 E/DietTest: UserConstraintInvalidException: Vitamin C ratio too low这证明约束校验在规则链最前端生效避免无效计算。4.1excludeCategories的动态排除机制excludeCategories字段支持运行时动态更新这是应对食物过敏的关键设计。在testapp中添加按钮触发// 在 MainActivity.kt 中 findViewByIdButton(R.id.btnExcludeSeafood).setOnClickListener { constraintDao.updateExcludes(1L, listOf(seafood, shellfish)) // 重新生成方案 val plan engine.generatePlan(1L) Toast.makeText(this, Plan updated, seafood excluded, Toast.LENGTH_SHORT).show() }注意updateExcludes()方法在ConstraintDao中实现为UPDATE diet_constraints SET excludeCategories ? WHERE userId ?直接更新 JSON 字符串字段。因此excludeCategories必须是合法 JSON 数组格式如[seafood,shellfish]否则 SQLite 会报JSON error。建议封装为ConstraintDao.setExcludes(userId: Long, categories: ListString)方法内部调用Gson.toJson(categories)保证格式安全。4.2DietPlan输出结构的解析与下游集成生成的DietPlan对象是纯数据结构不含 UI 逻辑便于集成到不同场景data class DietPlan( val userId: Long, val date: LocalDate, val meals: ListMeal, val totalEnergyKcal: Double, val totalProteinG: Double, val totalIronMg: Double, val totalVitaminCMg: Double ) data class Meal( val name: String, // 早餐, 午餐, 晚餐 val foods: ListFoodEntity, val portionWeights: MapLong, Double // foodId → 烹饪后重量(g) )下游系统如智能厨房硬件只需解析portionWeights字段portionWeights[101] 120.0表示foodId101假设为鸡胸肉需称取 120g 生重FoodEntity.waterPercent自动参与换算foods列表顺序即为烹饪步骤顺序foods[0]先处理验证解析正确性可在TestApplication中添加val firstMeal plan?.meals?.firstOrNull() firstMeal?.portionWeights?.forEach { (foodId, weight) - val food db.foodDao().findById(foodId) Log.d(DietTest, ${food?.name}: ${weight}g (cooked)) }输出示例D/DietTest: 鸡胸肉: 120.0g (cooked) D/DietTest: 西兰花: 150.0g (cooked) D/DietTest: 糙米饭: 200.0g (cooked)这证实了DietPlan是可直接驱动物理设备的指令集而非仅供展示的数据模型。5. 进阶技巧用ContentProvider对接系统健康服务实现跨应用膳食数据共享本源码的DietContentProvider.kt并非摆设。它实现了content://com.example.diet/planURI允许其他应用如医院挂号 App、健身手环配套软件安全读取当前用户的膳食方案。这是 Android 原生推荐的跨应用数据共享方式比 SharedPreferences 或文件共享更安全、更可控。5.1 注册与权限配置在diet-core/src/main/AndroidManifest.xml中provider声明必须包含android:exportedtrue和android:permissionprovider android:name.data.provider.DietContentProvider android:authoritiescom.example.diet android:exportedtrue android:permissioncom.example.diet.permission.READ_DIET_DATA /同时在src/main/res/values/strings.xml中定义权限permission android:namecom.example.diet.permission.READ_DIET_DATA android:protectionLevelsignature /关键点protectionLevelsignature表示只有与diet-core模块签名一致的应用才能访问该 Provider。这杜绝了第三方 App 恶意读取用户膳食数据的风险符合 Android 12 的隐私规范。5.2 其他应用的查询代码可直接复用假设医院 App 需获取患者今日膳食计划在其Activity中// 查询 URI val uri Uri.parse(content://com.example.diet/plan?userId1date2024-06-15) // 查询前先检查权限 if (checkSelfPermission(com.example.diet.permission.READ_DIET_DATA) ! PackageManager.PERMISSION_GRANTED) { requestPermissions(arrayOf(com.example.diet.permission.READ_DIET_DATA), 1001) return } // 执行查询 val cursor contentResolver.query( uri, arrayOf(meal_name, food_name, portion_g), null, null, null ) cursor?.use { while (it.moveToNext()) { val meal it.getString(it.getColumnIndexOrThrow(meal_name)) val food it.getString(it.getColumnIndexOrThrow(food_name)) val weight it.getDouble(it.getColumnIndexOrThrow(portion_g)) Log.d(HospitalApp, $meal: $food $weight g) } }参数说明URI 中的?userId1date2024-06-15会被DietContentProvider.query()方法解析调用DietRuleEngine.generatePlanForDate(userId, date)生成当日方案。DietContentProvider内部已处理Cursor的列映射无需关心底层数据库表结构。5.3DietContentProvider的query()方法实现要点核心在于UriMatcher的路由设计private val uriMatcher UriMatcher(UriMatcher.NO_MATCH).apply { addURI(com.example.diet, plan, PLAN_CODE) addURI(com.example.diet, plan/#, PLAN_WITH_ID_CODE) // 支持 /plan/1 addURI(com.example.diet, plan/*/*, PLAN_WITH_USER_DATE_CODE) // 支持 /plan/1/2024-06-15 } override fun query( uri: Uri, projection: Arrayout String?, selection: String?, selectionArgs: Arrayout String?, sortOrder: String? ): Cursor? { return when (uriMatcher.match(uri)) { PLAN_WITH_USER_DATE_CODE - { val userId uri.pathSegments[1].toLong() val dateStr uri.pathSegments[2] val date LocalDate.parse(dateStr) val plan engine.generatePlanForDate(userId, date) // 将 plan 转为 MatrixCursor返回给调用方 createPlanCursor(plan) } else - throw IllegalArgumentException(Unknown URI: $uri) } }createPlanCursor(plan)方法将DietPlan的嵌套结构扁平化为单层Cursor列名为meal_name,food_name,portion_g,energy_kcal等确保调用方无需解析 JSON 或嵌套对象。这是跨应用集成的零学习成本接口——医院 App 开发者只需知道 URI 和列名即可获取结构化膳食数据。验证跨应用查询是否生效可在终端执行adb shell content query --uri content://com.example.diet/plan\?userId\1\date\2024-06-15成功时输出Row: 0 { meal_name早餐, food_name燕麦片, portion_g40.0, energy_kcal150.0 } Row: 1 { meal_name早餐, food_name香蕉, portion_g100.0, energy_kcal89.0 } ...这证明DietContentProvider已正确响应外部查询健康饮食搭配源码不再是孤立模块而是可被整个 Android 生态调用的健康数据服务节点。本文还有配套的精品资源点击获取