Skip to content

Repository files navigation

Charge Flow

一个 Android 充电记录应用。通过前台服务每秒采样电量,自动记录每次充电过程,将 0-100% 划分为 10 个 10% 档位统计每档耗时,使用密集窗口过滤离群值,预测充满时间。

功能特性

  • 自动记录:检测到充电自动开始记录,拔出充电器或充到 100% 自动结束
  • 分段统计:将 0-100% 分为 10 档(每 10%),统计每档平均耗时
  • 充满预测:基于历史平均数据,实时估算当前电量到 100% 的剩余时间
  • 两种查看模式:「平均」展示历史均值,「上次」展示最近一次充电数据
  • 常驻通知:通知栏显示当前电量、充电进度、已用时长、预计充满时间
  • 开机自启:支持开机和应用更新后自动启动监听服务
  • 异常过滤:密集窗口算法自动过滤边充边玩、换用慢充等异常数据

技术栈

技术 用途
Kotlin 主语言
Jetpack Compose 声明式 UI
Room 本地数据库
Foreground Service 后台持续监听
KSP Room 注解处理
Material 3 UI 组件

系统要求

  • Android 8.0 (API 26) 及以上
  • targetSdk 35

构建

# 克隆仓库
git clone https://github.com/ymlyyy/ChargeRecord.git
cd ChargeRecord

# 构建 Debug APK
./gradlew assembleDebug

# 构建 Release APK
./gradlew assembleRelease

项目结构

app/src/main/java/com/mulin/chargerecord/
├── MainActivity.kt      # 主界面 + 前台服务 + 充电记录逻辑 + 充满预测
└── ChargeDb.kt          # Room 数据库 + Repository + 过滤与平均算法

核心算法

1. 采样与会话管理

前台服务 ChargeRecordService 每秒读取一次电池状态:

  • 通过 BatteryManager.BATTERY_PROPERTY_CAPACITY 获取当前电量百分比
  • 通过 ACTION_BATTERY_CHANGED 广播判断是否正在充电

ChargeRuntime 维护一个状态机管理充电会话的生命周期:

  • 开始:检测到充电开始(且电量 < 100%)时,记录起始时间和电量,开始收集数据点
  • 记录中:电量百分比发生变化时追加 (percent, timestamp) 数据点(百分比未变则不重复记录)
  • 结束:充电停止(拔出充电器)或电量达到 100% 时结束,触发分段计算和存储
  • 丢弃:如果结束时电量没有增长,不保存本次记录

2. 分段拆分 (buildSegmentsFromPoints)

充电结束后,将收集到的数据点序列拆分为 10% 的档位(0-10%, 10-20%, ..., 90-100%)。

遍历相邻数据点对 (a, b),按时间比例将耗时分配到各档位:

电量从 35% 充到 42%,耗时 420 秒

→ 30-40% 档:分配 5/7 × 420 = 300 秒(观测跨度 5%)
→ 40-50% 档:分配 2/7 × 420 = 120 秒(观测跨度 2%)

跨多个档位的数据点会用游标逐档拆分,同一档位的多次贡献累加。

大跨度过滤:相邻数据点电量差超过 3% 时跳过,避免后台采样中断(如系统休眠)导致的虚假大跨度跳变污染统计。

3. 归一化

每个档位存储两组时间:

  • observedSeconds:该档位内实际观测到的充电秒数
  • observedSpan:该档位内实际覆盖的电量百分比

写入数据库前归一化到完整的 10% 跨度:

normalizedSeconds = observedSeconds × 10 / observedSpan

例如:30-40% 档只观测到 5% 的跨度(35%→40%),耗时 300 秒,归一化后为 600 秒,代表该档充满 10% 的等效耗时。

4. 密集窗口过滤 (filterByDenseWindow)

查询每个档位最近 50 条归一化样本后,使用密集窗口算法过滤异常值:

  • 将样本按升序排序
  • 用滑动窗口(宽度 = 3 分钟 = 180 秒)遍历,找到包含样本数最多的连续区间
  • 仅保留该区间内的样本

这个设计针对使用同一充电器的场景:正常充电的每档耗时会稳定聚集,而边充边玩、临时换慢充头、系统休眠中断等导致的异常样本会偏离主簇被过滤掉。

样本数 ≤ 2 时不过滤,直接返回全部。

5. 平均值计算 (average)

对过滤后的样本求简单算术平均:

平均耗时 = 样本之和 / 样本数

样本为空时返回 -1(UI 显示 "NA")。

6. 充满时间预测 (getEstimatedFullSeconds)

基于「平均」模式的历史均值,计算当前电量到 100% 的预计剩余时间:

  • 找到当前电量所在的 10% 档位
  • 当前档位按剩余比例折算(例如电量 67%,该档均值 600 秒,则剩余 3/10 × 600 = 180 秒)
  • 加上后续所有档位的完整均值

任意一个档位没有有效数据时返回 -1(UI 显示 "NA")。

7. 0-100% 总耗时 (getFullChargeSeconds)

将 10 个档位的均值直接相加,任意档位缺失则返回 -1。

数据库

Room 数据库 charge_log.db,两张表:

用途 主要字段
charge_session 充电会话 startTime, endTime, startBattery, endBattery, durationSeconds, segmentCount
charge_segment 分段样本 sessionId, binStart, binEnd, observedSpan, observedSeconds, normalizedSeconds, createdAt

查询规则:

  • 每个档位查询最近 50 条样本(按 createdAt 降序),用于密集窗口过滤后计算均值
  • 最近记录显示最近 10 次充电会话
  • 「上次」模式读取最新一次会话的所有分段数据

权限说明

权限 用途
POST_NOTIFICATIONS 显示常驻通知(Android 13+)
RECEIVE_BOOT_COMPLETED 开机自启
FOREGROUND_SERVICE 前台服务
WAKE_LOCK 保持 CPU 唤醒

License

MIT

About

Android 充电记录应用,自动记录充电过程,分段统计每档耗时,预测充满时间

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages