本文将带大家去解读下安卓官方关于方向传感器数据,提供的新方法。熟悉手机传感器开发的朋友对这段代码一定不会陌生吧。

sm.registerListener(this,

sm.getDefaultSensor(Sensor.TYPE_ORIENTATION),

SensorManager.SENSOR_DELAY_NORMAL);

sm.registerListener(this,

sm.getDefaultSensor(Sensor.TYPE_GRAVITY),

在高版本的安卓中,Sensor.TYPE_ORIENTATION被画上了横线,那么我们鼠标放上去看下IDE提示是这样写的:The fieldSensor.TYPE_ORIENTATION is deprecated,意思是说这个方法已经被弃用了。当然随着版本的更新,老方法被弃用,自然会有更精确,更高效的方法来代替。那么新方法就是本文要重要介绍的。为了更好解释,首先从传感器基础开始讲起。

一、传感器基础知识

官方文档说的很清楚,Android平台支持三大类的传感器,它们分别是:

a.Motion sensors

b.Environmental sensors

c.Position sensors

从另一个角度划分,安卓的传感器又可以分为基于硬件的和基于软件的。基于硬件的传感器往往是通过物理组件去实现的,他们通常是通过去测量特殊环境的属性获取数据,比如:重力加速度、地磁场强度或方位角度的变化。而基于软件的传感器并不依赖物理设备,尽管它们是模仿基于硬件的传感器的。基于软件的传感器通常是通过一个或更多的硬件传感器获取数据,并且有时会调用虚拟传感器或人工传感器等等,线性加速度传感器和重力传感器就是基于软件传感器的例子。下面通过官方的一张图看看安卓平台支持的所有传感器类型:

使用传感器的话那么首先需要了解的必然是传感器API了,在Android中传感器类是通过Sensor类来表示的,它属于android.hardware包下的类,顾名思义,和硬件相关的类。传感器的API不复杂,包含3个类和一个接口,分别是:

SensorManager

Sensor

SensorEvent

SensorEventListener

根据官方文档的概述分别简单解释一下这4个API的用处:

SensorManager:可以通过这个类去创建一个传感器服务的实例,这个类提供的各种方法可以访问传感器列表、注册或解除注册传感器事件监听、获取方位信息等。

Sensor:用于创建一个特定的传感器实例,这个类提供的方法可以让你决定一个传感器的功能。

SensorEvent:系统会通过这个类创建一个传感器事件对象,提供了一个传感器的事件信息,包含一下内容,原生的传感器数据、触发传感器的事件类型、精确的数据以及事件发生的时间。

SensorEventListener:可以通过这个接口创建两个回调用法来接收传感器的事件通知,比如当传感器的值发生变化时。

介绍了基础的分类之后,我们再看看传感器的可用性表格,不同的传感器在不同的Android版本之间是有差异的,比如有的在低版本可以用,但在高版本就被弃用,详细的数据依然看看官方的这张表格:

右上角标有1的是在Android1.5版本添加的,并且在Android2.3之后就无法使用。

右上角标有2的是已经过时的。

很明显,我们需要用到的方向传感器TYPE_ORIENTATION就已经过时了,后面再说用什么来替代它。

接着讲一下如何使用常用的传感器:

1.   实例化SensorManager

SensorManager mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);

2.   获取设备支持的全部Sensor的List

List<Sensor> deviceSensors = mSensorManager.getSensorList(Sensor.TYPE_ALL);

3.   下面就通过这两个方法看一下手机支持哪些传感器,并以列表数据展示出来,代码很简单:

package com.example.sensordemo;import java.util.ArrayList;
import java.util.List;import android.app.Activity;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;public class MainActivity extends Activity {private SensorManager mSensorManager;private ListView sensorListView;private List<Sensor> sensorList;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);sensorListView = (ListView) findViewById(R.id.lv_all_sensors);// 实例化传感器管理者mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);// 得到设置支持的所有传感器的ListsensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL);List<String> sensorNameList = new ArrayList<String>();for (Sensor sensor : sensorList) {sensorNameList.add(sensor.getName());}ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1, sensorNameList);sensorListView.setAdapter(adapter);}}

最后看一下真机的效果图:

了解了传感器的基础知识,下面就具体到我们要重点讲解的Orientation Sensor传感器。

三、OrientationSensor官方给的替代方法

