一、前言

本篇blog是我的“Android进阶”的第一篇文章,从初学Android到现在断断续续也有4个多月时间了,也算是有了一些自己的心得体会,也能自己独立做一些东西了,这都要感谢我们公司的安卓开发璟博和无所不能的鸿洋给我的帮助和指点。本系列blog将记录我在开发中、学习中遇到的较为重点的、值得记录的知识点和技巧,简单的说就不再是基础教程了。由于项目中需要用到方向传感器,所以就借此机会来学一学Android的传感器部分的知识了,自然也就是本篇blog的内容了。

二、传感器基础

官方文档说的很清楚,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

[java]  view plain copy
  1. SensorManager mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);

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

[java]  view plain copy
  1. List<Sensor> deviceSensors = mSensorManager.getSensorList(Sensor.TYPE_ALL);

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

[java]  view plain copy
  1. package com.example.sensordemo;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import android.app.Activity;
  5. import android.content.Context;
  6. import android.hardware.Sensor;
  7. import android.hardware.SensorManager;
  8. import android.os.Bundle;
  9. import android.widget.ArrayAdapter;
  10. import android.widget.ListView;
  11. public class MainActivity extends Activity {
  12. private SensorManager mSensorManager;
  13. private ListView sensorListView;
  14. private List<Sensor> sensorList;
  15. @Override
  16. protected void onCreate(Bundle savedInstanceState) {
  17. super.onCreate(savedInstanceState);
  18. setContentView(R.layout.activity_main);
  19. sensorListView = (ListView) findViewById(R.id.lv_all_sensors);
  20. // 实例化传感器管理者
  21. mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
  22. // 得到设置支持的所有传感器的List
  23. sensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL);
  24. List<String> sensorNameList = new ArrayList<String>();
  25. for (Sensor sensor : sensorList) {
  26. sensorNameList.add(sensor.getName());
  27. }
  28. ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
  29. android.R.layout.simple_list_item_1, sensorNameList);
  30. sensorListView.setAdapter(adapter);
  31. }
  32. }

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

了解了传感器的基础知识,下面我们就具体看看我们需要用到的Orientation Sensor。

三、Orientation Sensor

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

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

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

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

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

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

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

[java]  view plain copy
  1. mOrientation = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);

虽然这样做没错,但是如果你在IDE中写了这样一行代码的话,不难发现它已经过期了,但是没关系,我们先用这个看看,后面再介绍代替它的方法。

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

[java]  view plain copy
  1. class MySensorEventListener implements SensorEventListener {
  2. @Override
  3. public void onSensorChanged(SensorEvent event) {
  4. // TODO Auto-generated method stub
  5. float a = event.values[0];
  6. azimuthAngle.setText(a + "");
  7. float b = event.values[1];
  8. pitchAngle.setText(b + "");
  9. float c = event.values[2];
  10. rollAngle.setText(c + "");
  11. }
  12. @Override
  13. public void onAccuracyChanged(Sensor sensor, int accuracy) {
  14. // TODO Auto-generated method stub
  15. }
  16. }

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

[java]  view plain copy
  1. mSensorManager.registerListener(new MySensorEventListener(),
  2. mOrientation, SensorManager.SENSOR_DELAY_NORMAL);

当设备位置发生变化时触发监听,界面上的值改变,由于模拟器无法演示传感器效果,真机也没有root没法用屏幕投影啥的,所以就贴一张截图象征性看一下,这几个值无时无刻都在变化:

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

mOrientation = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);

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

显而易见,官方推荐我们用SensorManager.getOrientation()这个方法去替代原来的TYPE_ORITNTATION。那我们继续在源码中看看这个方法:
[java]  view plain copy
  1. public static float[] getOrientation(float[] R, float values[]) {
  2. /*
  3. * 4x4 (length=16) case:
  4. *   /  R[ 0]   R[ 1]   R[ 2]   0  \
  5. *   |  R[ 4]   R[ 5]   R[ 6]   0  |
  6. *   |  R[ 8]   R[ 9]   R[10]   0  |
  7. *   \      0       0       0   1  /
  8. *
  9. * 3x3 (length=9) case:
  10. *   /  R[ 0]   R[ 1]   R[ 2]  \
  11. *   |  R[ 3]   R[ 4]   R[ 5]  |
  12. *   \  R[ 6]   R[ 7]   R[ 8]  /
  13. *
  14. */
  15. if (R.length == 9) {
  16. values[0] = (float)Math.atan2(R[1], R[4]);
  17. values[1] = (float)Math.asin(-R[7]);
  18. values[2] = (float)Math.atan2(-R[6], R[8]);
  19. } else {
  20. values[0] = (float)Math.atan2(R[1], R[5]);
  21. values[1] = (float)Math.asin(-R[9]);
  22. values[2] = (float)Math.atan2(-R[8], R[10]);
  23. }
  24. return values;
  25. }

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

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

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

