How can I ensure compatibility between Android API levels for RotationVector?

末鹿安然 提交于 2019-11-29 15:51:57
Sean Barbeau

The post that you reference actually refers to a bug in a few Samsung devices (Galaxy S4, Galaxy Note 3) - see this Android Developer list post. You actually shouldn't have to do any special handling between SDK levels for this code to work on normal devices. But, alas, fragmentation...

Chromium handles this issue by truncating the array if the size is greater than 4:

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);
}

However, I found in my app GPSTest that this solution didn't seem to work on the Galaxy S3 (see Github issue here).

So, I ended up only truncating the array on devices that throw the IllegalArgumentException. This also avoids the extra System.arraycopy() unless its absolutely necessary.

Here's the code snippet (that also supports orientation sensors on devices with API levels less than Gingerbread (i.e., prior to when the ROTATION_VECTOR sensor was introduced), and handles remapping the coordinate system for orientation changes), which uses a class member mTruncateVector that is initialized to 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);
}

and to register the sensors in 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);
        }
    }

Full implementation is here on Github.

Short summary: Presuming you don't want to make use of values[3] and values[4], your code is fine as is.

From the docs:

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.

If I read that right, event.values.length will only be greater 3 if you compiled with SDK 18 or older.

SensorManager.getRotationMatrixFromVector seems to assume a rotation vector of length==3. I'm not certain what that function does if the rotation vector passed in is larger than 3 elements anyway.

If you ever needed to make use of the event.values[3] and event.values[4], you could detect if the device supports these extended values simply by checking the event.values.length. You could also check to see if Build.VERSION.SDK_INT is >= 18 at runtime as well. But if you don't need it, stick with your hardcoded assumption of 3.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!