安卓平台提供了2个传感器用于让我们判断设备的位置,分别是地磁场传感器(thegeomagnetic field sensor)和方向传感器(theorientation sensor)。关于OrientationSensor在官方文档中的概述里有这样一句话:

Theorientation sensor is software-based and derives its data from theaccelerometer and the geomagnetic field sensor.(方向传感器是基于软件的,并且它的数据是通过加速度传感器和磁场传感器共同获得的)

至于具体算法Android平台已经封装好了我们不必去关心实现,下面在了解方向传感器之前我们还需要了解一个重要的概念:传感器坐标系统(Sensor Coordinate System)。

在Android平台中,传感器框架通常是使用一个标准的三维坐标系去表示一个值的。以方向传感器为例,确定一个方向当然也需要一个三维坐标,毕竟我们的设备不可能永远水平端着吧,准确的说android给我们返回的方向值就是一个长度为3的float数组,包含三个方向的值。下面看一下官方提供的传感器API使用的坐标系统示意图:

仔细看一下这张图,不难发现,z是指向地心的方位角,x轴是仰俯角(由静止状态开始前后反转),y轴是翻转角(由静止状态开始左右反转)。下面切入正题,看看如何通过方向传感器API去获取方向。结合上面的图再看看官方提供的方向传感器事件的返回值:

这样就和上面提到的相吻合了,确实是通过一个长度为3的float数组去表示一个位置信息的,并且数组的第一个元素表示方位角(z轴),数组的第二个元素表示仰俯角(x轴),而数组的第三个元素表示翻转角(y轴),最后看看代码怎么写。

依旧参考官方文档Usingthe Orientation Sensor部分内容,首先是实例化一个方向传感器:

mOrientation = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);

虽然这样做没错,但是如果你在IDE中写了这样一行代码的话(API低版本可能不会提示),如文中开头说的,发现它已经过期了,但是没关系,我们先用这个看看,后面再介绍代替它的方法。

下面是创建一个自定义传感器事件监听接口:

class MySensorEventListener implements SensorEventListener {@Overridepublic void onSensorChanged(SensorEvent event) {// TODO Auto-generated method stubfloat a = event.values[0];azimuthAngle.setText(a + "");float b = event.values[1];pitchAngle.setText(b + "");float c = event.values[2];rollAngle.setText(c + "");}@Overridepublic void onAccuracyChanged(Sensor sensor, int accuracy) {// TODO Auto-generated method stub}}

最后通过SensorManager为Sensor注册监听即可:

mSensorManager.registerListener(new MySensorEventListener(),mOrientation, SensorManager.SENSOR_DELAY_NORMAL);

当设备位置发生变化时触发监听,界面上的值改变,由于模拟器无法演示传感器效果,就用真机截图看一下,这几个值无时无刻都在变化:

由于我在截图的时候手机是水平端平的,所以后两个值都接近于0,而第一个方位角就代表当前的方向了,好了,现在功能基本算实现了,那么现在就解决一下Sensor类的常量过期的问题。不难发现,在IDE中这行代码是这样的:

mOrientation= mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);

既然过期了必定有新的东西去替代它,我们打开源代码可以看到这样的注释:

显而易见,官方推荐我们用SensorManager.getOrientation()这个方法去替代原来的TYPE_ORITNTATION。那我们继续在源码中看看这个方法:

 public static float[] getOrientation(float[] R, float values[]) {/** 4x4 (length=16) case:*   /  R[ 0]   R[ 1]   R[ 2]   0  \*   |  R[ 4]   R[ 5]   R[ 6]   0  |*   |  R[ 8]   R[ 9]   R[10]   0  |*   \      0       0       0   1  /** 3x3 (length=9) case:*   /  R[ 0]   R[ 1]   R[ 2]  \*   |  R[ 3]   R[ 4]   R[ 5]  |*   \  R[ 6]   R[ 7]   R[ 8]  /**/if (R.length == 9) {values[0] = (float)Math.atan2(R[1], R[4]);values[1] = (float)Math.asin(-R[7]);values[2] = (float)Math.atan2(-R[6], R[8]);} else {values[0] = (float)Math.atan2(R[1], R[5]);values[1] = (float)Math.asin(-R[9]);values[2] = (float)Math.atan2(-R[8], R[10]);}return values;}

再看一下这个方法的注释中的一句话:

第一行讲了这个方法的作用,计算设备的方向基于旋转矩阵,这个旋转矩阵我们当成一种计算方向的算法就OK了,不必深究,下面再看我标出来的这句话,很明显说明了我们通常不需要这个方法的返回值,这个方法会根据参数R[ ]的数据填充values[ ]而后者就是我们想要的。既然不需要返回值,那么就是参数的问题了,这两个参数:float[ ] R 和 float[ ] values该怎么获取呢?继续看注释,首先是第一个参数R:

既然这个方法是基于旋转矩阵去计算方向,那么第一个参数R自然就表示一个旋转矩阵了,实际上它是用来保存磁场和加速度的数据的,根据注释我们可以发现让我们通过getRotationMatrix这个方法来填充这个参数R[ ],那我们就再去看看这个方法源码,依旧是SensorManager的一个静态方法:

