浅析Android中的传感器开发(黄汉煜 1501210915)

浅析Android中的传感器开发

作者:黄汉煜 学号:1501210915

摘要:安卓中基于传感器的开发是安卓系统特有的开发形式,本文简要介绍了传感器开发的模式,列举了常用的传感器类型,并利用光照传感器、加速度传感器、地磁传感器实现了一个简易的指南针。

关键词:安卓、传感器、指南针、摇一摇

正文

说起移动开发中的特色开发技术,许多人的第一反应便是基于gps的位置服务,实际上,基于传感器的编程也是当前移动开发中极具特色的一环。所谓传感器,指的是,手机中的一种微型硬件设备,例如:光照传感器、加速度传感器、地磁传感器、压力传感器、温度传感器等。不同的传感器往往具有不同的规格与功能,但是,通过android系统提供的抽象接口,我们可以轻松的读取传感器提供的信息,在许多手机游戏中使用的重力感应技术、微信中的摇一摇等诸多有趣的应用都是基于传感器完成的。

一、 传感器编程的基本用法

Android中的传感器开发时用法大同小异,基本框架是相同的,可以分为以下几步: 首先,获取SensorManager的实例,这是所有传感器的管理器,有了它之后,我们就可以访问传感器类型:

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

然后,在该实例上通过调用getDefaultSensor()来获得任意的传感器类型,其参数为具体的传感器类型,我们一光照传感器为例:

Sensor sensor=sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);

接下来,对传感器的输出信号进行监听,借助接口SensorEventListener实现,该接口含两种方法,onSensorChanged()和 onAccuracyChanged()。当传感器的监测数值变化时,调用前者,前者的方法中含SensorEvent参数,该参数含一个values数组,记录了了输出的具体信息,其内容与监听的传感器类型有关。而当传感器精度发生变化时,我们调用后者。

SensorEventListener listener=new SensorEventListener()           {
            @Override
            public void onSensorChanged(SensorEvent event) {

            }

            @Override
            public void onAccuracyChanged(Sensor sensor, int accuracy) {

            }
        };

此后,我们需要调用SensorManager的 registerListener()方法来注册 SensorEventListener, registerListener()方法接收三个参数,第一个参数就是 SensorEventListener的实例,第二个参数是Sensor的实例,第三个参数是用于表示传感器输出信息的更新速率, 共有SENSOR_DELAY_UI、 SENSOR_DELAY_NORMAL、SENSOR_DELAY_GAME 和 SENSOR_DELAY_FASTEST 这四种值可选, 它们的更新速率是依次递增的。

sensorManager.registerListener
(listener,sensor,SensorManager.SENSOR_DELAY_NORMAL);

最后,当传感器使用完毕时,调用unregisterListener ()方法将使用的资源释放掉:

 sensorManager.unregisterListener(listener);

二、 获取传感器类型

自android1.5开始,安卓便内置了八种传感器的支持,到了android4.4中,更是有多达20种的内置传感器,但是,并非所有的手机都支持如此多得独特硬件,我们列举常见的8种传感器,并通过一个简单的小程序来遍历当前手机中的传感器。

首先,我们在布局文件中加入textview以显示本机的情况:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

    <TextView android:id="@+id/textview01" android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</RelativeLayout>

然后,我们在mainActivi中键入如下代码:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    final TextView textView=(TextView) findViewById(R.id.textview01);
    SensorManager sensorManager=(SensorManager) getSystemService(Context.SENSOR_SERVICE);
    List<Sensor> allSensors=sensorManager.getSensorList(Sensor.TYPE_ALL);
    textView.setText("经检测该手机有" + allSensors.size() + "个传感器,他们分别是:\n");
    for(Sensor s:allSensors)
    {
        String tempString = "\n" + "  设备名称:" + s.getName() + "\n" + "  设备版本:" + s.getVersion() + "\n" + "  供应商:"
                +s.getVendor()+"\n";
        switch (s.getType())
        {
            case Sensor.TYPE_ACCELEROMETER:
                textView.setText(textView.getText().toString()+s.getType()+" 加速度传感器accelerometer" + tempString);
                break;
            case Sensor.TYPE_GYROSCOPE:
                textView.setText(textView.getText().toString()+s.getType()+" 陀螺仪传感器gyroscope" + tempString);
                break;
            case Sensor.TYPE_LIGHT:
                textView.setText(textView.getText().toString()+s.getType()+" 环境光线传感器light" + tempString);
                break;
            case Sensor.TYPE_MAGNETIC_FIELD:
                textView.setText(textView.getText().toString()+s.getType()+" 电磁场传感器magnetic field" + tempString);
                break;
            case Sensor.TYPE_PRESSURE:
                textView.setText(textView.getText().toString()+s.getType()+" 压力传感器pressure" + tempString);
                break;
            case Sensor.TYPE_PROXIMITY:
                textView.setText(textView.getText().toString()+s.getType()+" 距离传感器proximity" + tempString);
                break;

            default:
                textView.setText(textView.getText().toString() + s.getType() + " 未知传感器" + tempString);
                break;
        }
    }
}

