For a few days now im struggling with the following: I want to count movement patterns using the gravity sensor on an Android device while the screen is off. I am using a bound service that I start in the foreground (with a notification for Android 8 to keep it running) and everything works fine when the screen is on. Even if the App is not running in the foreground everything works fine.
However, as soon as the display is turned off, very strange things start to happen: The sensor data is still processed and movement counted to some extend but the results are very bad and inaccurate. Also, if the screen is turned on again, the app behaves very strange. Sometimes the app works as expected but sometimes it seems that old sensor events are processed delayed and counted later. The whole algorithm is not running smoothly from this time on. What is also interesting: If the device is plugged in and I observe everything with the console in Android Studio everything works just perfect, even if the screen is off. However, if the device is unplugged, the results become wrong again.
I tried a lot of things: Running everything on the main thread, on another thread, using an IntentService, setting the App on the whitelist for Doze, not sending data to the MainActivity while the screen is turned off, and followed this guide (I am developing on an Galaxy J5) - but nothing worked. It seems that either the SensorFusion algorithms of the Android system shut down in Standby even if there is a registered listener or that Samsung has some battery optimization running in the background and limits CPU operations. Is there something else that could cause this behavior? So running fine if the app is active, if it is in the background, but not when the device is in sleep?
Also maybe interesting to mention: I am developing a plugin for Cordova.
This is my code
public class SensorPlugin extends CordovaPlugin implements ServiceClass.Delegate {
public void initialize() {
...
Intent serviceIntent = new Intent(applicationContext, ServiceClass.class);
applicationContext.bindService(serviceIntent, serviceConnection,
Context.BIND_AUTO_CREATE);
}
public void start() {
serviceClass.startMeasuring();
}
@Override
public void updateMovementCount(int count) {
...
};
}
public class ServiceClass extends Service implements OtherClass.Delegate {
private volatile boolean isMeasuring;
private volatile double gravityX;
private volatile double gravityY;
private volatile double gravityZ;
public IBinder onBind(Intent intent) {
sensorManager = (SensorManager) getApplicationContext().
getSystemService(Context.SENSOR_SERVICE);
startForeground(1, buildNotification());
return mBinder;
}
public void startMeasuring() {
assert sensorManager != null;
Sensor gravity = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY);
sensorManager.registerListener(this, gravity,
SensorManager.SENSOR_DELAY_GAME);
isTracking = true;
SensorThread sensorThread = new SensorThread();
sensorThread();
}
@Override
public void onSensorChanged(SensorEvent event) {
gravityX = event.values[0];
gravityY = event.values[1];
gravityZ = event.values[2];
}
// This is an interface method from another class that I wrote
// (see below). For which I set this class as delegate and as soon
// as the other class finds a pattern in the data it calls this method
@Override
public void movementPatternDidChange(int count) {
// Usually I send this count to the main class with
// the same delegation pattern
delegate.updateMovementCount(count);
}
class SensorProcessingThread extends Thread {
Handler handler = new Handler();
// I use another runnable here, as I only want to process 10
// sensor events per second. The sensor manager usually returns
// way more which I don't need.
private Runnable sensorProcessingRunnable = new Runnable() {
public void run() {
otherClass.processMotionData(gravityX, gravityY, gravityZ);
if (isMeasuring) {
handler.postDelayed(this, 100);
}
}
};
@Override
public void run() {
if (!isMeasuring) {
return;
}
handler.postDelayed(sensorProcessingRunnable, 100);
}
}
}
I tested a lot in the meantime. I am now using the Apache Commons Primitives Collections for a better performance (instead of the large FastUtil which also caused this error in the past).
I also tested the app on two devices, a rather old LG G2 and the Galaxy J5. The problem is the same on both. So probably not manufacturer specific. The Android Studio Profiler reports a CPU usage of average 1-3% on both devices, so I assume an overload is likely not the cause. I also tested a TimerTask and Timer instead of a Runnable and Handler, which did not work as well.
What is also very interesting: I tried to debug the App over Wifi as explained here, and the App works absolutely fine, even if the device sleeps and the screen is turned off. It is so difficult to debug this issue, because the App behaves fine as soon as I am debugging it even without a cable attached. I have no idea what I could do else.
For everyone with a similar problem: I finally found the solution.
I think it is not well explained in the Android documentation, and not very intuitive either, but it is still necessary to set a partial wake lock to prevent the CPU from sleeping. This explains how to set a wake lock. A foreground service alone is not enough to keep a time-critical process running.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With