[java]  view plain copy
  1. public static boolean getRotationMatrix(float[] R, float[] I,
  2. float[] gravity, float[] geomagnetic) {
  3. // TODO: move this to native code for efficiency
  4. float Ax = gravity[0];
  5. float Ay = gravity[1];
  6. float Az = gravity[2];
  7. final float Ex = geomagnetic[0];
  8. final float Ey = geomagnetic[1];
  9. final float Ez = geomagnetic[2];
  10. float Hx = Ey*Az - Ez*Ay;
  11. float Hy = Ez*Ax - Ex*Az;
  12. float Hz = Ex*Ay - Ey*Ax;
  13. final float normH = (float)Math.sqrt(Hx*Hx + Hy*Hy + Hz*Hz);
  14. if (normH < 0.1f) {
  15. // device is close to free fall (or in space?), or close to
  16. // magnetic north pole. Typical values are  > 100.
  17. return false;
  18. }
  19. final float invH = 1.0f / normH;
  20. Hx *= invH;
  21. Hy *= invH;
  22. Hz *= invH;
  23. final float invA = 1.0f / (float)Math.sqrt(Ax*Ax + Ay*Ay + Az*Az);
  24. Ax *= invA;
  25. Ay *= invA;
  26. Az *= invA;
  27. final float Mx = Ay*Hz - Az*Hy;
  28. final float My = Az*Hx - Ax*Hz;
  29. final float Mz = Ax*Hy - Ay*Hx;
  30. if (R != null) {
  31. if (R.length == 9) {
  32. R[0] = Hx;     R[1] = Hy;     R[2] = Hz;
  33. R[3] = Mx;     R[4] = My;     R[5] = Mz;
  34. R[6] = Ax;     R[7] = Ay;     R[8] = Az;
  35. } else if (R.length == 16) {
  36. R[0]  = Hx;    R[1]  = Hy;    R[2]  = Hz;   R[3]  = 0;
  37. R[4]  = Mx;    R[5]  = My;    R[6]  = Mz;   R[7]  = 0;
  38. R[8]  = Ax;    R[9]  = Ay;    R[10] = Az;   R[11] = 0;
  39. R[12] = 0;     R[13] = 0;     R[14] = 0;    R[15] = 1;
  40. }
  41. }
  42. if (I != null) {
  43. // compute the inclination matrix by projecting the geomagnetic
  44. // vector onto the Z (gravity) and X (horizontal component
  45. // of geomagnetic vector) axes.
  46. final float invE = 1.0f / (float)Math.sqrt(Ex*Ex + Ey*Ey + Ez*Ez);
  47. final float c = (Ex*Mx + Ey*My + Ez*Mz) * invE;
  48. final float s = (Ex*Ax + Ey*Ay + Ez*Az) * invE;
  49. if (I.length == 9) {
  50. I[0] = 1;     I[1] = 0;     I[2] = 0;
  51. I[3] = 0;     I[4] = c;     I[5] = s;
  52. I[6] = 0;     I[7] =-s;     I[8] = c;
  53. } else if (I.length == 16) {
  54. I[0] = 1;     I[1] = 0;     I[2] = 0;
  55. I[4] = 0;     I[5] = c;     I[6] = s;
  56. I[8] = 0;     I[9] =-s;     I[10]= c;
  57. I[3] = I[7] = I[11] = I[12] = I[13] = I[14] = 0;
  58. I[15] = 1;
  59. }
  60. }
  61. return true;
  62. }

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

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

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

