我有一项服务在 Moto 360 的后台持续运行。它以 50Hz 的频率测量加速度计数据。我将样本打包成块(每个块 15,000 个样本 == 5 分钟的数据)并通过 DataApi 将这些块发送到移动设备。一切都很好,但随机我在来自传感器更改侦听器的事件中存在差距。采样间隔可以在 10 小时内出现一次或两次,间隔时间可以是每个间隔 1-10 分钟。
到目前为止做了什么:
- 所有采样处理/打包/记录/发送都在单独的线程上完成,因此不会阻塞传感器侦听器。
- 服务是粘性的,日志被添加到生命周期回调中,所以我可以确定服务在采样间隙时间是活跃的。
- 该服务声明为前台服务以获得更多的 CPU 时间和更高的优先级。
- 我在服务的 onCreate 中获取 PARTIAL_WAKE_LOCK 并在 onDestroy 中释放(我知道可能对电池有影响,但此时,连续数据更重要)。
抽样差距的原因和解决方案是什么?
提前致谢。
我的服务代码:
public class MeasurementService extends Service implements SensorEventListener {
public final static int SENS_ACCELEROMETER = Sensor.TYPE_ACCELEROMETER;
private static int mCounter;
private ArrayList<AccelerometerSampleData> mAccelerometerSensorSamples;
@Inject
SensorManager mSensorManager;
@Inject
DataTransferHolder mDataTransferHolder;
@Inject
EventBus mEventBus;
@Inject
WearSharedPrefsController mSharedPrefsController;
@Inject
WearConfigController mConfigController;
@Inject
MyWearLogger mMyWearLogger;
private Sensor mAccelerometerSensor;
private AccelerometerSampleData mLastEventData;
protected HandlerThread handlerThread;
private PowerManager.WakeLock mWakeLock;
@Override
public void onCreate() {
super.onCreate();
((MyWearApplication)getApplication()).getApplicationComponent().inject(this);
mMyWearLogger.writeToLogFile(DateUtils.getCurrentTimeString() + " MeasurementService: " +
"onCreate");
initSensors();
mEventBus.register(this);
startThread();
acquireWakeLock();
resetPackageValues();
mSharedPrefsController.setMessagePackageIndex(0);
startForeground();
startMeasurement();
mEventBus.postSticky(new MeasurementServiceStatus(true));
}
private void startForeground() {
Notification.Builder builder = new Notification.Builder(this);
builder.setContentTitle("Measurement Service");
builder.setContentText("Collecting sensor data..");
builder.setSmallIcon(R.drawable.ic_play_circle_outline_black_48dp);
startForeground(1, builder.build());
}
private void acquireWakeLock() {
if (mWakeLock == null || !mWakeLock.isHeld()) {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SensorAdapter Lock");
mWakeLock.acquire();
}
}
private void releaseWakeLock() {
if (mWakeLock != null && mWakeLock.isHeld()) {
mWakeLock.release();
mWakeLock = null;
}
}
@SuppressWarnings("unused")
@Subscribe
public void onSamplingRateUpdated(UpdateSamplingRateMessage updateSamplingRateMessage) {
stopMeasurement();
startMeasurement();
}
@SuppressWarnings("unused")
@Subscribe
public void onSamplesPerPackageLimitUpdated(UpdateChunkLimitMessage chunkLimitMessage) {
stopMeasurement();
startMeasurement();
}
private void startThread() {
if (handlerThread == null) {
handlerThread = new HandlerThread(this.getClass().getSimpleName() + "Thread");
}
if (handlerThread.getState() == Thread.State.NEW) {
handlerThread.start();
}
}
private void resetPackageValues() {
mCounter = 0;
mAccelerometerSensorSamples = new ArrayList<>();
}
private void initSensors() {
Timber.d("initiating sensors");
if (mAccelerometerSensor == null) {
mAccelerometerSensor = mSensorManager.getDefaultSensor(SENS_ACCELEROMETER, true);
if (mAccelerometerSensor == null){
mAccelerometerSensor = mSensorManager.getDefaultSensor(SENS_ACCELEROMETER);
}
}
}
private void startMeasurement() {
Timber.d("starting measurement");
if (checkNotNull()) {
Timber.d("sensors are valid, registering listeners");
Handler handler = new Handler(handlerThread.getLooper());
// This buffer is max 300 on Moto 360, so we use 250;
int maxSamplesBuffer = 250 * mConfigController.getSamplingRateMicrosecond();
mSensorManager.registerListener(this,
mAccelerometerSensor,
mConfigController.getSamplingRateMicrosecond(),
maxSamplesBuffer,
handler);
} else {
Timber.w("sensors are null");
}
}
private void stopMeasurement() {
mSensorManager.unregisterListener(this);
}
private boolean checkNotNull() {
Timber.d("checking sensors validity");
return mSensorManager != null
&& mAccelerometerSensor != null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
mMyWearLogger.writeToLogFile(DateUtils.getCurrentTimeString() + " MeasurementService: " +
"onStartCommand");
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
mMyWearLogger.writeToLogFile(DateUtils.getCurrentTimeString() + " MeasurementService: " +
"onDestroy");
stopMeasurement();
releaseWakeLock();
stopThread();
mEventBus.unregister(this);
mEventBus.postSticky(new MeasurementServiceStatus(false));
}
private void stopThread() {
if (handlerThread != null) {
handlerThread.quit();
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onSensorChanged(SensorEvent newEvent) {
AccelerometerSampleData newEventData = new AccelerometerSampleData(
System.currentTimeMillis() + ((newEvent.timestamp - SystemClock.elapsedRealtimeNanos()) / 1000000L),
newEvent.values[0],
newEvent.values[1],
newEvent.values[2]);
if (DefaultConfiguration.LOG_EACH_SAMPLE) {
logData(newEventData, calculateTimeDiff(newEventData));
}
addNewEventToPackage(newEventData);
updateCurrentValues(newEventData);
if (mCounter >= mConfigController.getSamplesPerChunk()) {
float batteryPercentage = getBatteryStatus();
sendPackageToMobileDevice(batteryPercentage);
resetPackageValues();
}
}
private void sendPackageToMobileDevice(float batteryPercentage) {
Timber.i("sending package to processing service");
long messagePackageId = System.currentTimeMillis();
MessagePackage messagePackage = createMessagePackage(mAccelerometerSensorSamples, batteryPercentage);
mMyWearLogger.logChunkToFile(messagePackage);
// Sending package in singleton holder
mDataTransferHolder.getQueueOfMessagePackages().put(messagePackageId, messagePackage);
Intent sendPackageIntent = new Intent(this, DataProcessingService.class);
sendPackageIntent.putExtra(MESSAGE_PACKAGE_ID, messagePackageId);
startService(sendPackageIntent);
}
private MessagePackage createMessagePackage(ArrayList<AccelerometerSampleData> mAccelerometerSensorSamples, float batteryPercentage) {
MessagePackage messagePackage = new MessagePackage();
messagePackage.setAccelerometerSamples(mAccelerometerSensorSamples);
messagePackage.setBatteryPercentage(batteryPercentage);
return messagePackage;
}
private float getBatteryStatus() {
IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = this.registerReceiver(null, ifilter);
float batteryPercentage;
if (batteryStatus != null) {
int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
batteryPercentage = level / (float) scale;
} else {
Timber.w("failed to retrieve battery percentage");
batteryPercentage = -1;
}
return batteryPercentage;
}
private void addNewEventToPackage(AccelerometerSampleData newEventData) {
mAccelerometerSensorSamples.add(newEventData);
}
private void updateCurrentValues(AccelerometerSampleData newEventData) {
mLastEventData = newEventData;
mCounter++;
}
private void logData(AccelerometerSampleData newEventData, long diff) {
// if (diff > MAX_ALLOWED_SAMPLES_DIFF_IN_MILLIS
// || diff < MIN_ALLOWED_SAMPLES_DIFF_IN_MILLIS) {
Timber.d("new accelerometer event, timestamp: %s, time difference: %s milliseconds",
newEventData.getTimestamp(), diff);
// }
}
private long calculateTimeDiff(AccelerometerSampleData newEventData) {
if (mLastEventData == null) {
return 0;
}
return (newEventData.getTimestamp() - mLastEventData.getTimestamp());
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
}