传感器事件onChanged多线程?

3
我正在检测设备的方向,以确定是否将其放置在倒置的表面上,然后调用该方法。我遇到的问题是用户可能会无意中将设备的方向短暂地转到那个位置,然后返回正面位置以纠正错误,但是该方法仍然会在第一次这样做时被调用。
我尝试在800毫秒的延迟后添加“双重检查”来检查方向,以便用户有时间在触发该方法之前纠正方向。不幸的是,我无法控制onSensorChanged被调用的频率,以便添加一个带有800毫秒延迟的线程,该线程将双重检查方向以防止无意中更改方向。
我的代码如下:
public SensorEventListener accelerometerListener = new SensorEventListener(){

    @Override
    public void onSensorChanged(SensorEvent event) {

            float z_value = event.values[2];

                if (z_value < 0) {
                    //the device is now upside-down

                       try {

                           Thread.sleep(800);

                           //this if statement is never called correctly because
                           // onSensorChanged is not called again.
                                if (z_value < 0) {
                                    toggleMethod();
                                }

                        } catch (InterruptedException e) {
                               e.printStackTrace();
                        }
                 }
          }

我的问题:onSensorChanged方法内部是否有一种方法可以延迟调用onSensorChanged,以进行双重检查?

3个回答

2
一个非常简单的方法是在接下来的n毫秒内(就像要求的那样),给传感器一个机会提供新值,具体做法如下:
1- 决定您要给用户多少毫秒来撤消他的操作(您使用了800):
private static final long TIME_MARGIN_FOR_USER_TO_UNDO = 800;

2- 创建一个处理程序和一个可运行对象来执行您的操作(让它们处于比onSensorChanged更大的范围内,例如您的活动):

Handler sensorHandler = new Handler();
Runnable toggleRunnable = new Runnable() {
   public void run() {
      toggleMethod();
   }
}

3- 当您的if语句评估为true时,请在n毫秒后发布此可运行项。

@Override
public void onSensorChanged(SensorEvent event) {
   float z_value = event.values[2];
   if (z_value < 0) {
      sensorHandler.postDelayed(toggleRunnable, TIME_MARGIN_FOR_USER_TO_UNDO);
   }
}

4- 由于onSensorChanged将在传感器值发生变化时被调用,因此如果用户修复了它,您可以停止可运行线程的运行。因此,您需要一个 else 语句来删除可运行线程。

@Override
public void onSensorChanged(SensorEvent event) {
   float z_value = event.values[2];
   if (z_value < 0) {
      sensorHandler.postDelayed(toggleRunnable, TIME_MARGIN_FOR_USER_TO_UNDO);
   }
   else {
      sensorHandler.removeCallbacks(toggleRunnable);
   }
}

2

在使用onSensorCheck时,最好的做法是使用滤波器:始终基于传感器提供的n个最近值的平均值作出决策,这样您的应用程序将运行流畅,突然的传感器变化也不会影响它。
以下是如何实现:

z_value = 0.2*event.values[2]+0.8*z_value; // 0.2+0.8 = 1 

这样做只取新值的20%,其余是最近四个值的平均值。

尝试调整系数,直到您对结果满意。

对我而言,这种方法效果不错:

 z_value = 0.02*event.values[2]+0.98*z_value;

---------------------------------------------------------------

编辑

以下是初始化z_value的方法: z_value应该是一个字段,你需要添加另一个布尔字段仅用于初始化,即用于第一次onSensorChanged调用。因此,您将具有两个字段:

double z_value =0;
boolean firstTime = true;

以下是onSensorChanged方法的内容:

@Override
public void onSensorChanged(SensorEvent event) {
        if(firstTime){
            z_value = event.values[2]; // you will enter here only the first time
            firstTime = false
        }
        else{
            z_value = 0.02*event.values[2] + 0.98*z_value;
        }

        if (z_value < 0) {
                //the device is now upside-down

               // do what you gotta do
        }
 }

可能会出现精度损失。发现:Double,需要:Float。 - user1889890
"变量 z_value 可能未被初始化" 我不知道该怎么做,但我真的想尝试你的解决方案。 - user1889890
我尝试了一下,但是我没有看到任何区别。问题仍然存在。不过,这可能会对我正在进行的另一个项目有所帮助。 - user1889890
我已经尝试了0.001、0.999以及它们之间的所有值,但是我想要的结果没有任何差别。还有其他什么我可以尝试的吗? - user1889890
这意味着用户无意中进行的移动不是短暂的,它持续足够长的时间以在结束之前调用onSensorChanged 1000次。你在问题中尝试的是对z_value进行双重检查,使用过滤器检查了1000次,但这还不够,显然你的设备传感器非常快。 - Tourki
显示剩余4条评论

0

另一种方法是随时间平滑输入,因此在800毫秒的输入中取平均读数。

(YAT的回应有正确的想法,但实际执行并没有考虑到从一个手机到另一个手机传递的样本数量将是未知的。它也永远不会忘记任何测量,10秒后的行为可能在1分钟后显着不同。)

//the idea here is to store all the inputs over the last 800ms and average them
LinkedList<Entry<long, double>> inputs = new LinkedList<Entry<long,double>>
double totalZ = 0.0f;   //keep a running total so we don't have to iterate through the list to calculate the average

@Override
public void onSensorChanged(SensorEvent event) {
    //clear out values older than 800ms
    while(inputs.size() > 0 && inputs.getLast().getKey() < System.currentTimeInMillis() - 800){
        Entry<long, double> deletedEntry = inputs.removeLast();
        totalZ -= deletedEntry.getValue(); //update the running total
    }

    //add the new measurement to the head
    inputs.addFirst(new Entry<long, double>(System.currentTimeInMillis(),event.values[2]));
    totalZ += event.values[2]; //update the running total with the new measurement

    //this is the Z averaged over 800ms
    float averagedZ = totalZ / inputs.size();
}

或者更简单的方法,更符合所提出的问题,在检测到初始变化时。

节省时间,保存新的潜在状态

然后当你再次得到onSensorChanged()

那个状态是否与潜在状态相同?如果不是,则清除时间和潜在状态

如果已经过去了800毫秒,而潜在状态仍然是当前状态,则启动该变化。

在大多数精度模式下,onSensorChanged每秒调用多次,因此您应该接近于等待正常的onSensorChanged调用达到800ms。

以下是粗略的伪代码

long lastChange = 0;
int potentialState = 0;

@Override
public void onSensorChanged(SensorEvent event) {
    int currentState = determineCurrentState(event); //whatever your logic is and states

    if(potentialState != currentState){
         lastChange = System.currentTimeInMillis();
         potentialState = currentState;
    }
    else if(lastChange + 800 < System.currentTimeInMillis()){
         //execute state change, 800ms have elapses with no further change in state
         stateChange(currentState);
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接