[java]  view plain copy
  1. package com.example.sensordemo;
  2. import android.app.Activity;
  3. import android.content.Context;
  4. import android.hardware.Sensor;
  5. import android.hardware.SensorEvent;
  6. import android.hardware.SensorEventListener;
  7. import android.hardware.SensorManager;
  8. import android.os.Bundle;
  9. import android.util.Log;
  10. import android.widget.TextView;
  11. public class MainActivity extends Activity {
  12. private SensorManager mSensorManager;
  13. private Sensor accelerometer; // 加速度传感器
  14. private Sensor magnetic; // 地磁场传感器
  15. private TextView azimuthAngle;
  16. private float[] accelerometerValues = new float[3];
  17. private float[] magneticFieldValues = new float[3];
  18. private static final String TAG = "---MainActivity";
  19. @Override
  20. protected void onCreate(Bundle savedInstanceState) {
  21. super.onCreate(savedInstanceState);
  22. setContentView(R.layout.activity_main);
  23. // 实例化传感器管理者
  24. mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
  25. // 初始化加速度传感器
  26. accelerometer = mSensorManager
  27. .getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
  28. // 初始化地磁场传感器
  29. magnetic = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
  30. azimuthAngle = (TextView) findViewById(R.id.azimuth_angle_value);
  31. calculateOrientation();
  32. }
  33. @Override
  34. protected void onResume() {
  35. // TODO Auto-generated method stub
  36. // 注册监听
  37. mSensorManager.registerListener(new MySensorEventListener(),
  38. accelerometer, Sensor.TYPE_ACCELEROMETER);
  39. mSensorManager.registerListener(new MySensorEventListener(), magnetic,
  40. Sensor.TYPE_MAGNETIC_FIELD);
  41. super.onResume();
  42. }
  43. @Override
  44. protected void onPause() {
  45. // TODO Auto-generated method stub
  46. // 解除注册
  47. mSensorManager.unregisterListener(new MySensorEventListener());
  48. super.onPause();
  49. }
  50. // 计算方向
  51. private void calculateOrientation() {
  52. float[] values = new float[3];
  53. float[] R = new float[9];
  54. SensorManager.getRotationMatrix(R, null, accelerometerValues,
  55. magneticFieldValues);
  56. SensorManager.getOrientation(R, values);
  57. values[0] = (float) Math.toDegrees(values[0]);
  58. Log.i(TAG, values[0] + "");
  59. if (values[0] >= -5 && values[0] < 5) {
  60. azimuthAngle.setText("正北");
  61. } else if (values[0] >= 5 && values[0] < 85) {
  62. // Log.i(TAG, "东北");
  63. azimuthAngle.setText("东北");
  64. } else if (values[0] >= 85 && values[0] <= 95) {
  65. // Log.i(TAG, "正东");
  66. azimuthAngle.setText("正东");
  67. } else if (values[0] >= 95 && values[0] < 175) {
  68. // Log.i(TAG, "东南");
  69. azimuthAngle.setText("东南");
  70. } else if ((values[0] >= 175 && values[0] <= 180)
  71. || (values[0]) >= -180 && values[0] < -175) {
  72. // Log.i(TAG, "正南");
  73. azimuthAngle.setText("正南");
  74. } else if (values[0] >= -175 && values[0] < -95) {
  75. // Log.i(TAG, "西南");
  76. azimuthAngle.setText("西南");
  77. } else if (values[0] >= -95 && values[0] < -85) {
  78. // Log.i(TAG, "正西");
  79. azimuthAngle.setText("正西");
  80. } else if (values[0] >= -85 && values[0] < -5) {
  81. // Log.i(TAG, "西北");
  82. azimuthAngle.setText("西北");
  83. }
  84. }
  85. class MySensorEventListener implements SensorEventListener {
  86. @Override
  87. public void onSensorChanged(SensorEvent event) {
  88. // TODO Auto-generated method stub
  89. if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
  90. accelerometerValues = event.values;
  91. }
  92. if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
  93. magneticFieldValues = event.values;
  94. }
  95. calculateOrientation();
  96. }
  97. @Override
  98. public void onAccuracyChanged(Sensor sensor, int accuracy) {
  99. // TODO Auto-generated method stub
  100. }
  101. }
  102. }

四、总结

第一次研究Android传感器感觉还是挺难的,继续努力吧,要学的东西还有很多,先去给Map项目集成指南针啦

原文链接:
http://www.bkjia.com/Androidjc/924827.html