 public static boolean getRotationMatrix(float[] R, float[] I,float[] gravity, float[] geomagnetic) {// TODO: move this to native code for efficiencyfloat Ax = gravity[0];float Ay = gravity[1];float Az = gravity[2];final float Ex = geomagnetic[0];final float Ey = geomagnetic[1];final float Ez = geomagnetic[2];float Hx = Ey*Az - Ez*Ay;float Hy = Ez*Ax - Ex*Az;float Hz = Ex*Ay - Ey*Ax;final float normH = (float)Math.sqrt(Hx*Hx + Hy*Hy + Hz*Hz);if (normH < 0.1f) {// device is close to free fall (or in space?), or close to// magnetic north pole. Typical values are  > 100.return false;}final float invH = 1.0f / normH;Hx *= invH;Hy *= invH;Hz *= invH;final float invA = 1.0f / (float)Math.sqrt(Ax*Ax + Ay*Ay + Az*Az);Ax *= invA;Ay *= invA;Az *= invA;final float Mx = Ay*Hz - Az*Hy;final float My = Az*Hx - Ax*Hz;final float Mz = Ax*Hy - Ay*Hx;if (R != null) {if (R.length == 9) {R[0] = Hx;     R[1] = Hy;     R[2] = Hz;R[3] = Mx;     R[4] = My;     R[5] = Mz;R[6] = Ax;     R[7] = Ay;     R[8] = Az;} else if (R.length == 16) {R[0]  = Hx;    R[1]  = Hy;    R[2]  = Hz;   R[3]  = 0;R[4]  = Mx;    R[5]  = My;    R[6]  = Mz;   R[7]  = 0;R[8]  = Ax;    R[9]  = Ay;    R[10] = Az;   R[11] = 0;R[12] = 0;     R[13] = 0;     R[14] = 0;    R[15] = 1;}}if (I != null) {// compute the inclination matrix by projecting the geomagnetic// vector onto the Z (gravity) and X (horizontal component// of geomagnetic vector) axes.final float invE = 1.0f / (float)Math.sqrt(Ex*Ex + Ey*Ey + Ez*Ez);final float c = (Ex*Mx + Ey*My + Ez*Mz) * invE;final float s = (Ex*Ax + Ey*Ay + Ez*Az) * invE;if (I.length == 9) {I[0] = 1;     I[1] = 0;     I[2] = 0;I[3] = 0;     I[4] = c;     I[5] = s;I[6] = 0;     I[7] =-s;     I[8] = c;} else if (I.length == 16) {I[0] = 1;     I[1] = 0;     I[2] = 0;I[4] = 0;     I[5] = c;     I[6] = s;I[8] = 0;     I[9] =-s;     I[10]= c;I[3] = I[7] = I[11] = I[12] = I[13] = I[14] = 0;I[15] = 1;}}return true;}

依旧是4个参数,请观察30~41行之间的代码,不难发现这个旋转矩阵无非就是一个3*34*4的数组,再观察一下if语句块中的代码,不难发现给数组元素依次赋值,而这些值是从哪里来的呢?我们29行倒着看,直到第4行,不难发现其实最后的数据源是通过这个方法的后两个参数提供的,即:float[] gravity, float[]geomagnetic,老规矩,看看这两个参数的注释:

到这里应该就清晰了吧,分别是从加速度传感器和地磁场传感器获取的值,那么很明显,应当在监听中的回调方法onSensorChanged中去获取数据,同时也验证了上面的判断方向需要两个传感器的说法,分别是:加速度传感器(Sensor.TYPE_ACCELEROMETER)和地磁场传感器(TYPE_MAGNETIC_FIELD)。

说完了getRotationMatrix方法的后两个参数,那么前两个参数RI又该如何定义呢?其实很简单,第一个参数R就是getOrientation()方法中需要填充的那个数组,大小是9。而第二个参数I是用于将磁场数据转换进实际的重力坐标系中的,一般默认设置为NULL即可。到这里关于方向传感器基本就已经介绍完毕,最后看一个完整的例子:

package com.example.sensordemo;import android.app.Activity;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;public class MainActivity extends Activity {private SensorManager mSensorManager;private Sensor accelerometer; // 加速度传感器private Sensor magnetic; // 地磁场传感器private TextView azimuthAngle;private float[] accelerometerValues = new float[3];private float[] magneticFieldValues = new float[3];private static final String TAG = "---MainActivity";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 实例化传感器管理者mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);// 初始化加速度传感器accelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);// 初始化地磁场传感器magnetic = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);azimuthAngle = (TextView) findViewById(R.id.azimuth_angle_value);calculateOrientation();}@Overrideprotected void onResume() {// TODO Auto-generated method stub// 注册监听mSensorManager.registerListener(new MySensorEventListener(),accelerometer, Sensor.TYPE_ACCELEROMETER);mSensorManager.registerListener(new MySensorEventListener(), magnetic,Sensor.TYPE_MAGNETIC_FIELD);super.onResume();}@Overrideprotected void onPause() {// TODO Auto-generated method stub// 解除注册mSensorManager.unregisterListener(new MySensorEventListener());super.onPause();}// 计算方向private void calculateOrientation() {float[] values = new float[3];float[] R = new float[9];SensorManager.getRotationMatrix(R, null, accelerometerValues,magneticFieldValues);SensorManager.getOrientation(R, values);values[0] = (float) Math.toDegrees(values[0]);Log.i(TAG, values[0] + "");if (values[0] >= -5 && values[0] < 5) {azimuthAngle.setText("正北");} else if (values[0] >= 5 && values[0] < 85) {// Log.i(TAG, "东北");azimuthAngle.setText("东北");} else if (values[0] >= 85 && values[0] <= 95) {// Log.i(TAG, "正东");azimuthAngle.setText("正东");} else if (values[0] >= 95 && values[0] < 175) {// Log.i(TAG, "东南");azimuthAngle.setText("东南");} else if ((values[0] >= 175 && values[0] <= 180)|| (values[0]) >= -180 && values[0] < -175) {// Log.i(TAG, "正南");azimuthAngle.setText("正南");} else if (values[0] >= -175 && values[0] < -95) {// Log.i(TAG, "西南");azimuthAngle.setText("西南");} else if (values[0] >= -95 && values[0] < -85) {// Log.i(TAG, "正西");azimuthAngle.setText("正西");} else if (values[0] >= -85 && values[0] < -5) {// Log.i(TAG, "西北");azimuthAngle.setText("西北");}}class MySensorEventListener implements SensorEventListener {@Overridepublic void onSensorChanged(SensorEvent event) {// TODO Auto-generated method stubif (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {accelerometerValues = event.values;}if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {magneticFieldValues = event.values;}calculateOrientation();}@Overridepublic void onAccuracyChanged(Sensor sensor, int accuracy) {// TODO Auto-generated method stub}}}

四、总结

本文详细介绍了官方提供的方向传感器新方法,对于具体的精度和效率的提升,本人没有进一步去比较,希望尝试过的朋友能和大家一起讨论下。后续工作会把两种方法对比结果补充进来~~~

参考:http://www.bkjia.com/Androidjc/924827.html

AndroidOrientation Sensor(方向传感器),新的替代方法详解(安卓官方提供)相关推荐

  1. linux系统下添加新硬盘的方法详解

    对于linux新手来说,在linux上添加新硬盘,是很有挑战性的一项工作. 在Linux服务器上把硬盘接好,启动linux,以root登陆. fdisk -l ## 这里是查看目前系统上有几块硬盘 D ...

  2. python索引取值_对pandas的层次索引与取值的新方法详解

    1.层次索引 1.1 定义 在某一个方向拥有多个(两个及两个以上)索引级别,就叫做层次索引. 通过层次化索引,pandas能够以较低维度形式处理高纬度的数据 通过层次化索引,可以按照层次统计数据 层次 ...

  3. CSS3 渐变新特性和HTML5 Canvas画布背景渐变实现方法详解

    CSS3 渐变新特性和HTML5 Canvas画布背景渐变实现方法详解 大家好,又见面了,感觉我上一篇博客对部分同学都挺有帮助的,于是我决定继续写下去,会继续的解析知识点让大家更容易理解,希望能给大家 ...

  4. ES5和ES6数组遍历方法详解

    ES5和ES6数组遍历方法详解 在ES5中常用的10种数组遍历方法: 1.原始的for循环语句 2.Array.prototype.forEach数组对象内置方法 3.Array.prototype. ...

  5. python反向缩进_在Pycharm中对代码进行注释和缩进的方法详解

    在Pycharm中对代码进行注释和缩进的方法详解 一.注释 1. #单行注释 2. """ 多行注释 """ 3. pycharm多行注释快 ...

  6. 【Python入门】Python字符串的45个方法详解

    Python中字符串对象提供了很多方法来操作字符串,功能相当丰富.必须进行全面的了解与学习,后面的代码处理才能更得心应手,编程水平走向新台阶的坚实基础.目前一共有45个方法,给大家分类整理,可以收藏查 ...

  7. 【深度学习/机器学习】为什么要归一化?归一化方法详解

    [深度学习/机器学习]为什么要归一化?归一化方法详解 文章目录 1. 介绍 1.1 什么是归一化 1.2 归一化的好处 2. 归一化方法 2.1 最大最小标准化(Min-Max Normalizati ...

  8. moviepy音视频剪辑:视频剪辑基类VideoClip的属性及方法详解

    ☞ ░ 前往老猿Python博文目录 ░ 一.概述 在<moviepy音视频剪辑:moviepy中的剪辑基类Clip详解>和<moviepy音视频剪辑:moviepy中的剪辑基类Cl ...

  9. 【前端用法】$.ajax()方法详解,以及$.ajax()标准写法

    使用JQuery中的异步请求$.ajax()方法,经常记不全参数,所以在这里记录一下,方便后续使用. 首先看一个标准的写法: $(document).ready(function () {$.ajax ...

最新文章

  1. 1134. Vertex Cover (25)
  2. 前端封装接口弹出错误_项目实践:SpringBoot三招组合拳,手把手教你打出优雅的后端接口...
  3. webService上传图片
  4. html5+css3实战之-幽灵按钮
  5. Oracle案例:深入解析ASM rebalance无法启动
  6. python读取txt文件并输出到表格_Python读取txt内容写入xls格式excel中的方法
  7. python扩展库xlwt支持对_python第三方库——xlrd和xlwt操作Excel文件学习
  8. 2021-08-30二叉树后向遍历 leetcode 栈
  9. python实现三级菜单
  10. python爬取b站番剧链接,Scrapy爬虫爬取B站视频标题及链接
  11. mysql 重启监听器_Oracle Lsnrctl监听器的启动和关闭
  12. Labview LabSQL下载
  13. 前端Mocha+Chai单元测试
  14. 27位名校博士选择入职中学?!顶尖人才浪费了吗?
  15. 必学比知性能指标和常用术语
  16. GeoPandas入门 | 05-Python可视化空间数据
  17. 为什么你裹成粽子还冷得瑟瑟发抖,别人在雪山徒步却轻装上阵,全因这件黑科技上装!...
  18. OpenCV的imwrite或者imshow全白
  19. matlab:使用改进欧拉法,求解微分方程
  20. UI----Android开发艺术字体设置

热门文章

  1. 视频编解码的理论和实践1:基础知识介绍
  2. 【干货分享】如何应对线上数据库的误操作
  3. swscanf_s和sscanf_s的使用
  4. Swift 中如何测试驱动开发
  5. 2016.2.17文件夹选择框及文件选择框
  6. bzero, memset ,setmem 区别【转】
  7. 网络请求的基本知识《极客学院 --AFNetworking 2.x 网络解析详解--1》学习笔记...
  8. Android——SQLite实现面向对象CRUD
  9. PHP验证码无法显示的原因
  10. samba   服务