传感器和LBS
一、传感器
手机中内置的传感器是一种微型的物理设备,它能够探测、感受到外界的信号,并按一定规律转换成我们所需要的信息。看似比较复杂的传感器,其实在我们写代码的过程是非常简单的,因为我们只需要调用手机中已经有的传感器硬件,然后分析数据就行了。
1. 下面来简单介绍一下传感器的使用方法:
◆ 首先获取SensorManager的实例,然后通过它得到任意的传感器类型(下面三种方法选一种);
SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);//获取SensorManager实例
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);//获取某个类型的传感器
List<Sensor> sensors = sensorManager.getSensorList(Sensor.TYPE_PRESSURE);//获取某种类型的传感器列表
List<Sensor> sensors = sensorManager.getSensorList(Sensor.TYPE_ALL);//获取所有类型的传感器
◆ 接下来就传感器输出信号进行监听,这就要借助SensorEventListener来实现。SensorEventListener是一个接口,其中定义了onSensorChanged()和
onAccuracyChanged()这两种方法,如下所示:
@Override
//传感器事件的数据改变的回调接口
//硬件会持续不断的调用这个方法,所以不要阻塞这个方法
public void onSensorChanged(SensorEvent event) {
}
@Override
//传感器精度发生改变的回调接口
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
◆ 然后注册一个SensorEventListener就可以了,记住使用完后要调用unregisterListener()方法将使用资源释放掉
senserManager.registerListener(listener, senser, SensorManager.SENSOR_DELAY_NORMAL);
<pre name="code" class="java">sensorManager.unregisterListener(listener);
2. 加速度传感器
传感器的使用方法都是大同小异,这里要介绍的是在使用加速度传感器时,传感器输出的信息是放在SensorEvent的values数组中的。此时values数组中会有三个值,分别代表手机在x轴、y轴和z轴的方向上的加速度信息。如下图:
3. 方向传感器
Android曾经的方向传感器是Sensor.TYPE_ORIENTATION,但是现在已经被弃用了。目前,安卓获取手机旋转的方向和角度是通过加速度传感器和地磁传感器共同计算得出的。◆ 分别得到两个传感器,并注册:
mAccelerometerSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mMagneticSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
mSensorManager.registerListener(this, mAccelerometerSensor, SensorManager.SENSOR_DELAY_GAME);
mSensorManager.registerListener(this, mMagneticSensor, SensorManager.SENSOR_DELAY_GAME);
◆ 实现SensorEventListener接口, 接下来在 onSensorChanged() 方法中可以获取到 SensorEvent 的 values 数组,分 别记录着加速度传感器和地磁传感器输出的值。然后将这两个值传入到 SensorManager 的 getRotationMatrix() 方法中就可以得到一个包含旋转矩阵的 R 数组,然后调用getOrientation()方法计算出各方向上的旋转弧度values, values 是一个长度为 3 的 float 数组,手机在各个方向上的旋转数据都会被存放到这 个数组当中。 其中 values[0] 记录着手机围绕着图 12.3 中 Z 轴的旋转弧度, values[1]记录着手机围绕 X 轴的旋转弧度, values[2] 记录着手机围绕 Y 轴的旋转弧度。
注意这里计算出的数据都是以弧度为单位的,因此如果你想将它们转换成角度还需要调用如下方法:
Math.toDegrees(values[0]);
values[0]的取值范围是- 180度到 180度,其中±180度表示正南方向,0度表示正北方向,- 90 度表示正西方向,90 度表示正东方向。
private SensorEventListener listener = new SensorEventListener() {
float[] accelerometerValues = new float[3];
float[] mageneticValues = new float[3];
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER){
//注意赋值时要用clone()方法,<span style="font-family: Arial, Helvetica, sans-serif;"><span style="font-family:宋体;">不然</span> </span><span style="font-family: Arial, Helvetica, sans-serif;">accelerometerValues</span><span style="font-family: Arial, Helvetica, sans-serif;"> <span style="font-family:宋体;">和</span> </span><span style="font-family: Arial, Helvetica, sans-serif;">magneticValues</span><span style="font-family: Arial, Helvetica, sans-serif;"> <span style="font-family: 宋体;">将会指向同一个引用。</span></span>
accelerometerValues = event.values.clone();
}else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD){
mageneticValues = event.values.clone();
}
float[] R = new float[9];
float[] values = new float[3];
//计算出旋转矩阵 R,<span style="font-family: 宋体;">其中第一个参数</span> R <span style="font-family: 宋体;">是一个长度为</span> 9 <span style="font-family: 宋体;">的</span> float <span style="font-family: 宋体;">数组,</span>getRotationMatrix()方法计算出的旋转数据就会赋值到这个数组当中。第二个参数是一个用于将地磁向量转换成重力坐标的旋<span style="font-family: 宋体;">转矩阵,通常指定为</span><span style="font-family: Arial, Helvetica, sans-serif;"> null </span><span style="font-family: 宋体;">即可。第三和第四个参数则分别就是加速度传感器和地磁传感器</span><span style="font-family: 宋体;">输出的</span><span style="font-family: Arial, Helvetica, sans-serif;"> values </span><span style="font-family: 宋体;">值。</span>
mSensorManager.getRotationMatrix(R,null,accelerometerValues,mageneticValues);
//计算出各方向上的旋转弧度 values
mSensorManager.getOrientation(R,values);
}
注意事项:1. 最好在onResume中注册监听器,在onPause方法中注销,这样会在页面被覆盖或锁屏时暂停监听。不然会耗电很快,因为硬件会不断扫描数据上传。
2. 谨慎使用传感器延时,根据应用场景选择合适的频率,因为他会占用系统资源、消耗电量甚至造成卡死;
二、LBS(基于位置服务)
Android常用的位置提供器有两种,GPS_PROVIDER和NETWORK_PROVIDER。GPS定位的精准度比较高,但是非常耗电,而网络定位的精准度稍差,但耗电量比较少。定位功能是需要用户手动开启的,可以在设置中设置开启或关闭。不过不需要担心一旦启用了定位功能,手机的电量就会直线下滑,这些选项只是表明你已经让应用程序来对你的手机进行定位了,但只有当定位操作真正开始的时候才会影响到手机的电量。
下面是一段代码示例,得到了location格式的位置信息,包含经度、纬度、海拔等信息,可是这些信息我们一般人是看不懂的。我们就要通过反向地理编码,通过google提供的Geocoding 的API,用Http协议把经度、纬度传给服务器并获取对应的位置信息。
public class LocationActivity extends Activity {
private LocationManager mLocationManager;
private String provider;
private TextView mLocationText;
private TextView mPositionText;
private String mCurrentLocation;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.location_activity);
mLocationText = (TextView) findViewById(R.id.location_text);
mPositionText = (TextView) findViewById(R.id.location_name_text);
mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); //获取LocationManager实例
List<String> providerList = mLocationManager.getProviders(true); //获得此设备开启的定位功能
//判断哪个定位功能可用
if (providerList.contains(LocationManager.GPS_PROVIDER)){
provider = LocationManager.GPS_PROVIDER;
}else if (providerList.contains(LocationManager.NETWORK_PROVIDER)){
provider = LocationManager.NETWORK_PROVIDER;
}else {
Toast.makeText(this,"定位功能没有开启",Toast.LENGTH_SHORT).show();
}
//获取定位信息,这个location中包含经度、纬度、海拔等一系列信息,我们只需要取出我们需要的内容即可
Location location = mLocationManager.getLastKnownLocation(provider);
if (location != null){
showLocation(location);
}
/**
* 注册位置改变的监听器,这里指LocationManager每隔5秒会检测一下位置的变化情况,
* 当移动距离超过10米时,就会调用locationListener的onLocationChanged()方法,并把新位置作为参数传入
*/
mLocationManager.requestLocationUpdates(provider,5000,10,locationListener);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mLocationManager != null){
//移除位置监听器
mLocationManager.removeUpdates(locationListener);
}
}
/**
* 定义一个位置监听器
*/
LocationListener locationListener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
showLocation(location);
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
}
};
/**
* 显示位置信息location,这里只提取了经度纬度
*/
private void showLocation(Location location) {
mCurrentLocation = "latitude is" + location.getAltitude() + "\n" + "longitude is" + location.getLongitude();
mLocationText.setText(mCurrentLocation);
showPosition(location);
}
/**
* 解析位置信息,得到我们能看得懂的地理位置
*/
private void showPosition(final Location location){
new Thread(new Runnable() {
@Override
public void run() {
//组装url,这里我们用的是google提供的API接口,将经度纬度传给服务器并获取返回的位置信息
StringBuilder url = new StringBuilder();
//由三部分组成——接口的连接地址、经度纬度、是否来自某个设备的位置传感器
url.append("http://maps.googleapis.com/maps/api/geocode/json?latlng=");//json表示希望服务器返回的是JSON格式数据,也可以指定为xml
url.append(location.getAltitude()).append(",");
url.append(location.getLongitude());
url.append("&sensor=false");//通常指定false
HttpClient httpClient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(url.toString());
//在请求消息头中指定语言,保证服务器会返回中文数据
httpGet.addHeader("Accept-Language","zh-CN");
try {
HttpResponse httpResponse = httpClient.execute(httpGet);
if (httpResponse.getStatusLine().getStatusCode() == 200){
HttpEntity entity = httpResponse.getEntity();
String reponse = EntityUtils.toString(entity,"utf-8");
//解析JSON数据
JSONObject jsonObject = new JSONObject(reponse);
//获取results节点下的位置信息
JSONArray resultArray = jsonObject.getJSONArray(reponse);
if (resultArray.length() > 0){
JSONObject subObject = resultArray.getJSONObject(0);
//取出格式化后的位置信息
String address = subObject.getString("formatted_address");
Message message = new Message();
message.what = 0;
message.obj = address;
mHandler.sendMessage(message);
}
}
} catch (IOException | JSONException e) {
e.printStackTrace();
}
}
});
}
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 0:
mPositionText.setText((String) msg.obj);
}
}
};
}