在虚拟机上运行效果如下,在具体手机上运行的结果不同,例如在笔者的实体机上运行时,结果就明显不同:

三、 具体传感器实例

在前两个部分中,我们大致介绍了安卓传感器开发上的一些注意事项,现在我们决定着手开发一个简单的指南针,并在其中加入“摇一摇”的功能,以显示当前的光照强度,其中共需使用“方向传感器”、加速度传感器与光照传感器三种常见的传感器。

首先,光照传感器部分最为简单,只需注意光照改变时SensorEvent中values数组内仅含一个当前光照强度的数值,我们用一个变量存储该值,当环境变化时,修改之即可。

其次,加速度传感器稍微复杂一点,其values数组含三个值,分别代表手机在 X轴、 Y轴和 Z 轴方向上的加速度信息。X轴、Y轴、Z轴在空间坐标系上的含义如图所示。特别需要注意的是由于地心引力的作用,手机静止时某个轴必然受到9.8的加速度,我们为触发摇一摇,只需假定某个轴上出现超过预设值的加速度即可,不妨设预设值为15,若需调节敏感度,可自行设定:

最后,方向传感器有两种解决方案。可以沿用前两种传感器的模式,调用Sensor.TYPE_ORIENTATION 这种传感器类型,其values数组会记录手机在所有方向上的旋转角度,看似简单,并且实际上有效,但是,android现在已废弃Sensor.TYPE_ORIENTATION。因此,我们推荐使用加速度传感器和地磁传感器共同计算得出方向。由于方向传感器的精确度要求通常都比较高, 这里我们把传感器输出信息的更新速率提高了一些,使用的是 SENSOR_DELAY_GAME。

        sensorManager=(SensorManager) getSystemService(Context.SENSOR_SERVICE);
        Sensor magneticSensor=sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
        Sensor accelerometerSensor=sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        sensorManager.registerListener(listener,magneticSensor,SensorManager.SENSOR_DELAY_GAME);
        sensorManager.registerListener(listener,accelerometerSensor,SensorManager.SENSOR_DELAY_GAME);

接下来在 onSensorChanged()方法中可以获取到 SensorEvent的 values数组,分别记录着加 速 度 传 感 器 和 地 磁 传 感 器 输 出 的 值 。 然 后 将 这 两 个 值 传 入 到 SensorManager 的getRotationMatrix()方法中就可以得到一个包含旋转矩阵的 R数组。

SensorManager.getRotationMatrix(R, null, accelerometerValues, magneticValues);

其中第一个参数 R 是一个长度为 9 的 float 数组,getRotationMatrix()方法计算出的旋转数据就会赋值到这个数组当中。 第二个参数是一个用于将地磁向量转换成重力坐标的旋转矩阵,通常指定为 null即可。第三和第四个参数则分别就是加速度传感器和地磁传感器输出的values值。 然后,调用SensorManager 的 getOrientation()方法来计算手机的旋转数据,values是一个长度为 3的 float数组, 手机在各个方向上的旋转数据都会被存放到这个数组当中。其中 values[0]记录着手机围绕着Z 轴的旋转弧度,values[1]记录着手机围绕 X轴的旋转弧度,values[2]记录着手机围绕 Y轴的旋转弧度。

SensorManager.getOrientation(R, values);

最后,注意这里计算出的数据都是以弧度为单位的, 因此如果你想将它们转换成角度还需要调用如下方法:

(float) Math.toDegrees(values[0])

四、 编程实践

首先,我们先实现获取角度(即“方向传感器”)的功能,键入如下代码:

