血糖分析功能在健康管理类App中非常常见,无论是配合蓝牙血糖仪采集数据,还是用户手动录入,最终都需要对原始数据做清洗、统计和可视化。Glucose相关的分析逻辑看似简单,无非是求平均值、找最大最小值,但实际做起来会涉及单位换算、异常值过滤、趋势判断等一系列细节问题。这篇文章以一个完整的Android项目为例,从数据建模、分析算法到图表展示和单元测试,把血糖分析测试的完整流程梳理一遍。

血糖数据的建模与存储
做血糖分析的第一步是设计合理的数据结构。一条血糖记录至少应包含测量值、测量时间、测量时段(空腹、餐前、餐后、睡前)和单位类型。国内常用mmol/L作为血糖单位,而欧美一些设备输出的则是mg/dL,两者之间的换算关系是1 mmol/L等于18 mg/dL。如果数据模型一开始就把单位固定死,后面接入不同设备时会很痛苦,所以建议把单位作为字段存进数据库。
存储方案推荐使用Room。Room是Android官方推荐的ORM框架,编译期会校验SQL语句,比直接拼SQL字符串安全得多。下面是一个血糖记录实体的定义:
@Entity(tableName = "glucose_records")
public class GlucoseRecord {
@PrimaryKey(autoGenerate = true)
public long id;
// 血糖值,统一存储为 mmol/L
public float value;
// 测量时间戳,毫秒
public long measureTime;
// 测量时段:0空腹 1餐前 2餐后 3睡前 4随机
public int period;
// 原始单位,"mmol/L" 或 "mg/dL"
public String unit;
public GlucoseRecord(float value, long measureTime, int period, String unit) {
if ("mg/dL".equals(unit)) {
// 统一换算成 mmol/L 再入库,方便后续分析
this.value = value / 18f;
} else {
this.value = value;
}
this.measureTime = measureTime;
this.period = period;
this.unit = unit;
}
}这里有一个关键设计:入库时就把所有数值统一换算成mmol/L,分析层就不需要关心单位问题。Dao层的查询也很简单,按时间倒序取最近N条,或者按时间段区间查询,为后面的统计分析提供数据源。需要注意的是,测量时间要用长整型时间戳存储,不要用格式化的日期字符串,否则按范围查询时性能和准确度都会受影响。
核心分析算法的实现
数据拿到之后,接下来就是分析逻辑。常见的分析指标包括:指定时间范围内的平均血糖、最高值、最低值、血糖达标率(比如3.9到10.0 mmol/L之间算达标),以及根据平均血糖估算糖化血红蛋白HbA1c。HbA1c的估算公式是:HbA1c = (平均血糖 + 4.69) / 1.59,这个公式来自ADAG研究,在健康类App中被广泛使用。
分析逻辑建议写成纯函数,也就是只依赖输入参数、不依赖任何Android组件的静态方法或普通类。这样做最大的好处是方便单元测试,不需要在测试环境里启动Android运行时。下面是核心分析类的实现:
public class GlucoseAnalyzer {
// 正常范围常量,单位 mmol/L
public static final float LOW_THRESHOLD = 3.9f;
public static final float HIGH_THRESHOLD = 10.0f;
/**
* 计算平均血糖,会先过滤异常值
*/
public static float average(List<GlucoseRecord> records) {
List<Float> valid = filterValid(records);
if (valid.isEmpty()) return 0f;
float sum = 0;
for (float v : valid) sum += v;
return sum / valid.size();
}
/**
* 过滤异常值:低于1.0 或高于33.3 视为无效数据
*/
public static List<Float> filterValid(List<GlucoseRecord> records) {
List<Float> result = new ArrayList<();
for (GlucoseRecord r : records) {
if (r.value >= 1.0f && r.value <= 33.3f) {
result.add(r.value);
}
}
return result;
}
/**
* 血糖达标率,返回0到100的百分比
*/
public static float inRangeRate(List<GlucoseRecord> records) {
List<Float> valid = filterValid(records);
if (valid.isEmpty()) return 0f;
int count = 0;
for (float v : valid) {
if (v >= LOW_THRESHOLD && v <= HIGH_THRESHOLD) count++;
}
return count * 100f / valid.size();
}
/**
* 根据平均血糖估算糖化血红蛋白
*/
public static float estimateHbA1c(float avgGlucose) {
return (avgGlucose + 4.69f) / 1.59f;
}
}异常值过滤这一步非常重要。真实场景中,蓝牙设备可能传输错误数据,用户手动录入时也可能误输入(比如把5.6录成56),如果没有过滤,一个极端值就能把平均值带偏,进而影响HbA1c的估算结果。人体血糖的生理极限大约在1.0到33.3 mmol/L之间,超出这个范围的数据基本可以判定为无效。
另外建议把低血糖和高血糖事件单独统计出来,比如低于3.9 mmol/L记为低血糖事件,持续高血糖超过某个时长给出提醒。这类事件信息对用户的价值比单纯的平均值更大,也是血糖分析类应用区别于普通记录工具的核心竞争力。
用MPAndroidChart绘制血糖趋势图
数据和分析结果都有了,可视化部分推荐使用MPAndroidChart这个图表库,它功能完善、性能不错,而且社区活跃。绘制血糖趋势图时,一般用折线图,横轴是时间,纵轴是血糖值,同时可以在图上标出正常范围的上下限,让用户一眼看出哪些点超出了正常区间。
下面是把血糖记录转成折线图数据集的示例代码:
private void setupChart(LineChart chart, List<GlucoseRecord> records) {
List<Entry> entries = new ArrayList<();
for (GlucoseRecord r : records) {
entries.add(new Entry(r.measureTime, r.value));
}
LineDataSet dataSet = new LineDataSet(entries, "血糖值");
dataSet.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER);
dataSet.setColor(Color.parseColor("#4CAF50"));
dataSet.setCircleColor(Color.parseColor("#388E3C"));
dataSet.setLineWidth(2f);
dataSet.setValueTextSize(10f);
// 绘制正常范围区间背景色
LimitLine lowLine = new LimitLine(3.9f, "低血糖线");
lowLine.setLineColor(Color.parseColor("#FF5722"));
lowLine.enableDashedLine(10f, 10f, 0f);
chart.getAxisLeft().addLimitLine(lowLine);
LimitLine highLine = new LimitLine(10.0f, "高血糖线");
highLine.setLineColor(Color.parseColor("#FF9800"));
highLine.enableDashedLine(10f, 10f, 0f);
chart.getAxisLeft().addLimitLine(highLine);
LineData lineData = new LineData(dataSet);
chart.setData(lineData);
chart.invalidate();
}横轴时间格式化需要自定义一个IAxisValueFormatter(新版本是ValueFormatter的子类),把时间戳格式化成MM-dd这样的短日期,否则默认会显示一长串原始时间戳数字,用户体验很差。图表的数据量也要控制,如果查询范围是一个月,动辄几百个数据点,建议按天聚合后再绘图,既能减轻渲染压力,曲线也更平滑。
分析逻辑的单元测试
血糖分析属于医疗健康相关功能,计算出错可能误导用户,所以单元测试不能省。前面我们把分析逻辑写成了纯函数,现在用JUnit加Mockito就能直接测试,跑在本地JVM上,速度快且不依赖模拟器。
测试要点包括:正常平均值计算、空列表的边界情况、异常值是否被正确过滤、达标率的计算、单位换算是否准确、HbA1c估算结果是否正确。示例代码如下:
public class GlucoseAnalyzerTest {
@Test
public void testAverage() {
List<GlucoseRecord> records = new ArrayList<>();
records.add(new GlucoseRecord(5.0f, 1000L, 0, "mmol/L"));
records.add(new GlucoseRecord(7.0f, 2000L, 2, "mmol/L"));
records.add(new GlucoseRecord(9.0f, 3000L, 2, "mmol/L"));
assertEquals(7.0f, GlucoseAnalyzer.average(records), 0.001f);
}
@Test
public void testAverage_withInvalidData() {
List<GlucoseRecord> records = new ArrayList<>();
records.add(new GlucoseRecord(5.0f, 1000L, 0, "mmol/L"));
records.add(new GlucoseRecord(56.0f, 2000L, 0, "mmol/L")); // 误录入
records.add(new GlucoseRecord(7.0f, 3000L, 0, "mmol/L"));
// 56.0 应被过滤,平均值为 6.0
assertEquals(6.0f, GlucoseAnalyzer.average(records), 0.001f);
}
@Test
public void testEmptyList() {
assertEquals(0f, GlucoseAnalyzer.average(new ArrayList<>()), 0.001f);
}
@Test
public void testUnitConvert() {
GlucoseRecord r = new GlucoseRecord(90f, 1000L, 0, "mg/dL");
// 90 mg/dL 应换算为 5.0 mmol/L
assertEquals(5.0f, r.value, 0.01f);
}
@Test
public void testHbA1c() {
float hba1c = GlucoseAnalyzer.estimateHbA1c(7.0f);
// (7.0 + 4.69) / 1.59 约等于 7.35
assertEquals(7.35f, hba1c, 0.01f);
}
}浮点数断言一定要用带误差范围的assertEquals重载方法,第三个参数传入允许的误差值,比如0.001,否则浮点精度问题会导致测试随机失败。如果分析逻辑里依赖数据库查询,可以用Mockito mock掉Dao层,只验证分析算法本身的正确性。数据库相关的测试则建议放在androidTest目录下,用Room的in-memory数据库跑仪器化测试。
总结
Android血糖分析测试的核心思路可以归纳为三步:数据层统一单位并入库存储,分析层把统计逻辑写成可测试的纯函数,展示层用图表库做可视化,最后用单元测试兜底保证计算正确。其中异常值过滤和单位换算是最容易踩坑的两个点,务必在数据入口处处理好。需要强调的是,App中的血糖分析结果只能作为参考,真正的诊断还是要依赖专业医疗设备,在应用中加上相应的免责声明也是必要的合规措施。
Android血糖分析Glucose测试Android数据可视化修改时间:2026-09-03 13:41:17