如何确保RotationVector的Android API级别之间的兼容性?

时间:2014-02-26 00:59:00

标签: android android-sensors

我有一个使用旋转矢量传感器的Android应用。阅读评论here后,我发现使用较新的API级别,SensorEvent包含4-5个值,而不是3.我的代码使用arraycopy来获取数据。

lastRotVal是一个类成员,并初始化为size [3]的数组。以下是响应传感器事件的代码的相关部分。

public void onSensorChanged(SensorEvent event) {
   System.arraycopy(event.values, 0, lastRotVal, 0, 3); //Hardcoded size
   lastRotValSet = true;
   updateDisplay(event);
}

private void updateDisplay(SensorEvent event){

   if (lastRotValSet){  
       float[] rotation = new float[9];
       float[] orientation = new float[3];

       SensorManager.getRotationMatrixFromVector(rotation, lastRotVal);
       SensorManager.getOrientation(rotation, orientation);
       double pitch = orientation[1];
       double roll = orientation[2];

       //Do stuff with roll and pitch
   }
}

我在arraycopy中只使用了3个值进行了硬编码。它似乎适用于较旧的和新的API级别。这是保持版本之间兼容性的最佳方式,还是我可以做得更好?

编辑:如接受的答案所述,提示此问题的IllegalArgumentException显然是由于三星设备上的API中的错误而导致的,而不是一般的API版本。所以我要补充一个事实,即在三星Galaxy SIII上遇到了我的初始错误。

2 个答案:

答案 0 :(得分:3)

您引用的

The post实际上是指少数三星设备中的错误(Galaxy S4,Galaxy Note 3) - 请参阅this Android Developer list post。实际上,您不必在SDK级别之间进行任何特殊处理,以使此代码可以在普通设备上运行。但是,唉,碎片......

如果大小大于4,则截断数组

Chromium handles this issue

if (values.length > 4) {
    // On some Samsung devices SensorManager.getRotationMatrixFromVector
    // appears to throw an exception if rotation vector has length > 4.
    // For the purposes of this class the first 4 values of the
    // rotation vector are sufficient (see crbug.com/335298 for details).
    if (mTruncatedRotationVector == null) {
        mTruncatedRotationVector = new float[4];
    }
    System.arraycopy(values, 0, mTruncatedRotationVector, 0, 4);
        getOrientationFromRotationVector(mTruncatedRotationVector);
} else {
    getOrientationFromRotationVector(values);
}

但是,我在我的应用GPSTest中发现此解决方案似乎不适用于Galaxy S3(请参阅Github问题here)。

所以,我最终只是在抛出IllegalArgumentException的设备上截断了数组。除非绝对必要,否则这也避免了额外的System.arraycopy()。

这是代码片段(在API级别低于Gingerbread的设备上也支持方向传感器(即,在引入ROTATION_VECTOR传感器之前),并处理重新映射坐标系以进行方向更改),它使用类成员mTruncateVector已初始化为false

@TargetApi(Build.VERSION_CODES.GINGERBREAD)
@Override
public void onSensorChanged(SensorEvent event) {

    double orientation = Double.NaN;
    double tilt = Double.NaN;

    switch (event.sensor.getType()) {
        case Sensor.TYPE_ROTATION_VECTOR:
            // Modern rotation vector sensors
            if (!mTruncateVector) {
                try {
                    SensorManager.getRotationMatrixFromVector(mRotationMatrix, event.values);
                } catch (IllegalArgumentException e) {
                    // On some Samsung devices, an exception is thrown if this vector > 4 (see #39)
                    // Truncate the array, since we can deal with only the first four values
                    Log.e(TAG, "Samsung device error? Will truncate vectors - " + e);
                    mTruncateVector = true;
                    // Do the truncation here the first time the exception occurs
                    getRotationMatrixFromTruncatedVector(event.values);
                }
            } else {
                // Truncate the array to avoid the exception on some devices (see #39)
                getRotationMatrixFromTruncatedVector(event.values);
            }

            int rot = getWindowManager().getDefaultDisplay().getRotation();
            switch (rot) {
                case Surface.ROTATION_0:
                    // No orientation change, use default coordinate system
                    SensorManager.getOrientation(mRotationMatrix, mValues);
                    // Log.d(TAG, "Rotation-0");
                    break;
                case Surface.ROTATION_90:
                    // Log.d(TAG, "Rotation-90");
                    SensorManager.remapCoordinateSystem(mRotationMatrix, SensorManager.AXIS_Y,
                            SensorManager.AXIS_MINUS_X, mRemappedMatrix);
                    SensorManager.getOrientation(mRemappedMatrix, mValues);
                    break;
                case Surface.ROTATION_180:
                    // Log.d(TAG, "Rotation-180");
                    SensorManager
                            .remapCoordinateSystem(mRotationMatrix, SensorManager.AXIS_MINUS_X,
                                    SensorManager.AXIS_MINUS_Y, mRemappedMatrix);
                    SensorManager.getOrientation(mRemappedMatrix, mValues);
                    break;
                case Surface.ROTATION_270:
                    // Log.d(TAG, "Rotation-270");
                    SensorManager
                            .remapCoordinateSystem(mRotationMatrix, SensorManager.AXIS_MINUS_Y,
                                    SensorManager.AXIS_X, mRemappedMatrix);
                    SensorManager.getOrientation(mRemappedMatrix, mValues);
                    break;
                default:
                    // This shouldn't happen - assume default orientation
                    SensorManager.getOrientation(mRotationMatrix, mValues);
                    // Log.d(TAG, "Rotation-Unknown");
                    break;
            }
            orientation = Math.toDegrees(mValues[0]);  // azimuth
            tilt = Math.toDegrees(mValues[1]);
            break;
        case Sensor.TYPE_ORIENTATION:
            // Legacy orientation sensors
            orientation = event.values[0];
            break;
        default:
            // A sensor we're not using, so return
            return;
    }
}

@TargetApi(Build.VERSION_CODES.GINGERBREAD)
private void getRotationMatrixFromTruncatedVector(float[] vector) {
    System.arraycopy(vector, 0, mTruncatedRotationVector, 0, 4);
    SensorManager.getRotationMatrixFromVector(mRotationMatrix, mTruncatedRotationVector);
}

并在onResume()注册传感器:

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
        // Use the modern rotation vector sensors
        Sensor vectorSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
        mSensorManager.registerListener(this, vectorSensor, 16000); // ~60hz
    } else {
        // Use the legacy orientation sensors
        Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
        if (sensor != null) {
            mSensorManager.registerListener(this, sensor,
                    SensorManager.SENSOR_DELAY_GAME);
        }
    }

完整实施是here on Github

答案 1 :(得分:1)

简短摘要:假设您不想使用值[3]和值[4],您的代码就可以了。

来自文档:

values[0]: x*sin(θ/2)
values[1]: y*sin(θ/2)
values[2]: z*sin(θ/2)
values[3]: cos(θ/2)
values[4]: estimated heading Accuracy (in radians) (-1 if unavailable)
values[3], originally optional, will always be present from SDK Level 18 onwards. values[4] is a new value that has been added in SDK Level 18.

如果我读得正确,如果使用SDK 18或更早版本编译,则event.values.length只会大于3。

SensorManager.getRotationMatrixFromVector似乎假设一个长度为== 3的旋转矢量。如果传入的旋转矢量大于3个元素,我不确定该函数会起什么作用。

如果你需要使用event.values [3]和event.values [4],你可以通过检查event.values.length来检测设备是否支持这些扩展值。您还可以在运行时检查Build.VERSION.SDK_INT是否为> = 18。但如果你不需要它,坚持你的硬编码假设3。