校园助手 YU · Android 应用
把宿舍电费、课程和校园卡放进一个 Android 应用。既能查当前余额,也能留下历史记录、估算还能用多久,并把常用信息放到桌面上。
背景与目标
校园里经常要查的几件事,入口却不在同一处:宿舍电量在一卡通服务里,课程在教务系统里,校园卡余额和交易又是另一组页面。每天重复打开网页、登录和查找信息,操作本身并不复杂,但次数多了就显得零碎。我开发 YU,是想把这些与日常生活直接相关的内容整理到一个 Android 应用中,随手就能看到。
项目从“查询当前结果”继续往前走了一步。电费不仅需要显示余额,还需要保存历史,帮助判断消耗变化和大致还能用多久;课表不仅需要导入,还需要能够在桌面查看;校园卡不仅需要查卡片信息,也需要看到月账单、消费趋势和交易记录。信息留在本地后,工具才有机会提供连续的观察,而不是每次查询都重新开始。
这是我的个人 Android 项目,主要源码在 YU_2 的 master 分支。默认 main 分支只有发布说明,不能用它判断应用没有实现。我独立开展需求整理、架构和页面组织、功能开发与版本维护,围绕电费、课表、校园卡以及更新帮助等模块逐步完善。Python 版 YU 和小爱课表脚本是关联工具,各自的实现范围与 Android 应用分开展示。
使用问题与数据约束
余额变化不能直接当作用电量
电费估算的第一道问题是识别数据来源。余额下降通常对应消耗,余额上升可能是充值;两次记录间隔不同,下降金额也不能直接比较。如果把一次充值混进日均消耗,预计天数会偏离实际。因此,我把当前余额与历史记录分开处理,结合时间跨度整理下降记录和充值记录,再决定采用哪一段历史。
另一个问题是数据不足。刚设置宿舍的新用户可能没有连续历史,最近几天也可能记录较少。应用需要说明估算来自短期、中期、长期历史,还是充值模式与回退估计,不能所有情况下都给出同样确定的结果。预计天数是基于余额和规则的参考值,不代表通过机器学习模型验证过的预测准确率。
登录会话和网络状态同样影响数据获取。校园系统的接口需要当前登录信息,应用要在界面、请求和本地保存之间处理异常,而不是因为一次失败就丢掉历史数据。桌面组件还有单独的刷新与展示条件,后台任务也受 Android 调度约束。因此,有刷新代码不等于能够保证在每一个指定秒点完成查询。
技术架构与模块组织
应用使用 Kotlin 开发,按照 ViewModel、Repository、Room 数据库和网络服务组织代码。ViewModel 管理界面需要的状态与操作,Repository 协调请求、数据转换和本地保存,Room 通过 Entity、Dao 与 Database 管理记录。Retrofit 和 OkHttp 负责接口访问,Kotlin 协程把查询与存储操作放到合适的线程中,减少等待网络时对页面操作的影响。
界面结合 Jetpack Compose 与 XML 布局,按电费、课表、校园卡等业务划分文件和页面。这样既保留不同页面原有的布局实现,也能逐步组织新的交互组件。模块划分的意义在于把相对独立的业务放在自己的数据结构和状态管理中,例如电费历史不需要与校园卡流水共用一套含义模糊的记录。
后台刷新使用 WorkManager 的 CoroutineWorker,配合电费与课表的 AppWidgetProvider 展示桌面信息。组件提供的是 Android 桌面入口,与应用内完整页面分工:桌面适合快速看当前余额或课程,页面适合查看历史、筛选信息和调整设置。代码也包含深色主题、帮助、隐私说明和更新入口,让应用具备持续使用所需的外围内容。
核心功能与实现
多周期电费分析
ElectricityRepository 分别读取最近 7 天、30 天和 90 天的数据,整理日期与余额,再从相邻记录中区分用电下降与充值上升。消耗记录不仅保留金额,也保留跨越的时间,计算时按有效天数得到日均值。近期数据与长期数据的选择取决于是否有足够记录,而不是把每一种周期都当成同样可靠。
代码对有短期与中期记录的情况进行加权,例如采用近期日均值并参考中期变化;若近期记录不足,则寻找更长期的历史。没有有效消耗记录但存在充值记录时,会尝试分析充值模式;仍缺少依据时再使用估计值回退。数据来源通过枚举与界面状态传递,让使用者能够区分“来自历史”与“暂时估计”。
剩余天数计算还考虑不同阶段的日均消耗变化,并把最终估算限制在 90 天上限。这个上限是应用的保守规则,不是性能指标。它避免少量历史或较低日均值把结果放大成不合理的长期预测。实际消耗仍受人数、季节和用电习惯影响,所以这一功能的用途是提供充值判断参考,而不是替代真实计量。
历史保存与文件导入导出
查询结果保存进本地数据库,变化检测帮助组织电费历史。应用保留时间、余额、楼栋、房间、用电量和充值等字段,后续分析能够围绕这些记录继续进行。历史与当前值分开后,界面可以查看已有数据,也能在网络异常时保留之前的观察依据,而不必把所有内容绑定到一次实时请求。
ElectricityDataExportImport 使用 Android ContentResolver 和文件 URI 读写 JSON、CSV。JSON 导出保留结构化字段,CSV 提供便于表格软件查看的形式;导入时检查必要字段并解析时间与余额。文件入口的意义是让用户能够转移、保存和检查自己的历史,而不是把数据只能留在应用内部。它也让版本维护和用户排错有了更直接的数据依据。
课表导入与桌面展示
课表模块通过 WebView 打开教务登录页面,在用户已有登录状态的基础上注入解析脚本。ScheduleImportActivity 组织页面加载、JavaScript 执行和 Android 回调,接收解析后的课程数组;代码还包含进度提示、学期选择相关适配和备用解析路径,用于应对页面结构及返回内容的差异。
课程进入本地课表数据模型后,由 Repository、Dao 和 ViewModel 管理,再提供到课表页面与桌面组件。这里需要处理的不只是课程名称,还包括教师、教室、星期、周次和节次。如果这些维度在导入阶段混淆,之后的桌面显示再精致也会给出错误时间。因此,解析结果的字段含义与本地存储结构必须一致。
校园卡信息与分页流水
CampusCardRepository 提供卡片信息、月账单、消费趋势和交易列表查询。分页请求把接口返回记录映射到应用内的数据模型,再根据总记录数和页面结果计算页数,将获取的流水保存到 Room。CampusCardViewModel 组织加载状态、月份切换和页面数据,使当前选择与网络结果能够对应,减少界面直接操作接口细节。
流水保存的目的,是让“查到了多少记录”和“当前显示哪一批记录”有清晰依据。遇到账号缺失、响应异常或记录字段不完整时,Repository 返回明确结果,映射过程中也处理无法使用的数据。它没有把一次网络成功简单等同于数据完整,而是把账号、分页、数据转换和保存分别作为需要处理的环节。
后台刷新和组件同步
ElectricityScheduledWorker 在执行时检查当前时间及登录信息,符合条件后获取电费、保存数据、检查变化并更新桌面组件。查询没有获得有效结果时会进入重试或失败返回,避免假装刷新完成。组件刷新与数据库更新放在同一条工作流程里,是为了减少主页面已有新值而桌面仍显示旧值的情况。
不过,WorkManager 的执行时间受系统条件影响,代码中的时间窗口检查还可能使迟到的任务直接跳过。因此,我把这部分写为已有后台刷新实现,而不承诺精确到秒的定时监控。持续维护时需要在不同系统版本、网络和省电条件下检查实际行为,再决定是否调整调度策略和时间窗口。
完成目的与交付内容
YU 已把电费历史分析、课表导入、校园卡查询和桌面展示集中到 Android 应用中,形成从查询到保存、从历史到估算、从页面到组件的完整功能组合。项目完成的目的,是减少日常重复进入多个校园服务的操作,同时把一次性的查询结果变成能够连续使用的数据。
对外展示时,我不使用此前已经删除的日活和准确率数字。源码能够确认数据流程和功能入口,但用户规模、估算误差、后台可靠性以及各机型适配仍需要对应的实际记录。应用内的电费分析也与 Python 工具的占位消耗字段明确区分,避免借一个项目的成熟功能扩大另一个项目的实现范围。
迭代经验与后续方向
这个项目让我同时接触 Android 架构、网页登录适配、网络接口、本地数据库和桌面组件。很多问题出现在它们的连接处:网页登录成功后接口会话是否仍有效,课表脚本返回的字段能否被正确存储,数据库更新后组件是否同步,导入文件中的时间是否可以解析。处理这些问题时,清楚的模块边界比单纯增加功能入口更有帮助。
后续维护重点是登录与校园接口变化、历史数据质量、导入异常和后台调度的实际表现。对电费估算,应持续区分历史来源与规则回退,提供足够信息解释结果;对课表导入,应保留可检查的数据转换过程。继续迭代的方向是让已有功能在日常环境中更稳定,而不是用没有验证过的性能数字包装它。