本博文学习一下Android中几种传感器的应用
目录
Android传感器
传感器就是通过对外界信号的感受和探测,并按照一定的规律把这些信号转成我们需要的信息的一些微型物理设备,比如我们的手机中就存在很多传感器。android中提供了相应的接口,让我们可以访问这些传感器,并使用他们的数据。与传感器交互需要使用Sensor对象。Sennor就是传感器的一个类,包括传感器的类型、精度等等他都可以来描述。
Android提供sensor framework也帮助开发者利用设备的传感器进行开发。传感器是只读的(NFC例外),因此只要设定监听器接受传感器信息即可。手机上有各种各样的传感器
- 光传感器(Light sensor)
- 接近感应器(Proximity sensor)
- 温度传感器(Temperature sensor)
- 压力传感器(Pressure sensor)
- 陀螺仪传感器(Gyroscope sensor)
- 加速感应器(Accelerometer)
- 磁场感应器(Magnetic field sensor)
- 方向感应器(Orientation sensor)
- 重力感应器(Gravity sensor,Android 2.3引入)
- 线性加速感应器(Linear acceleration sensor ,Android 2.3引入)
- 旋转矢量传感器(Rotation vector sensor,Android 2.3)
- 相对湿度传感器(Relative humidity sensor,Android 4.0)
- 近场通信(NFC)传感器(Android 2.3引入),NFC和其他不一样,具有读写功能。
使用传感器步骤
一般来说,要使用传感器要分三个步骤:
1、获取传感器管理者对象。获取一个传感器管理者SensorManager方便对Sensor的管理
// 获取传感器管理者对象
SensorManager mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
2. 获取指定的传感器对象。SensorManager有方法是用来获取指定类型传感器的,如getDefaultSensor(int type),其中type可以使用Sensor类中的常量设置,Sensor中提供了很多传感器类型的常量
Sensor sensor = mSensorManager.getDefaultSensor(int type);
其中type就是传感器的类型了
- 加速度传感器Sensor.TYPE_ACCELEROMETER
- 方向传感器Sensor.TYPE_ORIENTATION
- 磁场传感器Sensor.TYPE_MAGNETIC_FIELD
- 温度传感器Sensor.TYPE_AMBIENT_TEMPERATURE
- 光传感器Sensor.TYPE_LIGHT
- 压力传感器Sensor.TYPE_PRESSURE
3.给传感器添加监听。通过sensorManager来为传感器注册一个监听器,然后程序就可以通过这个监听来获取传感器获取的数据了(一般来说,在onResume()方法中注册)
public boolean registerListener(SensorEventListener listener, Sensor sensor, int samplingPeriodUs);
msensorManager.registerListener(this,sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT),SensorManager.SENSOR_DELAY_GAME);
- 第一个参数是传感器监听器
- 第二个参数是上面获取到的传感器对象
- 第三个参数是指定获取传感器数据的频率
samplingPeriodUs 采样频率支持以下几个频率值
- SensorManager.SENSOR_DELAY_FASTEST。最快,延迟最小,比较消耗电量,由于传递的为原始数据,如果算法处理不好将会影响应用的性能
- SensorManager.SENSOR_DELAY_GAME。适合游戏的频率,在一般实时性要求的应用上适合使用这种频率。
- SensorManager.SENSOR_DELAY_NORMAL。正常的频率,实时性要求不是很高的时候适合这种频率。
- SensorManager.SENSOR_DELAY_UI。适合普通用户界面的频率,这种模式比较省电,而且系统开销也很小,但是延迟较大,适合在普通的小程序中使用
在实现监听器的时候,要重写两个方法onSensorChanged()和onAccuracyChanged(),第一个是当传感器的值改变时回调,第二个是当传感器精度改变时回调。另外,传感器的值是放在数组中保存的一般有三个值,分别是xyz轴三个方向上的值。
4、最后别忘记解监听
在onStop()和onPause()方法中注销监听器
sensorManager.unregisterListener(this);
Demo Test
查看手机支持哪些传感器
界面布局
之前博文《Android学习笔记之——Activity》已经介绍过如何使用Menu,本博文的框架则是基于该框架的。按不同的按钮实现不同sensor
首先在res目录下新建一个menu文件夹,右击res目录→New→Directory,输入文件夹名menu,点击OK。
接着在这个文件夹下再新建一个名叫menu_main的菜单文件,右击menu文件夹→New→Menu resource file。定义菜单文件如下
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:title="传感器列表" android:id="@+id/test_sensor_list"/>
<item android:title="光传感器" android:id="@+id/test_light_sensor"/>
<item android:title="近距离传感器" android:id="@+id/test_proximity_sensor"/>
<item android:title="陀螺仪" android:id="@+id/test_gyroscope_sensor"/>
<item android:title="加速测量仪" android:id="@+id/test_accelerometer_sensor"/>
<item android:title="重力测量" android:id="@+id/test_accelerometer_2_sensor"/>
<item android:title="磁场传感器" android:id="@+id/test_magnetic_sensor"/>
<item android:title="方位测量" android:id="@+id/test_orientation"/>
</menu>
在主文件下定义整个界面如下
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="android Sensor Test"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
package com.example.sensortest;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// TODO Auto-generated method stub
getMenuInflater().inflate(R.menu.menu_main, menu);
//inflate() 方法接收两个参数,
//第一个参数用于指定我们通过哪一个资源文件来创建菜单,这里当然传入R.menu.menu_main 。
//第二个参数用于指定我们的菜单项将添加到哪一个Menu 对象当中,这里直接使用onCreateOptionsMenu() 方法中传入的menu 参数。
return super.onCreateOptionsMenu(menu);
}
//然后给这些按键定义内容(不同的按菜单按键启动不一样的activity)
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// TODO Auto-generated method stub
switch(item.getItemId()){
//通过Intent来启动别的activity
case R.id.test_sensor_list:
startActivity(new Intent(this,SensorListActivity.class));
break;
case R.id.test_light_sensor:
// startActivity(new Intent(this,LightSensorActivity.class));
break;
case R.id.test_proximity_sensor:
// startActivity(new Intent(this,ProximitySensorActivity.class));
break;
case R.id.test_gyroscope_sensor:
// startActivity(new Intent(this,GyroscopeSensorActivity.class));
break;
case R.id.test_accelerometer_sensor:
// startActivity(new Intent(this,AccelerometerSensorActivity.class));
break;
case R.id.test_accelerometer_2_sensor:
// startActivity(new Intent(this,GravityActivity.class));
break;
case R.id.test_magnetic_sensor:
// startActivity(new Intent(this,MagneticFieldSensorActivity.class));
break;
case R.id.test_orientation:
// startActivity(new Intent(this,VirtualJax.class));
break;
default:
break;
}
return true;
}
}
结果如下
传感器list
package com.example.sensortest;
import androidx.appcompat.app.AppCompatActivity;
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import java.util.HashMap;
import java.util.List;
public class SensorListActivity extends AppCompatActivity {
private TextView tv=null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sensor_list);
tv=(TextView) findViewById(R.id.sensor_list_text);
showSensorList();//自定义的函数,展示手机所带有的sensor
}
private HashMap<Integer, String> sensorTypes = new HashMap<Integer,String>();
{
sensorTypes.put(Sensor.TYPE_ACCELEROMETER, "TYPE_ACCELEROMETER");
sensorTypes.put(Sensor.TYPE_AMBIENT_TEMPERATURE, "TYPE_AMBIENT_TEMPERATURE");
sensorTypes.put(Sensor.TYPE_GAME_ROTATION_VECTOR, "TYPE_GAME_ROTATION_VECTOR");
sensorTypes.put(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR, "TYPE_GEOMAGNETIC_ROTATION_VECTOR");
sensorTypes.put(Sensor.TYPE_GRAVITY, "TYPE_GRAVITY");
sensorTypes.put(Sensor.TYPE_GYROSCOPE, "TYPE_GYROSCOPE");
sensorTypes.put(Sensor.TYPE_GYROSCOPE_UNCALIBRATED,"TYPE_GYROSCOPE_UNCALIBRATED");
sensorTypes.put(Sensor.TYPE_LIGHT, "TYPE_LIGHT");
sensorTypes.put(Sensor.TYPE_LINEAR_ACCELERATION, "TYPE_LINEAR_ACCELERATION");
sensorTypes.put(Sensor.TYPE_MAGNETIC_FIELD, "TYPE_MAGNETIC_FIELD");
sensorTypes.put(Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED, "TYPE_MAGNETIC_FIELD_UNCALIBRATED");
sensorTypes.put(Sensor.TYPE_ORIENTATION,"TYPE_ORIENTATION (deprecated)");//use SensorManager.getOrientation() instead.
sensorTypes.put(Sensor.TYPE_PRESSURE, "TYPE_PRESSURE");
sensorTypes.put(Sensor.TYPE_PROXIMITY, "TYPE_PROXIMITY");
sensorTypes.put(Sensor.TYPE_RELATIVE_HUMIDITY, "TYPE_RELATIVE_HUMIDITY");
sensorTypes.put(Sensor.TYPE_ROTATION_VECTOR, "TYPE_ROTATION_VECTOR");
sensorTypes.put(Sensor.TYPE_SIGNIFICANT_MOTION,"TYPE_SIGNIFICANT_MOTION");
sensorTypes.put(Sensor.TYPE_STEP_COUNTER,"TYPE_STEP_COUNTER");
sensorTypes.put(Sensor.TYPE_STEP_DETECTOR,"TYPE_STEP_DETECTOR");
sensorTypes.put(Sensor.TYPE_TEMPERATURE, "TYPE_TEMPERATURE(deprecated)");//Sensor.TYPE_AMBIENT_TEMPERATURE
}
private void showSensorList(){
showInfo("本设备具有以下传感器:");
//首先获取传感器管理器
SensorManager mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
//获取设备的传感器对象列表
List<Sensor> list=mSensorManager.getSensorList(Sensor.TYPE_ALL);
//显示传感器对象的信息。
// 同样的传感器类型,不同厂家不同型号会有所不同,例如解析度不同,数值范围不同,功耗对应用性能重要,
// 但是老实说这更多是OEM应该关心的问题,因为器件是他们选择的。对于应该如果发现功耗过大,就需要尽量避免长时间使用。
for(Sensor sensor : list){
showInfo("名字:" + sensor.getName());
showInfo(" type:" + sensorTypes.get(sensor.getType()) + "(" + sensor.getType() +")");
showInfo(" vendor:" + sensor.getVendor());
showInfo(" version:" + sensor.getVersion());
showInfo(" resolution:" + sensor.getResolution());
showInfo(" max range:" + sensor.getMaximumRange());
showInfo(" power:" + sensor.getPower());
}
}
private void showInfo(String info){
tv.append("\n" + info);
Log.d("SensorList",info);
}
}
<?xml version="1.0" encoding="utf-8"?>
<!--<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"-->
<!-- xmlns:app="http://schemas.android.com/apk/res-auto"-->
<!-- xmlns:tools="http://schemas.android.com/tools"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="match_parent"-->
<!-- tools:context=".SensorListActivity">-->
<!--</androidx.constraintlayout.widget.ConstraintLayout>-->
<LinearLayout
android:orientation="vertical"
android:layout_height="match_parent"
android:layout_width="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<ScrollView
android:layout_height="0dp"
android:layout_width="match_parent"
android:layout_weight="1">
<TextView
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:id="@+id/sensor_list_text"/>
</ScrollView>
</LinearLayout>
光传感器
光传感器比较简单,智能手机都会具有,读取光亮度值,单位为SI lux。维基百科:1流明每平方米面积,就是1勒克斯(lux)。而SI,即国际单位制,一串法文。小例子很简单,获取传感器,甚至监听器,将监听器监听到的数值在TextView上显示。
package com.example.sensortest;
import androidx.appcompat.app.AppCompatActivity;
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 LightSensorActivity extends AppCompatActivity implements SensorEventListener {
private SensorManager sensorManager=null;
private Sensor lightSensor=null;
private TextView tv=null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//使用相同的layout
setContentView(R.layout.activity_sensor_list);
tv=(TextView) findViewById(R.id.sensor_list_text);
//获取light传感器
//先定义管理器
sensorManager=(SensorManager)getSystemService(SENSOR_SERVICE);
lightSensor=sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
}
//在活动准备好和用户进行交互的时候调用onResume()方法。此时的活动一定位于返回栈的栈顶,并且处于运行状态。
@Override
protected void onResume() {
//如果有人监听传感器,传感器工作,需要耗电,因此我们应该只在需要的时候进行监听
// 如果activity不在前台就不需要监听,因此在onResume()注册监听器,在onPause()中注销监听器
sensorManager.registerListener(this,lightSensor,SensorManager.SENSOR_DELAY_NORMAL);
///第三个参数是指Sensor Event变化通知的频率,有效值为NORMAL,UI,GAME,FASTER。
//有些传感器很灵敏,短时间会有大量数据,对内存和垃圾回收造成压力,可能会导致APP的性能问题,因此根据需要选择合适的频率。
//对于旋转矢量传感器,通常需要不断地去读取。
super.onResume();
}
//这个方法在系统准备去启动或者恢复另一个活动的时候调用
@Override
protected void onPause() {
// TODO Auto-generated method stub
sensorManager.unregisterListener(this,lightSensor);
super.onPause();
}
//SensorEventListener要实现两个接口onAccuracyChanged()和onSensorChanged()。
// onAccuracyChanged()会在精度改变或在注册监听器时调用。
// accuracy分为4档,0(unreliable),1(low),2(medium),3(high)
// 注意0并不代表有问题,同时是传感器需要校准。
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// TODO Auto-generated method stub
showInfo(sensor.getName() + " accuracy changed: " + accuracy);
}
//onSensorChanged()在传感器数值发生变化已经注册监听器时调用,其更新频率就是注册中的参数三。
// 对于光传感器,有效数值存放在values[0]中的,单位为SI lunx。光传感器通常位于上方(一般靠左侧),
// 除了前置摄像头外还有一个孔,一般就是它。遮盖会触发onSensorChanged()
@Override
public void onSensorChanged(SensorEvent event) {
// TODO Auto-generated method stub
showInfo("Get Sensor Event: " + event.sensor.getName() + " " + event.values[0] );
}
private void showInfo(String info){
tv.append("\n" + info);
Log.d("LightSensor",info);
}
}
结果如下图所示
近距离传感器、温度传感器、气压传感器
近距离传感器(Proximity sensor)给出物体与设备之间的距离(cm),或者告知物体是近或是远。有些近距离传感器值的解析度与最大值一样,就是只返回两个值,0.0或者最大值。我们无法在安装设备之前知晓传感器的类型,因此我们要适配这两种情况。最大值一般在1.0~6.0。在手机中,近距离传感器和光传感器通常是同一物理硬件,但是Android在逻辑上视为两个独立的传感器。
package com.example.sensortest;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
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.view.View;
import android.widget.Button;
import android.widget.TextView;
public class ProximitySensorActivity extends AppCompatActivity implements SensorEventListener {
private SensorManager sensorManager=null;
private Sensor sensor=null;
private TextView tv=null;
//定义一个函数在TextView仲显示信息
private void showInfo(String info){
tv.append("\n" + info);
Log.d("ProximitySensor",info);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//使用相同的layout
setContentView(R.layout.activity_sensor_list);
tv=(TextView) findViewById(R.id.sensor_list_text);
//返回
Button bt=(Button) findViewById(R.id.Back_to_Home);
bt.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
startActivity(new Intent(ProximitySensorActivity.this,MainActivity.class));
}
});
sensorManager=(SensorManager)getSystemService(SENSOR_SERVICE);
sensor=sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
//检查解析度和最大值,如果两者一样,说明该近距离传感器智能给出接近和远离这两个状态。
showInfo("resolution:"+sensor.getResolution());
showInfo("max value:"+sensor.getMaximumRange());
}
//// 如果activity不在前台就不需要监听,因此在onResume()注册监听器,在onPause()中注销监听器
@Override
protected void onResume() {
sensorManager.registerListener(this,sensor,SensorManager.SENSOR_DELAY_NORMAL);
super.onResume();
}
@Override
protected void onPause() {
sensorManager.unregisterListener(this,sensor);
super.onPause();
}
//对于近距离传感器,有效数值存放在values[0]中的,单位为cm。
@Override
public void onSensorChanged(SensorEvent event) {
showInfo("传感器事件:"+event.sensor.getName()+""+event.values[0]);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
showInfo(sensor.getName()+"accuracy changed:"+accuracy);
}
}
结果如下图所示
温度传感器
温度传感器的读数同样在values[0],单位为摄氏度。TYPE_TEMPERATURE读取的是设备内部的温度,例如电池的温度,而TYPE_AMBIENT_TEMPERATURE读的是设备外部环境的温度。没找到支持温度传感器的手机,就不提供相关小例子了。
气压传感器
气压传感器(Pressuer Sensor)用于天气预测或者测量海拔高度,不要将之与触摸屏的手指压力向混淆,这里测量的是大气压力,单位为hPa(millibar),即百帕。在values[0]中读取。
陀螺仪
陀螺仪(Gyroscope sensor)测量设备转动的角速度。最早的陀螺仪发明在中国,科学应用则在西方,陀螺仪是为士大夫坐轿子看书是免收烛光摇曳发明的,这在很久之前一部西方拍的科教片看到,具体名字忘了。Pro Android 4.0中说陀螺仪的误差会慢慢积累,因此通与加速传感器一致使用,通过Kalman filter进行修正。接下来只简单地进行陀螺仪数据的读取。
package com.example.sensortest;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
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.view.View;
import android.widget.Button;
import android.widget.TextView;
public class GyroscopeSensorActivity extends AppCompatActivity implements SensorEventListener {
private SensorManager sensorManager = null;
private Sensor sensor = null;
private TextView tv = null;
//在华为P6的机器上,陀螺仪非常敏感,平放在桌面,由于电脑照成的轻微震动在不断地刷屏,为了避免写UI造成的性能问题,可以只写Log。
private void showInfo(String info){
tv.append("\n" + info);
Log.d("陀螺仪",info);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//使用相同的layout
setContentView(R.layout.activity_sensor_list);
tv = (TextView)findViewById(R.id.sensor_list_text);
//返回主页
Button bt=(Button) findViewById(R.id.Back_to_Home);
bt.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
startActivity(new Intent(GyroscopeSensorActivity.this,MainActivity.class));
}
});
sensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
showInfo("resolution is " + sensor.getResolution());
}
//在onResume()注册监听器,在onPause()中注销监听器
@Override
protected void onResume() {
sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_UI);
super.onResume();
}
@Override
protected void onPause() {
sensorManager.unregisterListener(this,sensor);
super.onPause();
}
//重写的方法
//对于陀螺仪,测量的是x、y、z三个轴向的角速度,分别从values[0]、values[1]、values[2]中读取,单位为弧度/秒。
@Override
public void onSensorChanged(SensorEvent event) {
if(event.sensor.getType() == Sensor.TYPE_GYROSCOPE)
showInfo("传感器测量结果:" + " x:" + event.values[0] + " y:" + event.values[1]
+ " z:" + event.values[2]);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
showInfo(sensor.getName() + " accuracy changed: " + accuracy);
}
}
加速传感器
加速度测量传感器有x、y、z三轴,注意和2D屏幕的座标,以左上角作为原点的,而且Y向下。注意区分这两个不同的座标系。
加速传感器的单位是加速度m/s2。如果手机平放好,x,y在位置为0,而z轴方向加速度=当前z方向加速度-g。由于g(重力加速度)垂直向下,则g=-9.81m/s2,即z轴 a=0-(-9.81)=9.81m/s2。也就是自由落体是为0。x、y、z的测量的加速度分别位于value[0]、value[1]、value[2]。
传感器类型为Sensor.TYPE_ACCELEROMETER。减去重力这很重要,当设备静止或者匀速运动时,可以获得设备的角度。
package com.example.sensortest;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.Surface;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TextView;
public class AccelerometerSensorActivity extends AppCompatActivity implements SensorEventListener {
private SensorManager sensorManager = null;
private Sensor sensor = null,gravitySensor = null,linearAcceleSensor = null;
private TextView tv = null;
private WindowManager window = null;
// private void showInfo(String info){
// tv.append("\n" + info);
// Log.d("加速度仪",info);
// }
private void showInfo1(String info){
tv.setText(info + "\n" + tv.getText());
tv.append("\n" + info);
Log.d("加速度仪",info);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//使用相同的layout
setContentView(R.layout.activity_sensor_list);
tv = (TextView)findViewById(R.id.sensor_list_text);
//返回主页
Button bt=(Button) findViewById(R.id.Back_to_Home);
bt.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
startActivity(new Intent(AccelerometerSensorActivity.this,MainActivity.class));
}
});
//定义传感器管理器
sensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
gravitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY);//重力传感器(获取重力传感器的值的目标是减去重力)
linearAcceleSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION);//线性加速度
showInfo1("resolution is " + sensor.getResolution());
showInfo1("API为" + Build.VERSION.SDK_INT);
window = (WindowManager)getSystemService(WINDOW_SERVICE);
//返回值为Surface.ROTATION_0(0)、Surface.ROTATION_90(1)、Surface.ROTATION_180(2)和Surface.ROTATION_270(3),
//可以用来确定屏幕UI的旋转方向。
// 注意:需要开启“自动旋转”才能有效检查,否则均为Surface.ROTATION_0(手机以竖屏为主,一般都会0,但不保证都如此)。
// 不是所以的手机都能检测到这4个值,例如P6,没有Surface.ROTATION_180,即UI不支持倒过来,如果有某个数值不支持,通过getRotation()获取的数值可能并不准确,仍以P6为例,如果我们顺时针转90°,得到Surface.ROTATION_90,继续顺时针转至180°,无检测新数值,仍未Surface.ROTATION_90,再继续顺时针转90°(至270°),仍显示为Surface.ROTATION_90,而非Surface.ROTATION_270。
showRotation();//展示旋转角
}
//在onResume()注册监听器,在onPause()中注销监听器
@Override
protected void onResume() {
sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_UI);
sensorManager.registerListener(this, gravitySensor, SensorManager.SENSOR_DELAY_UI);
sensorManager.registerListener(this, linearAcceleSensor, SensorManager.SENSOR_DELAY_UI);
super.onResume();
}
@Override
protected void onPause() {
sensorManager.unregisterListener(this);
super.onPause();
}
private int count = 1;
// 对于加速器,测量的是x、y、z三个轴向的加速度,分别从values[0]、values[1]、values[2]中读取。
@Override
public void onSensorChanged(SensorEvent event) {
if (count++%40==0){//还不到40个计数时
int type = event.sensor.getType();
if(type == Sensor.TYPE_ACCELEROMETER){
showInfo1("加速器:" + " x:" + event.values[0] + " y:" + event.values[1]+ " z:" + event.values[2]);
}else if(type == Sensor.TYPE_GRAVITY){
showInfo1("重力仪:" + " x:" + event.values[0] + " y:" + event.values[1]+ " z:" + event.values[2]);
}else if(type == Sensor.TYPE_LINEAR_ACCELERATION){
showInfo1("线性加速仪" + " x:" + event.values[0] + " y:" + event.values[1] + " z:" + event.values[2]);
}
count = 1;
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// TODO Auto-generated method stub
showInfo1(sensor.getName() + " accuracy changed: " + accuracy);
}
//将角度显示出来
private void showRotation(){
int rotation = window.getDefaultDisplay().getRotation();//获取旋转的方向
switch(rotation){
case Surface.ROTATION_0:
showInfo1("方向:ROTATION 0(" + rotation + ")");
break;
case Surface.ROTATION_90:
showInfo1("方向:ROTATION 90(" + rotation + ")");
break;
case Surface.ROTATION_180:
showInfo1("方向:ROTATION 180(" + rotation + ")");
break;
case Surface.ROTATION_270:
showInfo1("方向:ROTATION 270(" + rotation + ")");
break;
default:
showInfo1("方向:(" + rotation + ")");
break;
}
}
}
重力测试
package com.example.sensortest;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
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.view.View;
import android.widget.Button;
import android.widget.TextView;
public class GravityActivity extends AppCompatActivity implements SensorEventListener {
private SensorManager sensorManager = null;
private Sensor sensor = null;
private TextView tv = null;
// private void showInfo(String info){
// tv.append("\n" + info);
// Log.d("重力",info);
// }
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//使用相同的layout
setContentView(R.layout.activity_sensor_list);
tv = (TextView)findViewById(R.id.sensor_list_text);
//返回主页
Button bt=(Button) findViewById(R.id.Back_to_Home);
bt.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
startActivity(new Intent(GravityActivity.this,MainActivity.class));
}
});
sensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
}
//在onResume()注册监听器,在onPause()中注销监听器
@Override
protected void onResume() {
// TODO Auto-generated method stub
super.onResume();
sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_UI);
}
@Override
protected void onPause() {
sensorManager.unregisterListener(this,sensor);
super.onPause();
}
/////////////////////////////////////////////////
private float[] gravity = new float[3];
private float[] motion = new float[3];
private double ratioY;
private double angle;
private int counter = 1;
@Override
public void onSensorChanged(SensorEvent event) {
for (int i=0;i<3;i++){
//accelermeter是很敏感的,看之前小例子的log就知道。
// 因为重力是恒力,我们移动设备,它的变化不会太快,不象摇晃手机这样的外力那样突然
// 因此通过low-pass filter对重力进行过滤
gravity[i] = (float) (0.1 * event.values[i] + 0.9 * gravity[i]);
motion[i] = event.values[i] - gravity[i];
}
//计算重力在Y轴方向的量,即G*cos(α)
ratioY = gravity[1]/SensorManager.GRAVITY_EARTH;
if(ratioY > 1.0)
ratioY = 1.0;
if(ratioY < -1.0)
ratioY = -1.0;
//获得α的值,根据z轴的方向修正正负值。
angle = Math.toDegrees(Math.acos(ratioY));
if(gravity[2] < 0)
angle = - angle;
//避免扫屏,每10次变化显示一次值
if(counter ++ % 10 == 0){
tv.setText("Raw Values : \n"
+ " x,y,z = "+ event.values[0] + "," + event.values[1] + "," + event.values[2] + "\n"
+ "Gravity values : \n"
+ " x,y,z = "+ gravity[0] + "," + gravity[1] + "," + gravity[2] + "\n"
+ "Motion values : \n"
+ " x,y,z = "+ motion[0] + "," + motion[1] + "," + motion[2] + "\n"
+ "Y轴角度 :" + angle );
tv.invalidate();
counter = 1;
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// showInfo(sensor.getName() + " accuracy changed: " + accuracy);
}
}
磁场传感器
磁场传感器可以用来检测磁场大小,和加速度传感器一样,有x、y、z轴三个方向,单位为uT(microteslas)。磁场传感器也称为compass(指南针),在uses-feature中使用android.hardware.sensor.compass作为其名字。
下面代码可以获得磁场的值
package com.example.sensortest;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
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.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MagneticFieldSensorActivity extends AppCompatActivity implements SensorEventListener {
private SensorManager sensorManager = null;
private Sensor sensor = null;
private TextView tv = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//使用相同的layout
setContentView(R.layout.activity_sensor_list);
tv = (TextView)findViewById(R.id.sensor_list_text);
//返回主页
Button bt=(Button) findViewById(R.id.Back_to_Home);
bt.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
startActivity(new Intent(MagneticFieldSensorActivity.this,MainActivity.class));
}
});
sensorManager=(SensorManager)getSystemService(SENSOR_SERVICE);
sensor=sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
}
//在onResume()注册监听器,在onPause()中注销监听器
protected void onPause() {
// TODO Auto-generated method stub
sensorManager.unregisterListener(this, sensor);
super.onPause();
}
@Override
protected void onResume() {
sensorManager.registerListener(this, sensor,SensorManager.SENSOR_DELAY_UI);
super.onResume();
}
private int count = 1;
@Override
public void onSensorChanged(SensorEvent event) {
if (count++==20){//由于不稳定,所以每20次,再输出一次
double value = Math.sqrt(event.values[0]*event.values[0] + event.values[1]*event.values[1]
+event.values[2]*event.values[2]);
String str = String.format("X:%8.4f , Y:%8.4f , Z:%8.4f \n总值为:%8.4f", event.values[0],event.values[1],event.values[2],value);
count = 1;
tv.setText(str);
Log.d("磁场感应器",str);
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
}
方位传感器(Orientation Sensors)
磁场传感器和加速度传感器结合可获得设备摆放角度,两者结合可以获取方位。这些计算由Android代劳了,SensorManager提供getRotationMatrix(),获得转动的矩阵,并进一步通过getOrientation()获得方位矩阵。Android还有方位传感器(orientation Sensors),不是物理实体,而是通过acceleration传感器和磁场感应器来获取方位,而在Android2.2开始方位传感器就被deprecated了。
设备摆放情况通过azimuth、pitch和roll来表示。
azimuth即方位角,就是手机方向和正北的夹角,百度百科这样描述方位角:是从某点的指北方向线起,依顺时针方向到目标方向线之间的水平夹角。pitch和roll可能是引用了航天的术语。azimuth,pitch和roll分别是z轴、X轴和Y轴的旋转角度。
老方法采用orientation传感器,azimuth从0~360。pitch是在x轴方向的转动角度,其实就是Y轴和水平面的仰角,范围为-180~180,正的为朝下(手机头低于水平面),负的为朝上,pitch的方向逆时针为正。roll是Y轴防线的转动角度,实际就是X轴和水平面的角度,范围在-90~90,同样逆时针为正,右轴高于左轴时为正,右轴高于左轴时为负。
新的方法,azimuth的范围是-180~180,当然我们可以进行适当的处理,如果小于零,则加360,这样就可以和orientation的得到的值一样。需要注意的是pitch的范围是-90~90,机头上翘为负;roll的范围是-180~180,和老方法相反,右轴高时为负。
这两种方式的数值可能会使人有些迷糊,我们在使用之前先查文档弄清楚就是了。我曾经有个疑惑为何有两个数值的范围是360度,其中一个数值只有180度。想想球面就知道了,以地球仪为例,经度范围是360°,维度范围是180°,就可以确定球面上的任何一点,以球心到该点假设是手机中轴线,还有一个围绕Y轴360°转的角度的第三维,这就可以确定所有的排放方式。
package com.example.sensortest;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.GeomagneticField;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.Surface;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ToggleButton;
public class VirtualJax extends AppCompatActivity implements SensorEventListener {
// private ToggleButton toggleButton = null;//一个可选的按钮选项
private TextView oldOne = null, nowOne = null;//两种不同的方法获得方向数据
private SensorManager sensorManager = null;
private Sensor accelSensor = null, compassSensor = null, orientSensor = null, rotVecSensor = null;
//定义数组将方向传感器的数据放于数组中
private float[] accelValues = new float[3], compassValues = new float[3], orientValues = new float[3], rotVecValues = null;
private int mRotation;
private LocationManager locManager = null;
@SuppressWarnings("deprecation")//表示不检测过期的方法
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_virtual_jax);//跟之前的几个activity不同,这里定义了新的activity
//返回主页
Button bt=(Button) findViewById(R.id.Back_to_Home);
bt.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
startActivity(new Intent(VirtualJax.this,MainActivity.class));
}
});
oldOne = (TextView) findViewById(R.id.orientation);//采用orientation传感器
nowOne = (TextView) findViewById(R.id.preferred);//推荐的方式
// toggleButton = (ToggleButton) findViewById(R.id.toggle);
//定义传感器管理器
sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
//定义四个传感器
accelSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);//加速度传感器
compassSensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);//地磁传感器
orientSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);//方位传感器
rotVecSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);//旋转角度传感器???
// //当横屏的时候应该对座标进行修订
// WindowManager window = (WindowManager) this.getSystemService(WINDOW_SERVICE);
// //检验当前的版本号
// if (Build.VERSION.SDK_INT < 8)
// mRotation = window.getDefaultDisplay().getOrientation();
// else
// mRotation = window.getDefaultDisplay().getRotation();
//
// locManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
}
//在onResume()注册监听器,在onPause()中注销监听器
@Override
protected void onResume() {
// TODO Auto-generated method stub
// isAllowRemap = toggleButton.isChecked();
sensorManager.registerListener(this, accelSensor, SensorManager.SENSOR_DELAY_GAME);
sensorManager.registerListener(this, compassSensor, SensorManager.SENSOR_DELAY_GAME);
sensorManager.registerListener(this, orientSensor, SensorManager.SENSOR_DELAY_GAME);
sensorManager.registerListener(this, rotVecSensor, SensorManager.SENSOR_DELAY_GAME);
super.onResume();
}
@Override
protected void onPause() {
// TODO Auto-generated method stub
sensorManager.unregisterListener(this, accelSensor);
sensorManager.unregisterListener(this, compassSensor);
sensorManager.unregisterListener(this, orientSensor);
sensorManager.unregisterListener(this, rotVecSensor);
super.onPause();
}
///////////////////////////////////////////////////////********************************//////////////////////////////
private boolean ready = false; //检查是否同时具有加速度传感器和磁场传感器
private float[] inR = new float[9], outR = new float[9];
private float[] inclineMatrix = new float[9];
private float[] prefValues = new float[3];
private double mInclination;
private int count = 1;
private float[] rotvecR = new float[9], rotQ = new float[4];
private float[] rotvecOrientValues = new float[3];
@SuppressWarnings("deprecation")//表示不检测过期的方法
@Override
public void onSensorChanged(SensorEvent event) {
// TODO Auto-generated method stub
//将相关传感器的数值分别读入accelValues,compassValues(磁力感应器的数值)和orientValues和rotVecValues数组中
switch (event.sensor.getType()) {//获取传感器的类型
case Sensor.TYPE_ACCELEROMETER://当时加速度传感器时
for (int i = 0; i < 3; i++) {
accelValues[i] = event.values[i];//将三个值分别放于accelValues中
}
if (compassValues[0] != 0) //即accelerator和magnetic传感器都有数值
ready = true;//此时检测同时具有加速度传感器与地磁传感器
break;
case Sensor.TYPE_MAGNETIC_FIELD://获取地磁传感器的值
for (int i = 0; i < 3; i++) {
compassValues[i] = event.values[i];//将三个值分别放于compassValues中
}
if (accelValues[2] != 0) //即accelerator和magnetic传感器都有数值,换一个轴向检查
ready = true;//此时检测同时具有加速度传感器与地磁传感器
break;
case Sensor.TYPE_ORIENTATION://如果是方向传感器
for (int i = 0; i < 3; i++) {
orientValues[i] = event.values[i];//将三个值分别放于orientValues中
}
break;
case Sensor.TYPE_ROTATION_VECTOR://对于旋转传感器
if (rotVecValues == null) {
rotVecValues = new float[event.values.length];
}
for (int i = 0; i < rotVecValues.length; i++) {
rotVecValues[i] = event.values[i];
}
break;
}
if (!ready)//此时如果没有有加速度与地磁传感器,则退出返回
return;
//计算:inclination matrix 倾角矩阵 I(inclineMatrix) 以及 the rotation matrix 旋转矩阵 R(inR)
//根据加速传感器的数值accelValues[3]和磁力感应器的数值compassValues[3],进行矩阵计算,获得方位
if (SensorManager.getRotationMatrix(inR, inclineMatrix, accelValues, compassValues)) {
//下面是旋转屏幕的情况,此处不用
// if (isAllowRemap && mRotation == Surface.ROTATION_90) {
// //参数二表示设备X轴成为新座标的Y轴,参数三表示设备的Y轴成为新座标-x轴(方向相反)
// SensorManager.remapCoordinateSystem(inR, SensorManager.AXIS_Y, SensorManager.AXIS_MINUS_X, outR);
// SensorManager.getOrientation(outR, prefValues);
// } else {
/* Computes the device's orientation based on the rotation matrix.
* When it returns, the array values is filled with the result:
* 根据rotation matrix计算设备的方位。,范围数组:
values[0]: azimuth, rotation around the Z axis.
values[1]: pitch, rotation around the X axis.
values[2]: roll, rotation around the Y axis.*/
SensorManager.getOrientation(inR, prefValues);//根据rotation matrix计算设备的方位
// }
//根据inclination matrix计算磁仰角。
//计算磁仰角:地球表面任一点的地磁场总强度的矢量方向与水平面的夹角。
mInclination = SensorManager.getInclination(inclineMatrix);
//显示测量值
if (count++ % 100 == 0) {
doUpdate(null);
count = 1;
}
} else {
Toast.makeText(this, "无法获得矩阵(SensorManager.getRotationMatrix)", Toast.LENGTH_LONG);
finish();
}
if (rotVecValues != null) {
SensorManager.getQuaternionFromVector(rotQ, rotVecValues);
SensorManager.getRotationMatrixFromVector(rotvecR, rotVecValues);
SensorManager.getOrientation(rotvecR, rotvecOrientValues);
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// TODO Auto-generated method stub
}
//定义一个函数来显示测量值
public void doUpdate(View v) {
if (!ready)
return;
//preValues[0]是方位角,范围是-pi到pi,通过Math.toDegrees转换为角度
float mAzimuth = (float) Math.toDegrees(prefValues[0]);//方角位,地平经度
/*//纠正为orientation的数值。
* if(mAzimuth < 0)
mAzimuth += 360.0;*/
String msg = String.format("Acceleration sensor + magnetic sensor:\nazimuth:%7.3f\npitch: %7.3f\nroll: %7.3f\n地磁仰角:%7.3f\n重适配座标=%s\n%s\n",
mAzimuth, Math.toDegrees(prefValues[1]), Math.toDegrees(prefValues[2]),
Math.toDegrees(mInclination),
(isAllowRemap && mRotation == Surface.ROTATION_90) ? "true" : "false", info);
if (rotvecOrientValues != null && mRotation == Surface.ROTATION_0) {
msg += String.format("Rotation Vector Sensor:\nazimuth %7.3f\npitch %7.3f\nroll %7.3f\nw,x,y,z %6.2f,%6.2f,%6.2f,%6.2f\n",
Math.toDegrees(rotvecOrientValues[0]),
Math.toDegrees(rotvecOrientValues[1]),
Math.toDegrees(rotvecOrientValues[2]),
rotQ[0], rotQ[1], rotQ[2], rotQ[3]);
//Log.d("WEI","Quaternion w,x,y,z=" + rotQ[0] + "," + rotQ[1] + "," + rotQ[2] + "," + rotQ[3]);
}
nowOne.setText(msg);
msg = String.format("Orientation Sensor:\nazimuth:%7.3f\npitch: %7.3f\nroll: %7.3f",
orientValues[0], orientValues[1], orientValues[2]);
oldOne.setText(msg);
}
private boolean isAllowRemap = false;
public void doToggle(View v) {
isAllowRemap = ((ToggleButton) v).isChecked();
}
private String info = "";
public void doGeoNorth(View v) {
if (!ready)
return;
String providerName = locManager.getBestProvider(new Criteria(), true);
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
}
Location loc = locManager.getLastKnownLocation(providerName);
if(loc == null && locManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)){
//请注意,设备要打开网络定位的选项。在室内,由于不容易搜索到GPS,建议采用network方式。
//否则,locManager.isProviderEnabled("network")为false,不能使用网络方式,而GPS在室内搜半天卫星都不一定有
providerName = LocationManager.NETWORK_PROVIDER;
loc = locManager.getLastKnownLocation(providerName);
}
if(loc == null)
return;
info = "定位:"+ providerName+ "\n"+ String.format(" %9.5f,%9.5f",(float)loc.getLongitude(),(float)loc.getLatitude())+"\n";
Log.d("WEI","" + loc);
GeomagneticField geo = new GeomagneticField((float)loc.getLatitude(),(float)loc.getLongitude(),
(float)loc.getAltitude(),System.currentTimeMillis());
float declination = geo.getDeclination();
info += String.format("磁偏角:%7.3f\n", declination);
}
}
参考资料
https://blog.csdn.net/fukaimei/article/details/78321851
https://www.csdn.net/gather_2f/MtTakg5sMDkzNi1ibG9n.html
https://blog.csdn.net/flowingflying/article/details/6212512(主要参考代码)