实习BLE扫描应用时间戳严重偏移问题求助
BLE扫描应用时间戳年份偏移问题排查与解决
问题背景
实习开发BLE扫描应用,用于收集BLE协议相关数据,扫描结束后生成包含设备地址、RSSI、检测时间戳、位置(适配移动车辆场景)的CSV文件。但多部手机测试均出现时间戳年份严重偏移(如显示2002、2011年),无法定位问题。
扫描结果回调代码
public void onScanResult(int callbackType, ScanResult result) { super.onScanResult(callbackType, result); String deviceAddress = result.getDevice().getAddress(); long timestamp = System.currentTimeMillis(); if (mDeviceTimestamps.containsKey(deviceAddress)) { // 忽略3秒内重复检测的设备 if (timestamp - mDeviceTimestamps.get(deviceAddress) < 3000) { return; } } mDeviceTimestamps.put(deviceAddress, timestamp); Log.d(TAG, "New Device: " + deviceAddress + ", " + result.getTimestampNanos()/1000); mScanResults.add(result); }
CSV生成代码
private void generateCsvFile(List<ScanResult> scanResults) { if (scanResults.isEmpty()) { return; } // 创建CSV目录 File dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); if (!dir.exists()) { if (!dir.mkdirs()) { Log.e(TAG, "generateCsvFile: failed to create directory"); return; } } String filename = "scan_" + new Date().getTime() + ".csv"; File file = new File(dir, filename); try { FileWriter fileWriter = new FileWriter(file, true); CSVWriter csvWriter = new CSVWriter(fileWriter); // 写入表头 String[] headerRow = {"Device Name", "Device Address", "RSSI", "Timestamp", "Latitude", "Longitude"}; csvWriter.writeNext(headerRow); String[] headerRow2 = {"Number of devices"}; csvWriter.writeNext(headerRow2); // 统计唯一设备数 Set<String> uniqueDeviceAddresses = new HashSet<>(); for (ScanResult result : scanResults) { uniqueDeviceAddresses.add(result.getDevice().getAddress()); } String[] numUniqueDevicesRow = {"Number of Unique Devices:", String.valueOf(uniqueDeviceAddresses.size()), "", "", "", ""}; csvWriter.writeNext(numUniqueDevicesRow); // 遍历扫描结果写入CSV for (ScanResult result : scanResults) { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.BLUETOOTH_CONNECT}, REQUEST_BLUETOOTH_CONNECT_PERMISSION); return; } String deviceName = result.getDevice().getName(); if (deviceName == null) { deviceName = "n/a"; } String deviceAddress = result.getDevice().getAddress(); int rssi = result.getRssi(); long timestampMillis = result.getTimestampNanos() / 1000000; // 纳秒转毫秒 String timestamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date(timestampMillis)); // 匹配扫描时间对应的位置 Location loc = null; for (int i = mlocationUpdates.size() - 1; i >= 0; i--) { Location update = mlocationUpdates.get(i); if (update.getTime() <= timestampMillis) { loc = update; break; } } double lat = (loc != null) ? loc.getLatitude() : 0.0; double lon = (loc != null) ? loc.getLongitude() : 0.0; String[] row = {deviceName, deviceAddress, String.valueOf(rssi), timestamp, String.valueOf(lat), String.valueOf(lon)}; csvWriter.writeNext(row); } csvWriter.close(); fileWriter.close(); Toast.makeText(this, "CSV file generated successfully!", Toast.LENGTH_SHORT).show(); } catch (IOException e) { Log.e(TAG, "Error writing CSV file", e); Toast.makeText(this, "Error writing CSV file", Toast.LENGTH_SHORT).show(); e.printStackTrace(); } }
问题根源
CSV生成代码中使用的result.getTimestampNanos(),返回的不是从1970年开始的标准Unix时间戳,而是设备启动以来的纳秒运行时长(类似SystemClock.elapsedRealtimeNanos())。直接将该值转成毫秒后传给Date类,会被解析成1970年加上这个运行时长,若设备运行时间较短(如刚重启),就会出现年份偏移的情况。
而你在onScanResult中已经正确获取了标准Unix时间戳System.currentTimeMillis(),但仅用于去重判断,未将其与ScanResult绑定存储,导致生成CSV时错误使用了非标准时间戳。
修复方案
方案1:存储扫描时的标准时间戳(推荐)
创建自定义数据类,将ScanResult和对应的标准时间戳绑定存储:
- 定义扫描数据类:
public class ScanData { private ScanResult scanResult; private long timestampMillis; public ScanData(ScanResult scanResult, long timestampMillis) { this.scanResult = scanResult; this.timestampMillis = timestampMillis; } // Getter方法 public ScanResult getScanResult() { return scanResult; } public long getTimestampMillis() { return timestampMillis; } }
- 修改扫描回调的存储逻辑:
// 将原List<ScanResult>替换为List<ScanData> private List<ScanData> mScanResults = new ArrayList<>(); public void onScanResult(int callbackType, ScanResult result) { super.onScanResult(callbackType, result); String deviceAddress = result.getDevice().getAddress(); long timestamp = System.currentTimeMillis(); if (mDeviceTimestamps.containsKey(deviceAddress)) { if (timestamp - mDeviceTimestamps.get(deviceAddress) < 3000) { return; } } mDeviceTimestamps.put(deviceAddress, timestamp); Log.d(TAG, "New Device: " + deviceAddress + ", " + timestamp); mScanResults.add(new ScanData(result, timestamp)); // 存储绑定后的扫描数据 }
- 修改CSV生成方法:
private void generateCsvFile(List<ScanData> scanDataList) { if (scanDataList.isEmpty()) { return; } // 目录创建、文件初始化等代码不变 try { // 表头写入、唯一设备数统计等代码不变 // 遍历绑定后的扫描数据 for (ScanData data : scanDataList) { ScanResult result = data.getScanResult(); long timestampMillis = data.getTimestampMillis(); // 使用正确的标准时间戳 // 设备名称、地址、RSSI获取代码不变 String timestamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date(timestampMillis)); // 位置匹配代码不变 String[] row = {deviceName, deviceAddress, String.valueOf(rssi), timestamp, String.valueOf(lat), String.valueOf(lon)}; csvWriter.writeNext(row); } // 资源关闭、提示代码不变 } catch (IOException e) { // 异常处理代码不变 } }
方案2:计算标准时间戳(不推荐,精度易受影响)
若不想修改存储结构,可通过系统启动时间推导标准时间戳:
// 在CSV生成时替换原timestampMillis计算逻辑 long elapsedRealtimeMillis = result.getTimestampNanos() / 1000000; long bootTimeMillis = System.currentTimeMillis() - SystemClock.elapsedRealtime(); long timestampMillis = bootTimeMillis + elapsedRealtimeMillis; String timestamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date(timestampMillis));
此方法依赖系统时间与运行时长的差值,若系统时间在运行中被修改,会导致时间戳不准确,仅作为临时替代方案。
内容的提问来源于stack exchange,提问作者habascus
相关产品推荐
相关产品推荐

