多维度数据采集方案
以下文档经过真机测试得出
文档目的
本文档说明 Android 客户端各项数据是如何获取、系统函数实际返回什么、应用进行了哪些转换、最终形成哪些字段,以及这些数据的权限和精度限制。运动状态不作为已接入功能展开,仅说明方案状态、不可用原因和待定条件。
本文只整理采集端的数据定义和运动方案状态,不展开自动上传、后端存储和网页看板的实现细节。
需要区分四类数据来源:
- 系统状态直接值:例如铃声模式、电池状态。
- 系统原始测量值:例如经纬度、麦克风 PCM 采样。
- 外部服务返回值:例如 Open-Meteo 返回的天气和温度。
- 应用计算结果:例如由 PCM 振幅计算出的 dBFS 和噪声等级。
方案实现状态
这里的“可实现”表示当前项目有明确的系统或外部接口路径,并且可以在目标 Android 客户端中接入;仍然可能受到运行时权限、网络、设备硬件或系统设置影响。
| 功能或方案 |
状态 |
结论 |
| 铃声模式 |
可实现 |
AudioManager.getRingerMode() 可以直接返回系统铃声模式。 |
| 电量与充电 |
可实现 |
ACTION_BATTERY_CHANGED 可以返回电量、充电状态和电源类型。 |
| 定位 |
可实现 |
LocationManager 可以返回经纬度和定位相关信息,但需要用户授权定位。 |
| 天气与温度 |
可实现 |
通过定位结果调用 Open-Meteo;依赖网络和有效经纬度。 |
| 环境噪声 |
可实现 |
AudioRecord 可以读取 PCM 幅度并计算相对 dBFS;需要麦克风权限。 |
| 设备标识和时间 |
可实现 |
ANDROID_ID、设备型号和客户端系统时间可以直接读取。 |
| 调用Google Activity Recognition接口获取运动状态 |
不可实现 |
当前目标环境不能保证 Google Play services,不能作为可交付的通用方案。 |
| 系统传感器 + GPS 规则推断运动状态 |
待定 |
技术上可以运行,但精度取决于自定义规则设定、采样参数和实测校准,尚未达到确定交付标准。 |
Google Activity Recognition:不可实现的原因
原方案通常调用:
ActivityRecognition.getClient(context);
ActivityRecognitionClient.requestActivityUpdates(...);
或者使用 ActivityTransitionClient 监听活动进入和退出。
这些接口由 Google Play services 提供,主要问题是:
- 需要引入
com.google.android.gms:play-services-location 等 Google 依赖。
- 设备必须安装并正常运行 Google Play services;许多没有 GMS 的国内 Android 设备无法使用。
- Android 10 及以上还需要
android.permission.ACTIVITY_RECOGNITION 运行时权限。
- 在 GMS 缺失、版本不兼容或 Google 服务被限制时,接口可能无法初始化或持续返回结果。
- 它识别的是手机活动类型,不保证能区分具体汽车、公交或地铁,也不等于用户实际交通方式。
因此,Google 接口并非“技术上完全不存在”,而是在当前项目需要覆盖的设备环境中不可作为稳定交付方案。当前工程已移除该依赖、权限和接收器。
系统传感器 + GPS:待定
该方案可以调用 Android 原生接口:
SensorManager.registerListener(
listener,
accelerometer,
SensorManager.SENSOR_DELAY_GAME
);
LocationManager.requestLocationUpdates(...);
Location.getSpeed();
但 Android 原生不会直接返回“步行”“跑步”“乘车”这样的高层分类,应用必须自己制定规则,例如:
- 用加速度计计算线性加速度 RMS。
- 用陀螺仪衡量设备旋转幅度。
- 用
Location.getSpeed() 判断持续速度。
- 用采样窗口、阈值、平滑和滞回避免状态抖动。
该方案的精度取决于以下规则和条件:
- 采样时长和采样频率。
- 加速度、陀螺仪和 GPS 的阈值设定。
- GPS 位置的精度、更新时间和是否存在漂移。
- 手机放置姿态、机型传感器质量和厂商系统处理。
- 是否使用多次采样投票、平滑和状态滞回。
- 是否针对不同机型和场景单独校准。
因此当前应把输出理解为“规则推断结果”,而不是系统保证的真实交通状态。该方案只有在完成真实设备和场景测试后,才能从“待定”调整为“可实现”。
实现位置
以下路径相对于 Android 工程根目录:
app/src/main/java/com/example/devicepulse/DeviceReporter.java:铃声、电池、定位、天气和噪声采集编排。
app/src/main/java/com/example/devicepulse/WeatherClient.java:Open-Meteo 请求和地名解析。
app/src/main/java/com/example/devicepulse/NoiseMonitor.java:麦克风 PCM 采样和 dBFS 计算。
app/src/main/AndroidManifest.xml:Android 权限声明。
采集链路
Android 系统 API / 外部天气 API
|
v
DeviceReporter 读取并转换数据
|
v
Snapshot 保存最近一次有效数据
|
v
形成客户端上报字段
功能总览
| 功能 |
主要调用 |
原始数据 |
当前上报数据 |
权限 |
状态 |
| 铃声模式 |
AudioManager.getRingerMode() |
整数模式常量 |
响铃、振动、静音 |
无 |
可实现 |
| 电量与充电 |
ACTION_BATTERY_CHANGED |
电量、状态、电源类型 |
百分比、是否充电、充电来源 |
无 |
可实现 |
| 定位 |
LocationManager |
Location 对象 |
纬度、经度 |
定位权限 |
可实现 |
| 天气与温度 |
Open-Meteo |
当前气温、体感温度、天气代码 |
天气文字、温度、体感温度、地点 |
网络和定位权限 |
可实现 |
| 环境噪声 |
AudioRecord |
16 位 PCM 采样 |
dBFS、安静/一般/嘈杂 |
麦克风权限 |
可实现 |
| 设备标识和时间 |
Settings.Secure、Build、系统时钟 |
标识、厂商、型号和时间 |
device_id、device_name、captured_at |
无 |
可实现 |
1. 铃声模式
1.1 调用的系统函数
AudioManager audio =
(AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
int mode = audio.getRingerMode();
Context.getSystemService(Context.AUDIO_SERVICE) 返回 Android 的 AudioManager 系统服务对象。
AudioManager.getRingerMode() 返回一个 int,表示当前系统铃声模式。
1.2 系统真实返回值
| 数值 |
Android 常量 |
系统含义 |
应用转换值 |
0 |
AudioManager.RINGER_MODE_SILENT |
静音模式 |
静音 |
1 |
AudioManager.RINGER_MODE_VIBRATE |
振动模式 |
振动 |
2 |
AudioManager.RINGER_MODE_NORMAL |
正常响铃模式 |
响铃 |
1.3 状态变化监听
系统铃声模式变化时会发送:
AudioManager.RINGER_MODE_CHANGED_ACTION
应用注册广播接收器,收到广播后重新调用 getRingerMode(),而不是直接依赖广播附带的值。
1.4 能提供和不能提供的数据
可以提供:
不能直接提供:
- 当前铃声音量数值。
- 当前媒体音量或闹钟音量。
- 是否处于勿扰模式。
- 某个具体通知是否会发声。
如需铃声音量,可增加:
audio.getStreamVolume(AudioManager.STREAM_RING);
audio.getStreamMaxVolume(AudioManager.STREAM_RING);
如需勿扰状态,可增加:
NotificationManager.getCurrentInterruptionFilter();
2. 电量与充电状态
2.1 调用的系统函数
Intent intent = context.registerReceiver(
null,
new IntentFilter(Intent.ACTION_BATTERY_CHANGED)
);
Intent.ACTION_BATTERY_CHANGED 是 Android 电池状态粘性广播。
传入 null 接收器不会持续注册监听器,而是立即取得系统保存的最近一份电池状态 Intent。
2.2 读取的原始字段
当前代码从 Intent 中读取:
int level = intent.getIntExtra("level", -1);
int scale = intent.getIntExtra("scale", 100);
int status = intent.getIntExtra("status", -1);
int plugged = intent.getIntExtra("plugged", 0);
| 字段 |
Java 类型 |
真实含义 |
常见示例 |
level |
int |
当前电量刻度 |
76 |
scale |
int |
满电刻度,通常为 100 |
100 |
status |
int |
电池充放电状态常量 |
2 |
plugged |
int |
当前接入电源类型常量 |
2 |
2.3 电池状态原始值
| Android 常量 |
数值 |
含义 |
当前是否视为充电 |
BATTERY_STATUS_UNKNOWN |
1 |
未知 |
否 |
BATTERY_STATUS_CHARGING |
2 |
正在充电 |
是 |
BATTERY_STATUS_DISCHARGING |
3 |
正在放电 |
否 |
BATTERY_STATUS_NOT_CHARGING |
4 |
已接电但当前未充电,或系统报告未充电 |
否 |
BATTERY_STATUS_FULL |
5 |
电量已满 |
是 |
2.4 电源接入类型
| Android 常量 |
常见数值 |
应用显示 |
BATTERY_PLUGGED_AC |
1 |
适配器充电中 |
BATTERY_PLUGGED_USB |
2 |
USB 充电中 |
BATTERY_PLUGGED_WIRELESS |
4 |
无线充电中 |
| 其他接电类型 |
其他 |
充电中 |
| 未在充电 |
- |
未充电 |
2.5 最终提供的数据
{
"battery_level": 76,
"charging": true,
"charge_source": "USB 充电中"
}
| 字段 |
类型 |
含义 |
battery_level |
integer 或 null |
0 到 100 的电量百分比 |
charging |
boolean |
当前实现中表示正在充电或已经充满 |
charge_source |
string 或 null |
USB、适配器、无线或未充电 |
2.6 其他可选电池接口
Android 还提供:
BatteryManager batteryManager =
(BatteryManager) context.getSystemService(Context.BATTERY_SERVICE);
int capacity = batteryManager.getIntProperty(
BatteryManager.BATTERY_PROPERTY_CAPACITY
);
该接口通常直接返回 0 到 100 的百分比,但不能完整替代电池广播,因为充电状态和电源来源仍需要其他字段。
3. 定位数据
3.1 调用的系统服务
LocationManager locationManager =
(LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
当前项目首先尝试读取最近一次位置:
List<String> providers = locationManager.getProviders(true);
Location location = locationManager.getLastKnownLocation(provider);
如果没有可用的最近位置,则请求新位置:
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER,
0L,
0f,
locationListener,
Looper.getMainLooper()
);
同时也会尝试:
LocationManager.GPS_PROVIDER
3.2 定位来源
| Provider |
主要来源 |
特点 |
GPS_PROVIDER |
卫星 |
室外精度通常较高,首次定位可能较慢,耗电较高 |
NETWORK_PROVIDER |
Wi-Fi、基站和系统网络定位 |
返回较快,室内可能可用,但精度通常低于 GPS |
minTime=0 和 minDistance=0 表示应用希望接收所有可用更新,但实际频率仍由系统、硬件和节电策略决定。
3.3 Location 能提供的真实数据
| 系统函数 |
返回类型 |
含义 |
当前是否上报 |
getLatitude() |
double |
纬度,单位为度 |
是 |
getLongitude() |
double |
经度,单位为度 |
是 |
getAccuracy() |
float |
水平精度估计,单位为米 |
否 |
getTime() |
long |
该位置的 UTC 时间戳,毫秒 |
否 |
getElapsedRealtimeNanos() |
long |
从设备启动以来的位置时间 |
否 |
getAltitude() |
double |
海拔,单位为米 |
否 |
getSpeed() |
float |
速度,单位为米/秒 |
否,本功能文档不涉及运动识别 |
getBearing() |
float |
运动方向角,单位为度 |
否 |
getProvider() |
String |
位置提供者名称 |
否 |
3.4 当前应用保存的数据
{
"latitude": 31.187164,
"longitude": 121.605015
}
当前没有保存或上传:
- 定位精度
accuracy。
- 定位数据自身的时间。
- Provider 名称。
- 海拔和方向。
3.5 最近位置选择规则
当前代码遍历所有已启用 Provider 的 getLastKnownLocation(),选择 Location.getTime() 最大的一条,也就是系统报告时间最新的位置。
当前没有限制该位置的最大年龄,因此最新的一条仍有可能是几分钟甚至更久之前的位置。
3.6 新位置等待和失败条件
- 无最近位置时,请求 Network 和 GPS 更新。
- 收到第一条新位置后立即停止本次天气定位监听。
- 最长等待约 12 秒。
- 12 秒内没有位置则返回“定位超时”。
- 系统定位服务关闭时返回“请打开系统定位服务”。
- 权限不可用时返回“定位权限不可用”。
3.7 权限
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
Android 12 及以上用户可能只授予模糊位置,此时应用仍可收到位置,但经纬度精度会降低。
当前项目没有申请后台定位权限,因此定位只在应用前台、用户主动采集天气时使用。
3.8 数据质量限制
getLastKnownLocation() 不保证是刚刚产生的数据。
- 室内、地下、隧道或弱卫星环境可能没有 GPS 定位。
- Network Provider 精度受到 Wi-Fi 和基站环境影响。
- 当前未上报
accuracy,后端无法直接判断位置可信范围。
建议后续增加:
{
"location_accuracy_m": 18.4,
"location_provider": "gps",
"location_captured_at": "2026-08-03T16:45:00+08:00"
}
4. 天气与温度
4.1 数据来源说明
Android 系统本身没有通用的“当前天气”接口。当前项目使用 Android 定位获得经纬度,然后调用 Open-Meteo。
因此:
- 经纬度来自 Android 系统定位。
- 天气和温度来自 Open-Meteo 气象服务。
- 温度不是手机硬件温度传感器的读数。
4.2 Open-Meteo 请求
当前请求格式:
GET https://api.open-meteo.com/v1/forecast
?latitude=31.18716
&longitude=121.60502
¤t=temperature_2m,apparent_temperature,weather_code
&timezone=auto
&forecast_days=1
客户端设置:
connection.setConnectTimeout(10_000);
connection.setReadTimeout(10_000);
connection.setRequestProperty("Accept", "application/json");
connection.setRequestProperty("User-Agent", "DevicePulse/1.0");
Open-Meteo 不需要当前项目配置 API Key。
4.3 外部接口真实返回示例
Open-Meteo 返回内容包含大量字段,当前项目只读取 current 中的三个值:
{
"current": {
"time": "2026-08-03T16:45",
"interval": 900,
"temperature_2m": 34.7,
"apparent_temperature": 37.0,
"weather_code": 3
}
}
| Open-Meteo 字段 |
类型 |
含义 |
当前是否保存 |
temperature_2m |
double |
距地面约 2 米处的气温,单位通常为摄氏度 |
是 |
apparent_temperature |
double |
根据温度、湿度、风等因素计算的体感温度 |
是 |
weather_code |
integer |
WMO 天气代码 |
转换成中文后保存 |
time |
string |
天气数据对应时间 |
否 |
interval |
integer |
当前数据时间区间,单位秒 |
否 |
4.4 天气代码转换
当前项目采用以下映射:
| WMO 代码 |
应用文字 |
0 |
晴朗 |
1、2 |
少云 |
3 |
阴天 |
45、48 |
雾 |
51 到 57 |
毛毛雨 |
61 到 67 |
下雨 |
71 到 77 |
下雪 |
80 到 82 |
阵雨 |
85、86 |
阵雪 |
95 及以上 |
雷雨 |
| 其他未匹配值 |
多变天气 |
4.5 地点名称解析
项目使用 Android Geocoder 将经纬度转换为城市或行政区名称:
Geocoder geocoder = new Geocoder(
context,
Locale.SIMPLIFIED_CHINESE
);
List<Address> addresses = geocoder.getFromLocation(
latitude,
longitude,
1
);
应用按以下优先级选择地点:
Address.getLocality():城市。
Address.getSubAdminArea():次级行政区。
Address.getAdminArea():省、州或一级行政区。
- 均不可用时使用保留两位小数的经纬度文本。
例如:
或回退为:
4.6 最终提供的数据
{
"weather_condition": "阴天",
"temperature_c": 34.7,
"apparent_temperature_c": 37.0,
"weather_place": "上海市",
"latitude": 31.187164,
"longitude": 121.605015
}
4.7 数据含义和限制
temperature_c 是气象模型的 2 米高度气温,不是手机附近空气的直接测量值。
apparent_temperature_c 是模型计算结果,不是人体传感器测量值。
- 天气精度受定位精度和 Open-Meteo 网格分辨率影响。
Geocoder 在部分设备或网络环境中可能不可用,此时只显示坐标。
- 当前未保存 Open-Meteo 返回的天气数据时间,无法直接判断天气数据本身的年龄。
- 无网络、服务超时或非 2xx HTTP 响应时,本次天气采集失败,并保留上一次成功结果。
5. 环境噪声
5.1 调用的系统接口
当前项目使用 Android AudioRecord 读取麦克风原始 PCM 数据。
先查询设备支持的最小缓冲区:
int minimumBuffer = AudioRecord.getMinBufferSize(
44_100,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT
);
然后创建录音对象:
AudioRecord recorder = new AudioRecord(
MediaRecorder.AudioSource.MIC,
44_100,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT,
bufferSize
);
5.2 采样参数
| 参数 |
当前值 |
含义 |
| 音源 |
MediaRecorder.AudioSource.MIC |
设备主麦克风输入 |
| 采样率 |
44,100 Hz |
每秒最多 44,100 个采样点 |
| 声道 |
CHANNEL_IN_MONO |
单声道 |
| 编码 |
ENCODING_PCM_16BIT |
16 位有符号 PCM |
| 最小缓冲区 |
AudioRecord.getMinBufferSize() |
系统报告的最小可用字节数 |
| 实际缓冲区 |
max(minimumBuffer, 4096) |
至少 4,096 字节 |
| 单次采样时长 |
约 2.2 秒 |
按钮采样窗口 |
5.3 系统真实返回值
short[] samples = new short[bufferSize / 2];
int count = recorder.read(samples, 0, samples.length);
AudioRecord.read() 返回本次成功写入数组的采样数量 count。
每个 short 是 16 位 PCM 振幅,理论范围约为:
这些数值不是分贝,也不是声音文件,而是麦克风 ADC 和系统音频链路输出的相对数字振幅。
5.4 dBFS 计算
应用先将每个采样归一化:
normalized = sample / 32768.0
然后计算均方根:
RMS = sqrt(sum(normalized * normalized) / count)
最后计算数字满量程分贝:
当 RMS 为 0 时使用最低值 -90 dBFS,最终结果限制在:
解释:
0 dBFS 表示数字信号达到满量程附近。
- 越接近
-90 dBFS 表示数字振幅越低。
- dBFS 通常为负数。
5.5 平滑处理
为减少单次读数跳动,当前使用指数平滑:
smoothed = previous * 0.72 + current * 0.28
采样线程约每 240 毫秒向上层提供一次平滑结果。按钮采样约 2.2 秒,最终保存最近一次有效平滑值。
5.6 噪声等级
| dBFS 范围 |
应用等级 |
< -60.0 |
安静 |
>= -60.0 且 < -38.0 |
一般 |
>= -38.0 |
嘈杂 |
5.7 最终提供的数据
{
"noise_dbfs": -43.8,
"noise_level": "一般"
}
5.8 dBFS 与真实环境分贝的区别
noise_dbfs 不是专业声级计中的 dB SPL 或 dB(A)。
| 指标 |
含义 |
是否需要校准 |
| dBFS |
数字音频相对于满量程的幅度 |
不需要,但不同设备不可直接比较 |
| dB SPL |
物理声压级 |
需要校准麦克风 |
| dB(A) |
经过 A 计权的人耳感知声级 |
需要校准和频率加权 |
同一个环境在不同手机上可能产生不同 dBFS,原因包括:
- 麦克风灵敏度不同。
- 自动增益控制不同。
- 厂商音频处理不同。
- 手机外壳、麦克风位置和遮挡不同。
- 系统可能进行降噪、限幅或回声处理。
因此当前值适合:
- 判断同一台设备上环境相对变响或变安静。
- 做粗略的安静、一般、嘈杂分级。
不适合:
- 作为法律、职业健康或工程测量依据。
- 直接比较不同型号手机的绝对噪声值。
5.9 权限和隐私
需要运行时权限:
<uses-permission android:name="android.permission.RECORD_AUDIO" />
当前实现:
- 不创建音频文件。
- 不保存 PCM 数组。
- 不向后端上传录音内容。
- 只上传计算后的
noise_dbfs 和 noise_level。
5.10 当前实现注意事项
- 麦克风被其他应用占用时可能初始化失败。
- 设备不支持 44.1 kHz、单声道、16 位 PCM 组合时会返回格式不支持。
- 当前
lastNoise 在新一轮采样开始时没有主动重置;极端情况下,如果本轮没有产生新读数,可能复用上一轮值。更严格的实现应在每次采样开始时重置为 Double.NaN。
6. 设备标识和时间
6.1 设备 ID
String deviceId = Settings.Secure.getString(
context.getContentResolver(),
Settings.Secure.ANDROID_ID
);
ANDROID_ID 返回字符串标识,例如:
它不是硬件序列号。现代 Android 中通常与设备、用户和应用签名范围相关,以下情况可能发生变化:
- 恢复出厂设置。
- 更换 Android 用户或工作资料。
- 某些系统升级或厂商实现差异。
- 应用签名发生变化。
6.2 设备名称
String deviceName = Build.MANUFACTURER + " " + Build.MODEL;
例如:
其中:
Build.MANUFACTURER 是厂商标识。
Build.MODEL 是系统提供的型号字符串。
这两个值可以被厂商定制,不保证是面向消费者的正式产品名称。
6.3 客户端采集时间
new SimpleDateFormat(
"yyyy-MM-dd'T'HH:mm:ssXXX",
Locale.US
).format(new Date());
示例:
2026-08-03T16:46:06+08:00
该时间来自手机系统时钟,并包含时区偏移。
6.4 服务端接收时间
后端收到数据时生成:
2026-08-03T08:46:07+00:00
该值保存为 received_at,使用 UTC。
两个时间的区别:
| 字段 |
产生位置 |
用途 |
captured_at |
Android 客户端 |
表示客户端生成快照的时间 |
received_at |
Python 后端 |
表示后端真正收到数据的时间 |
如果手机系统时间不准确,captured_at 也会不准确;received_at 可以辅助发现客户端时间漂移或网络延迟。
多维度数据采集方案
文档目的
本文档说明 Android 客户端各项数据是如何获取、系统函数实际返回什么、应用进行了哪些转换、最终形成哪些字段,以及这些数据的权限和精度限制。运动状态不作为已接入功能展开,仅说明方案状态、不可用原因和待定条件。
本文只整理采集端的数据定义和运动方案状态,不展开自动上传、后端存储和网页看板的实现细节。
需要区分四类数据来源:
方案实现状态
这里的“可实现”表示当前项目有明确的系统或外部接口路径,并且可以在目标 Android 客户端中接入;仍然可能受到运行时权限、网络、设备硬件或系统设置影响。
AudioManager.getRingerMode()可以直接返回系统铃声模式。ACTION_BATTERY_CHANGED可以返回电量、充电状态和电源类型。LocationManager可以返回经纬度和定位相关信息,但需要用户授权定位。AudioRecord可以读取 PCM 幅度并计算相对 dBFS;需要麦克风权限。ANDROID_ID、设备型号和客户端系统时间可以直接读取。Google Activity Recognition:不可实现的原因
原方案通常调用:
或者使用
ActivityTransitionClient监听活动进入和退出。这些接口由 Google Play services 提供,主要问题是:
com.google.android.gms:play-services-location等 Google 依赖。android.permission.ACTIVITY_RECOGNITION运行时权限。因此,Google 接口并非“技术上完全不存在”,而是在当前项目需要覆盖的设备环境中不可作为稳定交付方案。当前工程已移除该依赖、权限和接收器。
系统传感器 + GPS:待定
该方案可以调用 Android 原生接口:
但 Android 原生不会直接返回“步行”“跑步”“乘车”这样的高层分类,应用必须自己制定规则,例如:
Location.getSpeed()判断持续速度。该方案的精度取决于以下规则和条件:
因此当前应把输出理解为“规则推断结果”,而不是系统保证的真实交通状态。该方案只有在完成真实设备和场景测试后,才能从“待定”调整为“可实现”。
实现位置
以下路径相对于 Android 工程根目录:
app/src/main/java/com/example/devicepulse/DeviceReporter.java:铃声、电池、定位、天气和噪声采集编排。app/src/main/java/com/example/devicepulse/WeatherClient.java:Open-Meteo 请求和地名解析。app/src/main/java/com/example/devicepulse/NoiseMonitor.java:麦克风 PCM 采样和 dBFS 计算。app/src/main/AndroidManifest.xml:Android 权限声明。采集链路
功能总览
AudioManager.getRingerMode()响铃、振动、静音ACTION_BATTERY_CHANGEDLocationManagerLocation对象AudioRecordSettings.Secure、Build、系统时钟device_id、device_name、captured_at1. 铃声模式
1.1 调用的系统函数
Context.getSystemService(Context.AUDIO_SERVICE)返回 Android 的AudioManager系统服务对象。AudioManager.getRingerMode()返回一个int,表示当前系统铃声模式。1.2 系统真实返回值
0AudioManager.RINGER_MODE_SILENT静音1AudioManager.RINGER_MODE_VIBRATE振动2AudioManager.RINGER_MODE_NORMAL响铃1.3 状态变化监听
系统铃声模式变化时会发送:
应用注册广播接收器,收到广播后重新调用
getRingerMode(),而不是直接依赖广播附带的值。1.4 能提供和不能提供的数据
可以提供:
不能直接提供:
如需铃声音量,可增加:
如需勿扰状态,可增加:
2. 电量与充电状态
2.1 调用的系统函数
Intent.ACTION_BATTERY_CHANGED是 Android 电池状态粘性广播。传入
null接收器不会持续注册监听器,而是立即取得系统保存的最近一份电池状态Intent。2.2 读取的原始字段
当前代码从
Intent中读取:levelint76scaleint100statusint2pluggedint22.3 电池状态原始值
BATTERY_STATUS_UNKNOWN1BATTERY_STATUS_CHARGING2BATTERY_STATUS_DISCHARGING3BATTERY_STATUS_NOT_CHARGING4BATTERY_STATUS_FULL52.4 电源接入类型
BATTERY_PLUGGED_AC1适配器充电中BATTERY_PLUGGED_USB2USB 充电中BATTERY_PLUGGED_WIRELESS4无线充电中充电中未充电2.5 最终提供的数据
{ "battery_level": 76, "charging": true, "charge_source": "USB 充电中" }battery_levelinteger或nullchargingbooleancharge_sourcestring或null2.6 其他可选电池接口
Android 还提供:
该接口通常直接返回 0 到 100 的百分比,但不能完整替代电池广播,因为充电状态和电源来源仍需要其他字段。
3. 定位数据
3.1 调用的系统服务
当前项目首先尝试读取最近一次位置:
如果没有可用的最近位置,则请求新位置:
同时也会尝试:
3.2 定位来源
GPS_PROVIDERNETWORK_PROVIDERminTime=0和minDistance=0表示应用希望接收所有可用更新,但实际频率仍由系统、硬件和节电策略决定。3.3
Location能提供的真实数据getLatitude()doublegetLongitude()doublegetAccuracy()floatgetTime()longgetElapsedRealtimeNanos()longgetAltitude()doublegetSpeed()floatgetBearing()floatgetProvider()String3.4 当前应用保存的数据
{ "latitude": 31.187164, "longitude": 121.605015 }当前没有保存或上传:
accuracy。3.5 最近位置选择规则
当前代码遍历所有已启用 Provider 的
getLastKnownLocation(),选择Location.getTime()最大的一条,也就是系统报告时间最新的位置。当前没有限制该位置的最大年龄,因此最新的一条仍有可能是几分钟甚至更久之前的位置。
3.6 新位置等待和失败条件
3.7 权限
Android 12 及以上用户可能只授予模糊位置,此时应用仍可收到位置,但经纬度精度会降低。
当前项目没有申请后台定位权限,因此定位只在应用前台、用户主动采集天气时使用。
3.8 数据质量限制
getLastKnownLocation()不保证是刚刚产生的数据。accuracy,后端无法直接判断位置可信范围。建议后续增加:
{ "location_accuracy_m": 18.4, "location_provider": "gps", "location_captured_at": "2026-08-03T16:45:00+08:00" }4. 天气与温度
4.1 数据来源说明
Android 系统本身没有通用的“当前天气”接口。当前项目使用 Android 定位获得经纬度,然后调用 Open-Meteo。
因此:
4.2 Open-Meteo 请求
当前请求格式:
客户端设置:
Open-Meteo 不需要当前项目配置 API Key。
4.3 外部接口真实返回示例
Open-Meteo 返回内容包含大量字段,当前项目只读取
current中的三个值:{ "current": { "time": "2026-08-03T16:45", "interval": 900, "temperature_2m": 34.7, "apparent_temperature": 37.0, "weather_code": 3 } }temperature_2mdoubleapparent_temperaturedoubleweather_codeintegertimestringintervalinteger4.4 天气代码转换
当前项目采用以下映射:
0晴朗1、2少云3阴天45、48雾51到57毛毛雨61到67下雨71到77下雪80到82阵雨85、86阵雪95及以上雷雨多变天气4.5 地点名称解析
项目使用 Android
Geocoder将经纬度转换为城市或行政区名称:应用按以下优先级选择地点:
Address.getLocality():城市。Address.getSubAdminArea():次级行政区。Address.getAdminArea():省、州或一级行政区。例如:
或回退为:
4.6 最终提供的数据
{ "weather_condition": "阴天", "temperature_c": 34.7, "apparent_temperature_c": 37.0, "weather_place": "上海市", "latitude": 31.187164, "longitude": 121.605015 }4.7 数据含义和限制
temperature_c是气象模型的 2 米高度气温,不是手机附近空气的直接测量值。apparent_temperature_c是模型计算结果,不是人体传感器测量值。Geocoder在部分设备或网络环境中可能不可用,此时只显示坐标。5. 环境噪声
5.1 调用的系统接口
当前项目使用 Android
AudioRecord读取麦克风原始 PCM 数据。先查询设备支持的最小缓冲区:
然后创建录音对象:
5.2 采样参数
MediaRecorder.AudioSource.MIC44,100 HzCHANNEL_IN_MONOENCODING_PCM_16BITAudioRecord.getMinBufferSize()max(minimumBuffer, 4096)2.2 秒5.3 系统真实返回值
AudioRecord.read()返回本次成功写入数组的采样数量count。每个
short是 16 位 PCM 振幅,理论范围约为:这些数值不是分贝,也不是声音文件,而是麦克风 ADC 和系统音频链路输出的相对数字振幅。
5.4 dBFS 计算
应用先将每个采样归一化:
然后计算均方根:
最后计算数字满量程分贝:
当 RMS 为 0 时使用最低值
-90 dBFS,最终结果限制在:解释:
0 dBFS表示数字信号达到满量程附近。-90 dBFS表示数字振幅越低。5.5 平滑处理
为减少单次读数跳动,当前使用指数平滑:
采样线程约每 240 毫秒向上层提供一次平滑结果。按钮采样约 2.2 秒,最终保存最近一次有效平滑值。
5.6 噪声等级
< -60.0安静>= -60.0且< -38.0一般>= -38.0嘈杂5.7 最终提供的数据
{ "noise_dbfs": -43.8, "noise_level": "一般" }5.8 dBFS 与真实环境分贝的区别
noise_dbfs不是专业声级计中的dB SPL或dB(A)。同一个环境在不同手机上可能产生不同 dBFS,原因包括:
因此当前值适合:
不适合:
5.9 权限和隐私
需要运行时权限:
当前实现:
noise_dbfs和noise_level。5.10 当前实现注意事项
lastNoise在新一轮采样开始时没有主动重置;极端情况下,如果本轮没有产生新读数,可能复用上一轮值。更严格的实现应在每次采样开始时重置为Double.NaN。6. 设备标识和时间
6.1 设备 ID
ANDROID_ID返回字符串标识,例如:它不是硬件序列号。现代 Android 中通常与设备、用户和应用签名范围相关,以下情况可能发生变化:
6.2 设备名称
例如:
其中:
Build.MANUFACTURER是厂商标识。Build.MODEL是系统提供的型号字符串。这两个值可以被厂商定制,不保证是面向消费者的正式产品名称。
6.3 客户端采集时间
示例:
该时间来自手机系统时钟,并包含时区偏移。
6.4 服务端接收时间
后端收到数据时生成:
该值保存为
received_at,使用 UTC。两个时间的区别:
captured_atreceived_at如果手机系统时间不准确,
captured_at也会不准确;received_at可以辅助发现客户端时间漂移或网络延迟。