private SensorManager sensorManager;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    sensorManager=(SensorManager) getSystemService(Context.SENSOR_SERVICE);
    Sensor magneticSensor=sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
    Sensor accelerometerSensor=sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
    sensorManager.registerListener(listener,magneticSensor,SensorManager.SENSOR_DELAY_GAME);
    sensorManager.registerListener(listener,accelerometerSensor,SensorManager.SENSOR_DELAY_GAME);
}
@Override
protected void onDestroy()
{
    super.onDestroy();
    if(sensorManager!=null)
    {
        sensorManager.unregisterListener(listener);
    }
}
private SensorEventListener listener=new SensorEventListener() {
    float[] accelerometerValues=new float[3];
    float[] magneticValues=new float[3];
    @Override
    public void onSensorChanged(SensorEvent event) {
        if(event.sensor.getType()==Sensor.TYPE_ACCELEROMETER)
        {
            accelerometerValues=event.values.clone();
        }
        else if (event.sensor.getType()==Sensor.TYPE_MAGNETIC_FIELD)
        {
            magneticValues=event.values.clone();
        }
        float[] R = new float[9];
        float[] values = new float[3];
        SensorManager.getRotationMatrix(R, null, accelerometerValues,
                magneticValues);
        SensorManager.getOrientation(R, values);
        Log.d("MainActivity", "value[0] is " + Math.toDegrees(values[0]));
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {

    }
};

旋转手机,可以得到旋转的角度:

然后,我们实现指南针的gui部分,先准备两张图片,分别表示罗盘与指针:

在activity_main中插入:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

    <ImageView
        android:id="@+id/compass_img"
        android:layout_width="250dp"
        android:layout_height="250dp"
        android:layout_centerInParent="true"
        android:src="@drawable/compass" />
    <ImageView
        android:id="@+id/arrow_img"
        android:layout_width="60dp"
        android:layout_height="110dp"
        android:layout_centerInParent="true"
        android:src="@drawable/arrow" />

</RelativeLayout>

再修改MainActivity中的代码,通过旋转罗盘,实现指南针的功能。

private ImageView compassImg;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    compassImg=(ImageView)findViewById(R.id.compass_img);
……
}
……
private SensorEventListener listener=new SensorEventListener() {
    float[] accelerometerValues=new float[3];
    float[] magneticValues=new float[3];
    float lastRotateDegree;
    @Override
    public void onSensorChanged(SensorEvent event) {
        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
            accelerometerValues = event.values.clone();
        } else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
            magneticValues = event.values.clone();
        }
        float[] R = new float[9];
        float[] values = new float[3];
        SensorManager.getRotationMatrix(R, null, accelerometerValues,
                magneticValues);
        SensorManager.getOrientation(R, values);
        Log.d("MainActivity", "value[0] is " + Math.toDegrees(values[0]));
        float rotateDegree = -(float) Math.toDegrees(values[0]);
        if (Math.abs(rotateDegree - lastRotateDegree) > 1) {
            RotateAnimation animation = new RotateAnimation
                    (lastRotateDegree, rotateDegree, Animation.RELATIVE_TO_SELF, 0.5f, Animation.
                            RELATIVE_TO_SELF, 0.5f);
            animation.setFillAfter(true);
            compassImg.startAnimation(animation);
            lastRotateDegree = rotateDegree;
        }
    }
    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {

    }
};

当前结果如下:

下面,我们再加入摇一摇的功能:

private float lightLevel;
@Override
protected void onCreate(Bundle savedInstanceState) {
……
Sensor lightSensor=sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
sensorManager.registerListener(listener,accelerometerSensor,SensorManager.SENSOR_DELAY_NORMAL);
…….
}
private SensorEventListener listener=new SensorEventListener() {
    float[] accelerometerValues=new float[3];
    float[] magneticValues=new float[3];
    float lastRotateDegree;
    @Override
    public void onSensorChanged(SensorEvent event) {
…….
else if(event.sensor.getType()==Sensor.TYPE_LIGHT)
{
    lightLevel=event.values[0];
}
if(accelerometerValues[0]>15 ||accelerometerValues[1]>15||accelerometerValues[2]>15)
{
    Toast.makeText(MainActivity.this,"当前光照强度为"+lightLevel,Toast.LENGTH_SHORT).show();
}

至此,我们完成了一次基本的传感器开发案例。

参考文献:

[1] 郭霖. 第一行代码——Android. 人民邮电出版社,2014

[2]汪永松.Android平台开发之旅[M].北京:机械工业出版社,2010.7

[3] http://www.jizhuomi.com/android/course/258.html

results matching ""

    No results matching ""