传感器是Android用来感知周围环境以及运动信息的工具。因为具体的感应信息依赖于相关硬件,所以虽然Android提供了众多的感应器,但不是每部手机都能支持这么多感应器,恰恰相反,大多数安卓手机仅仅支持包括加速度在内的少数几个感应器。 传感器借助于硬件来监听环境改变的事件,从这个意义上来说,Android的事件都是由某个传感器触发,只不过这个触发来源可能是软件,也可能是屏幕,甚至可能是手机的sim卡。回顾一下之前的事件通信章节,我们会发现,原来它们在本质上跟传感器是类似的,比如说: 1、软件感应:UI事件(参见《Android开发笔记(四十四)动态UI事件》)、媒体播放事件(参见《Android开发笔记(五十七)录像录音与播放》)、浏览器加载、交互与下载事件(参见《Android开发笔记(六十四)网页加载与JS调用》)。 2、屏幕感应:点击事件(参见《Android开发笔记(四十三)点击事件》)、手势事件(参见《Android开发笔记(四十五)手势事件》)、拖动条的拖动事件(参见《Android开发笔记(五十八)铃声与震动》)。 3、sim卡感应:手机相关事件(参见《Android开发笔记(四十六)手机相关事件》)。 4、摄像头感应:拍照事件(参见《Android开发笔记(五十六)摄像头拍照》)。 5、麦克风感应:录音事件(参见《Android开发笔记(五十七)录像录音与播放》)。 6、系统感应:电量事件、屏幕开关事件(参见《Android开发笔记(一百一十七)app省电方略》)。 下面是目前Android支持的感应器类型: 1 TYPE_ACCELEROMETER //加速度 2 TYPE_MAGNETIC_FIELD //磁场 3 TYPE_ORIENTATION //方向,该类型已弃用,取而代之的是getOrientation方法 4 TYPE_GYROSCOPE //陀螺仪 5 TYPE_LIGHT //光线 6 TYPE_PRESSURE //压力 7 TYPE_TEMPERATURE //温度,该类型已弃用,取而代之的是TYPE_AMBIENT_TEMPERATURE 8 TYPE_PROXIMITY //距离 9 TYPE_GRAVITY //重力 10 TYPE_LINEAR_ACCELERATION //线性加速度 11 TYPE_ROTATION_VECTOR //旋转矢量 12 TYPE_RELATIVE_HUMIDITY //湿度 13 TYPE_AMBIENT_TEMPERATURE //环境温度 14 TYPE_MAGNETIC_FIELD_UNCALIBRATED //无标定磁场 15 TYPE_GAME_ROTATION_VECTOR //无标定旋转矢量 16 TYPE_GYROSCOPE_UNCALIBRATED //未校准陀螺仪 17 TYPE_SIGNIFICANT_MOTION //特殊动作 18 TYPE_STEP_DETECTOR //步行检测,用户每走一步就触发一次事件 19 TYPE_STEP_COUNTER //计步器,记录激活后的步伐数 20 TYPE_GEOMAGNETIC_ROTATION_VECTOR //地磁旋转矢量
这个功能最有名的应用就是微信里的“摇一摇”了,用户通过摇晃手机来寻找周围的人;类似的业务还有摇奖、玩游戏等等。
下面以摇一摇的实现来演示传感器开发的步骤:
1、声明一个SensorManager对象,该对象从系统服务Context.SENSOR_SERVICE中获取实例; 2、编写一个传感器事件监听器,该监听器继承自SensorEventListener,同时需实现onSensorChanged和onAccuracyChanged两个方法。其中前一个方法在感应变化时触发,业务逻辑都在这边处理;后一个方法在精度改变时触发,一般无需处理。 3、重写onResume方法,在该方法中注册传感器监听事件,使用的是registerListener方法,该方法的第二个参数为Sensor类型,须调用SensorManager对象的getDefaultSensor来获取指定类型的传感器对象。例如摇一摇功能要注册加速度感应监听器,代码示例如下:
mSensroMgr.registerListener(this,
mSensroMgr.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
SensorManager.SENSOR_DELAY_NORMAL);
4、重写onPause方法,在该方法中注销传感器事件,代码示例如下:
mSensroMgr.unregisterListener(this);
下面是摇一摇功能的核心代码:
float[] values = event.values;
if (sensorType == Sensor.TYPE_ACCELEROMETER) {
if ((Math.abs(values[0]) > 15 || Math.abs(values[1]) > 15
|| Math.abs(values[2]) > 15)) {
tv_shake.setText(getNowDateTime()+" 恭喜您摇一摇啦");
//系统检测到摇一摇事件后,震动手机提示用户
mVibrator.vibrate(500);
}
}
博主的手机比较廉价,支持的感应器不多,除了加速度之外,就只有光线与距离感应器了。不过很奇怪,距离感应器只能探测到0-1厘米的距离,不会探测到2厘米以上的距离。于是好好琢磨了下,发现只有遮挡手机上面扬声器与前置摄像头所在的位置,距离感应才会变化,遮挡屏幕其余地方,距离感应并无变化。同样的,光线感应也是如此,把手机上部遮住,光线强度一下就降得很低。据此,我推测,光线与距离很可能是依靠前置摄像头来感应,所以一旦遮住前置摄像头,光线与距离感应马上就被触发了。
下面是摇一摇、光线与距离感应的效果截图:
下面是传感器开发(摇一摇)的完整代码例子:
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
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.os.Vibrator;
import android.util.Log;
import android.widget.TextView;
import com.example.exmcamera.R;
public class SensorActivity extends Activity implements SensorEventListener {
private static final String TAG = "SensorActivity";
private TextView tv_sensor;
private TextView tv_shake;
private TextView tv_light;
private TextView tv_distance;
private SensorManager mSensroMgr;
private Vibrator mVibrator;
private String[] mSensorType = {
"加速度", "磁场", "方向", "陀螺仪", "光线",
"压力", "温度", "距离", "重力", "线性加速度",
"旋转矢量", "湿度", "环境温度", "无标定磁场", "无标定旋转矢量",
"未校准陀螺仪", "特殊动作", "步行检测", "计步器", "地磁旋转矢量"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sensor);
tv_sensor = (TextView) findViewById(R.id.tv_sensor);
tv_shake = (TextView) findViewById(R.id.tv_shake);
tv_light = (TextView) findViewById(R.id.tv_light);
tv_distance = (TextView) findViewById(R.id.tv_distance);
mSensroMgr = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mVibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
showSensorInfo();
}
private void showSensorInfo() {
List<Sensor> sensorList = mSensroMgr.getSensorList(Sensor.TYPE_ALL);
ArrayList<String> sensorNameList = new ArrayList<String>();
String show_content = "当前支持的传感器包括:\n";
for (Sensor sensor : sensorList) {
sensorNameList.add(sensor.getName());
String content = String.format("%s:%s\n",
mSensorType[sensor.getType()-1], sensor.getName());
show_content += content;
}
tv_sensor.setText(show_content);
}
private String getNowDateTime() {
SimpleDateFormat s_format = new SimpleDateFormat("HH:mm:ss");
Date d_date = new Date();
String s_date = "";
s_date = s_format.format(d_date);
return s_date;
}
@Override
protected void onPause() {
super.onPause();
mSensroMgr.unregisterListener(this);
}
@Override
protected void onResume() {
super.onResume();
mSensroMgr.registerListener(this,
mSensroMgr.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
SensorManager.SENSOR_DELAY_NORMAL);
mSensroMgr.registerListener(this,
mSensroMgr.getDefaultSensor(Sensor.TYPE_LIGHT),
SensorManager.SENSOR_DELAY_NORMAL);
mSensroMgr.registerListener(this,
mSensroMgr.getDefaultSensor(Sensor.TYPE_PROXIMITY),
SensorManager.SENSOR_DELAY_NORMAL);
}
@Override
public void onSensorChanged(SensorEvent event) {
int sensorType = event.sensor.getType();
if (sensorType == Sensor.TYPE_ACCELEROMETER) {
// values[0]:X轴,values[1]:Y轴,values[2]:Z轴
float[] values = event.values;
if (sensorType == Sensor.TYPE_ACCELEROMETER) {
if ((Math.abs(values[0]) > 15 || Math.abs(values[1]) > 15
|| Math.abs(values[2]) > 15)) {
tv_shake.setText(getNowDateTime()+" 恭喜您摇一摇啦");
//系统检测到摇一摇事件后,震动手机提示用户
mVibrator.vibrate(500);
}
}
} else if (sensorType == Sensor.TYPE_LIGHT) {
float light_strength = event.values[0];
tv_light.setText(getNowDateTime()+" 当前光线强度为"+light_strength);
} else if (sensorType == Sensor.TYPE_PROXIMITY) {
float distance = event.values[0];
tv_distance.setText(getNowDateTime()+" 有不明物体接近!距离"+distance+"厘米");
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
//当传感器精度改变时回调该方法,一般无需处理
}
}