Orientation类型传感器放弃,新方案改用旋转矩阵实现指南针相关推荐

  1. AndroidOrientation Sensor(方向传感器),新的替代方法详解(安卓官方提供)

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

  2. 正则不等于一个字符串_王晓阳 | 物理主义不等于物理学主义——表述物理主义的一个新方案...

    上海市社会科学界联合会主管主办 摘要:如何恰当地表述物理主义,是当前物理主义者面临的两大难题之一.常见的表述方案有四种:"基于物理学理论的"方案."基于范型物理对象的&q ...

  3. 电力物联网智慧路灯充电桩传感器技术应用方案

    电力物联网智慧路灯充电桩传感器技术应用方案 近几年来,全国各地电力电网机构以及城市基建部门都在积极通过利用在电力物联网技术,与智慧城市建设相结合,积极开展了智慧路灯感知共享平台开发和试点应用,旨在解决 ...

  4. 文远知行发布自动驾驶新方案:日产纯电动车型,搭载性价比激光雷达

    李根 发自 圣何塞·GTC  量子位 报道 | 公众号 QbitAI 新车型.新感知方案,新软件系统. 这是英伟达投资的自动驾驶公司文远知行WeRide,在2019年现场GTC展现的最新情况. 文远知 ...

  5. FPGA的发展策略、产品进程和新方案

    FPGA的发展策略.产品进程和新方案 2011年09月24日 全球市场回暖,中国市场的持续向好,在ASIC和ASSP市场中不断攻城掠地等等因素都在推动FPGA市场的增长.以通信市场为例,基于可编程器件 ...

  6. Visual Studio无法嵌入互操作类型“XXX.XXX”。请改用适用的接口

    问题描述 使用Visual Studio开发C#项目,添加引用后访问的时候出现了***无法嵌入互操作类型"XXX.XXX".请改用适用的接口***的问题 解决方法 在Visual ...

  7. Go 学习笔记(32)— 类型系统(命名类型、未命名类型、底层类型、类型强制转换、类型别名和新声明类型)

    1. 命名类型和未命名类型 1.1 命名类型 类型可以通过标识符来表示,这种类型称为命名类型( Named Type ). Go 语言的基本类型中有 20 个预声明简单类型都是命名类型, Go 语言还 ...

  8. 和12岁小同志搞创客开发:如何驱动各类型传感器?

    目录 1.数字量输出类型传感器 2.数字量输入类型传感器 3.模拟量电压类型传感器 4.模拟量电流类型传感器 5.协议类型传感器  机缘巧合在网上认识一位12岁小同志,从零开始系统辅导其创客开发思维和 ...

  9. 网站站内优化新方案,SEO运营人员值得一看

    网站在建设完成之后,就会有大量的优化工作来提升网站的排名和权重.网站优化可分为站内优化和站外优化.随着互联网的不断进步和改变,搜索引擎的规则也在升级更新,那么我们该如何在时代的潮流下进行站内优化呢? ...

最新文章

  1. 2022-2028年中国动力电池行业深度调研及投资前景预测报告
  2. python文件操作(open()、write()、read()、readline()、readlines()、seek()、os)
  3. TensorFlow基础2(张量)
  4. git 提交命令_Git和Github快速上手指南
  5. Java 线程第三版 第四章 Thread Notification 读书笔记
  6. python爬取贴吧所有帖子-Python实现的爬取百度贴吧图片功能完整示例
  7. Centos 开机无法输入密码的问题
  8. Windows Phone 实用开发技巧(9):自定义Windows Phone 页面切换动画
  9. oracle导入时 ora39166,impdp ORA-39002,ORA-39166,ORA-39164的问题及解决
  10. 手把手教你使用Pandas读取结构化数据
  11. Python之数据分析(Numpy的数组切片、数组变维、组合与拆分)
  12. redhat 中安装rpm包时遇到异常 “error: Failed dependencies:xinetd is needed by .”
  13. 安装ansible自动运维工具
  14. python第一周练习 货币转换
  15. 【T+】畅捷通T+软件打印预览凭证或UFO生成报表 提示加载TBillOneCore.dll失败
  16. InDesign转曲字体 导出PDF的技巧
  17. SpringBoot官方开发工具,热部署和远程调试真带劲
  18. Python中print换行问题
  19. 基于5G森林防火无线监控解决方案
  20. SparkSession

热门文章

  1. 安装redis及redis集群及解决连接不上redist问题
  2. STM32Cube工程转为Keil工程的方法介绍
  3. php江湖源码,PHP源码包编译
  4. python画名字七十周年快乐用英语怎么说_一周年快乐的英语怎么说?
  5. 微星MAG B550M MORTAR 迫击炮无法开启虚拟化(SVM Mode)导致黑屏的问题与解决方法
  6. Resnet50残差网络代码详解
  7. PMP考试报名费用可以怎么支付?给你说明白
  8. 【基础篇】详解Zookeeper客户端Curator
  9. AOP -- 注解 @Aspect 、@Pointcut
  10. 阿里百秀项目之用户模块