问题
I am trying to write a method that collects accelerometer sensor values over a specific time period and returns the average of the sensor readings for that period.
It should be a synchronous i.e. blocking method that once it is called will block the calling thread for sometime and then will return the sensor average
I did check the below similar questions but does not seem to have a proper working solution for my case:
SensorEventListener in separate thread
Android - how to run your sensor ( service, thread, activity )?
Android sensors and thread
A method for waiting for sensor data
I've also tried to use Executors
similar to this question, but could not get it to work as I want.
Below is my code skeleton, where the method sensorAverage
is a blocking method that will calculate the accelerometer sensor average over a period equals to the timeout
parameter
Average average = new Average(); // Some class to calculate the mean
double sensorAverage(long timeout){
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION);
sensorManager.registerListener(this, sensor,SensorManager.SENSOR_DELAY_NORMAL);
// This does not work
Thread.sleep(timeout);
sensorManager.unregisterListener(this);
return average.value();
}
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_LINEAR_ACCELERATION) {
double x2 = Math.pow(event.values[0], 2);
double y2 = Math.pow(event.values[1], 2);
double z2 = Math.pow(event.values[2], 2);
average.add(Math.sqrt((x2 + y2 + z2)));
}
}
Edit:
I am aware that I need another thread, but the problem that I need to run it for a specific period only and so far I cannot find a proper working solution. Because when I use another thread I get the sensor average always 0
回答1:
I managed to implement a solution which exactly does what I want.
A blocking method that collects sensor values for specific period and returns the statistics of all sensor readings i.e. mean and variance.
It is possible to simply store all the sensor's values and then calculate the mean and variance; however you might run out of memory in case of collecting high frequency sensor over extended period of time.
I found a better solution to calculate the mean and variance for a stream of data in real-time (i.e. without storing the sensor values) using the below RunningStat
class
Example code:
// Calculate statistics of accelerometer values over 300 ms (a blocking method)
RunningStat[] stats = SensorUtils.sensorStats(context,
Sensor.TYPE_ACCELEROMETER, 300)
double xMean = stats[0].mean();
double xVar = stats[0].variance();
Full class code:
public class SensorUtils {
// Collect sensors data for specific period and return statistics of
// sensor values e.g. mean and variance for x, y and z-axis
public static RunningStat[] sensorStats(Context context, int sensorType,
long timeout) throws Exception {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<RunningStat[]> future = executor.submit(new SensorTask(context,
sensorType, timeout));
RunningStat[] stats = future.get();
return stats;
}
private static class SensorTask implements Callable<RunningStat[]> {
private final Context context;
private final long timeout;
private final int sensorType;
// We need a dedicated handler for the onSensorChanged
HandlerThread handler = new HandlerThread("SensorHandlerThread");
public SensorTask(Context context, int sensorType, long timeout) {
this.context = context;
this.timeout = timeout;
this.sensorType = sensorType;
}
@Override
public RunningStat[] call() throws Exception {
final SensorCollector collector = new SensorCollector(context);
handler.start();
Thread sensorThread = new Thread() {
public void run() {
collector.start(sensorType,
new Handler(handler.getLooper()));
};
};
sensorThread.start();
Thread.sleep(timeout);
return collector.finishWithResult();
}
}
private static class SensorCollector implements SensorEventListener {
protected Context context;
protected RunningStat[] runningStat;
protected SensorManager sensorManager;
protected int sensorType;
public SensorCollector(Context context) {
this.context = context;
}
protected void start(int sensorType, Handler handle) {
if (runningStat == null) {
runningStat = new RunningStat[3];
runningStat[0] = new RunningStat(3);
runningStat[1] = new RunningStat(3);
runningStat[2] = new RunningStat(3);
} else {
runningStat[0].clear();
runningStat[1].clear();
runningStat[2].clear();
}
this.sensorType = sensorType;
sensorManager = (SensorManager) context
.getSystemService(Context.SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(sensorType);
sensorManager.registerListener(this, sensor,
SensorManager.SENSOR_DELAY_NORMAL, handle);
}
public RunningStat[] finishWithResult() {
if (sensorManager != null) {
sensorManager.unregisterListener(this);
}
return runningStat;
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == sensorType) {
runningStat[0].push(event.values[0]);
runningStat[1].push(event.values[1]);
runningStat[2].push(event.values[2]);
}
}
}
}
Here is the RunningStat
code, which is a very handy class to calculate the mean and variance for stream of data without storing the data itself (perfect for calculating statistics of high frequency sensors with very small memory footprint)
//See Knuth TAOCP vol 2, 3rd edition, page 232
public class RunningStat {
private int n;
private double oldM, newM, oldS, newS;
private int precision = -1;
// An estimate for the t-value (can be read from the t-distribution table)
private static final double T_THRESHOLD = 1.68;
public RunningStat(int precision) {
this.precision = precision;
}
public RunningStat() {
}
public void clear() {
n = 0;
}
public void push(double x) {
n++;
if (n == 1) {
oldM = newM = x;
oldS = 0.0;
} else {
newM = oldM + (x - oldM) / n;
newS = oldS + (x - oldM) * (x - newM);
// set up for next iteration
oldM = newM;
oldS = newS;
}
}
public int count() {
return n;
}
public double mean() {
double mean = (n > 0) ? newM : 0.0;
if (precision > 0) {
return round(mean, precision);
}
return mean;
}
// The upper bound of the mean confidence interval
public double meanUpper() {
double mean = (n > 0) ? newM : 0.0;
double stdError = stdDeviation() / Math.sqrt(n);
double upperMean = mean + T_THRESHOLD * stdError;
if (precision > 0) {
return round((n > 0) ? upperMean : 0.0, precision);
}
return upperMean;
}
// The lower bound of the mean confidence interval
public double meanLower() {
double mean = (n > 0) ? newM : 0.0;
double stdError = stdDeviation() / Math.sqrt(n);
double lowerMean = mean - T_THRESHOLD * stdError;
if (precision > 0) {
return round((n > 0) ? lowerMean : 0.0, precision);
}
return lowerMean;
}
public double variance() {
if (precision > 0) {
return round(((n > 1) ? newS / (n - 1) : 0.0), precision);
}
return ((n > 1) ? newS / (n - 1) : 0.0);
}
public double stdDeviation() {
if (precision > 0) {
return round(Math.sqrt(variance()), precision);
}
return Math.sqrt(variance());
}
public void setPrecision(int precision) {
this.precision = precision;
}
public static double round(double value, int precision) {
BigDecimal num = new BigDecimal(value);
num = num.round(new MathContext(precision, RoundingMode.HALF_UP));
return num.doubleValue();
}
// A small test case
public static void main(String[] args) {
int n = 100;
RunningStat runningStat = new RunningStat();
double[] data = new double[n];
double sum = 0.0;
for (int i = 0; i < n; i++) {
data[i] = i * i;
sum += data[i];
runningStat.push(data[i]);
System.out.println(runningStat.mean() + " - "
+ runningStat.variance() + " - "
+ runningStat.stdDeviation());
}
double mean = sum / n;
double sum2 = 0.0;
for (int i = 0; i < n; i++) {
sum2 = sum2 + (data[i] - mean) * (data[i] - mean);
}
double variance = sum2 / (n - 1);
System.out.println("\n\n" + mean + " - " + variance + " - "
+ Math.sqrt(variance));
}
}
回答2:
You are essentially asking for a shake detector functionality, you can't block the main thread because you are highly likely to run accross ANR errors
You could try using the java class from Jake Wharton of Action Bar Sherlock fame https://github.com/square/seismic/tree/master/library/src/main/java/com/squareup/seismic
which will do pretty much what you are asking for, you would just need to adapt it slightly to meet your requirements, You could add onStart and onStop listeners and fire them from the start and stop methods and tie them up to your activity
It's not entirely clear what you are wishing to do exactly so it;s difficult to advise further but I am suire that what you want can be achieved without too much effort and achieved asynchronously thereby avoiding ANR's with a bit of thought using the shake detector as a base for what you want to do.
The isShaking method is probably where you might want to start making your amendments by taking a look at the sampleCount and acceleratingCount variables to see how they might help you.
boolean isShaking() {
return newest != null
&& oldest != null
&& newest.timestamp - oldest.timestamp >= MIN_WINDOW_SIZE
&& acceleratingCount >= (sampleCount >> 1) + (sampleCount >> 2);
}
You could adjust thse values to determine how many samples or how long you want to detect movement for.
There is already a sample list that you can use to make your calculations with, just
pass it back to the onStop listener
/** Copies the samples into a list, with the oldest entry at index 0. */
List<Sample> asList() {
List<Sample> list = new ArrayList<Sample>();
Sample s = oldest;
while (s != null) {
list.add(s);
s = s.next;
}
return list;
}
Update in response to comment You could determine if there is no movement simply by using a flag which is set to false in the onStop listener call back and you have complete control over how long "still" and comparing against a time stamp since the last stop to determine if the device has been still enough for long enough to meet your requirements
来源:https://stackoverflow.com/questions/22365905/collecting-android-sensor-for-a-specific-period-and-calculating-the-average