本章要点
只有在移动设备上才能够实现的技术——基于位置的服务(Location Based Service)。
11.1 基于位置服务的简介
基于位置的服务简称LBS。主要的工作原理就是利用无线电通讯网络或GPS等定位方式来确定出移动设备所在的位置。
有了Android系统作为载体,我们可以利用定位出的位置进行更多的丰富的操作。比如:天气预报程序可根据用户所在的位置自动选择城市,发微博朋友圈可以晒自己在哪里,不认识的路可以打开地图就可查看路线等等。
基于位置的服务所围绕的核心就是要先确定自己所在的位置。两种方式确定自己所在的位置:
- GPS,工作原理是基于手机内置的GPS硬件直接和卫星交互来获取当前的经纬度信息,精确度非常高,只能在室外使用。
- 网络定位,工作原理是根据手机当前网络的三个基站进行测量,以此计算出手机和每个基站之间的距离,在通过三角定位确定出一个大概的位置,精确度一般,室内外都可以使用。
Android对定位方式提供了相应的API的支持,但是由于Google的网络服务在中国不可访问,从而导致网络定位的方式API失效,GPS不需要网络,所以可以使用。
为了使我们的开发都可以获取到定位信息,目前国内在这一领域做的比较好的就是百度,高德。我们来学习一下百度在LBS方面提供的丰富多彩的功能。
11.2 申请 API Key
在自己的程序中使用百度的LBS功能,首先必须申请一个API Key。
登陆百度账号,并打开http://developer.baidu.com/user/reg。填写注册信息即可(只需填写带“*”的部分内容足够)。如图:
接下来点击提交,根据步骤来,点击“去我的邮箱”,就会收到百度发给我们邮件,点击邮件中的链接,完成注册。
成为百度开发者以后,接着访问http://lbsyun.baidu.com/apiconsole/key 。同意百度开发者协议,会看到如图:
点击创建应用申请API Key,应用名称随便写,应用类型选择Android SDK,启动服务保持默认即可,如图:
发布版SHA1和开发板SHA1是我们申请API Key所必须填写的一个字段,指的是打包程序时所用签名文件的SHA1指纹,可通过Android Studio查看。打开Android Studio的任意项目,点击右侧工具栏的Gradle—>项目名称—>:app—>Tasks—>android,如图:
展示了Android Studio项目所有内置的Gradle Tasks,其中signingReport用来查看签名文件信息,如下:
这里我们可以看到SHA1,这是Android自动生成的一个用于测试的签名文件。当我们的应用程序发布的时候需要创建一个正式的签名文件,得到它的指纹,在cmd中输入命令(签名文件路径):
keytool - list -v -keystore
然后输入正确的密码就可以了。创建签名我们将在第15章学习。
现在得到的这个SHA1指纹是一个开发版,我们暂时还没有发布版的SHA1指纹,因此两个值都填成一样就可以了。定义我们的包名(与应用程序的报名一致)。如下:
点击提交,创建完成。如图:
8EqUIKRK6AqqrFBOtqoZ7a8SuQ6st9GZ就是我们申请的API Key。
11.3 使用百度定位
新建LBSTest项目,包名自然就是com.example.hjw.lbstest了。
建议真机测试,获取真实的数据。
11.3.1 准备LBS SDK
百度LBS SDK,下载地址:http://lbsyun.baidu.com/sdk/download。
勾选基础地图和基础定位的SDK(我们要使用的),然后点击“开发包”下载按钮即可。如图:
下载完成解压并打开,你会发现lib目录,如图:
包含两部分:BaiduLBS_Android.jar这个文件是Java层要使用的,其他子目录下的so文件(C/C++编写,再用NDK编译出来的)是Native层用到的。放置文件到争取的位置。
将BaiduLBS_Android.jar放置libs目录下,如图:
将so文件放置,新创建(src-main)的jniLibs的目录中,如图:
我们每个创建的项目中app/build.gradle都会默认配置这段声明:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
...
}
这表示将所有libs目录下.jar结尾的文件都会添加到当前项目中。手动点击Android Studio 顶部的工具栏中的Sysc按钮,如图:
点击Sync按钮后,我们会发现libs下的.jar文件会多出箭头,表示已经引用到了Jar包了。如图:
这样就准备好了LBS的SDK.。
11.3.2 确定自己位置的经纬度
修改activity_main.xml中的代码,用于显示经纬度(TextView),如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_position"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
修改AndroidManifest.xml中的代码如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.hjw.lbstest">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<meta-data
android:name="com.baidu.lbsapi.API_KEY"
android:value="8EqUIKRK6AqqrFBOtqoZ7a8SuQ6st9GZ" />
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name="com.baidu.location.f"
android:enabled="true"
android:process=":remote" />
</application>
</manifest>
添加了LBS SDK内部用到的权限;< meta-data>标签的android:name是固定的(必须填写com.baidu.lbsapi.API_KEY),android-value填写我们申请到的API Key;注册LBS SDK中的服务。
修改MainActivity中的代码如下:
public class MainActivity extends AppCompatActivity {
public LocationClient mLocationClient;
private TextView tv_position;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mLocationClient = new LocationClient(getApplicationContext()); //创建LocationClient实例
mLocationClient.registerLocationListener(new MyLocationListener()); //注册监听,获取当前位置的时候回调
setContentView(R.layout.activity_main);
tv_position = (TextView) findViewById(R.id.tv_position);
//进行危险性权限的处理
List<String> permissionList = new ArrayList<>();
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
permissionList.add(Manifest.permission.ACCESS_FINE_LOCATION);
}
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_PHONE_STATE)
!= PackageManager.PERMISSION_GRANTED) {
permissionList.add(Manifest.permission.READ_PHONE_STATE);
}
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
if (!permissionList.isEmpty()) {
String[] permissions = permissionList.toArray(new String[permissionList.size()]);
ActivityCompat.requestPermissions(MainActivity.this, permissions, 1);
} else {
requestLocation();
}
}
private void requestLocation() {
mLocationClient.start(); //开始定位
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case 1:
if (grantResults.length > 0) {
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "必须同意所有权限才能够使用本程序", Toast.LENGTH_SHORT).show();
finish();
return;
}
}
requestLocation();
} else {
Toast.makeText(this, "发生未知错误", Toast.LENGTH_SHORT).show();
finish();
}
break;
default:
}
}
public class MyLocationListener implements BDLocationListener {
@Override
public void onReceiveLocation(BDLocation bdLocation) {
final StringBuilder currentPosition = new StringBuilder();
currentPosition.append("纬度:").append(bdLocation.getLatitude()).append("\n");
currentPosition.append("经度:").append(bdLocation.getLongitude()).append("\n");
currentPosition.append("定位方式:");
if (bdLocation.getLocType() == BDLocation.TypeGpsLocation) {
currentPosition.append("GPS");
} else if (bdLocation.getLocType() == BDLocation.TypeNetWorkLocation) {
currentPosition.append("网络");
}
runOnUiThread(new Runnable() {
@Override
public void run() {
tv_position.setText(currentPosition);
}
});
}
@Override
public void onConnectHotSpotMessage(String s, int i) {
}
}
}
运行程序,同意所有权限,就会立刻开始定位,如图:
实时更新当前位置的,LBS SDK允许更改默认的行为,修改MaiActivity中的代码如下:
public class MainActivity extends AppCompatActivity {
...
private void requestLocation() {
initLocation();
mLocationClient.start();
}
private void initLocation() {
LocationClientOption option = new LocationClientOption(); //创建LocationClientOption对象
option.setScanSpan(5000); //设置更新的间隔(5000表示5秒更新)
mLocationClient.setLocOption(option);
}
@Override
protected void onDestroy() {
super.onDestroy();
mLocationClient.stop(); //销毁的时候,停止定位,防止手机耗电
}
...
}
重新运行程序,随时移动你的位置,界面上的经纬度也会跟着发生变化。
11.3.3 选择定位模式
GPS定位功能必须由用户主动启动。进入手机的设置—>位置信息,如图:
通过顶部的开关控制定位是否开关,“模式”可以选择具体的定位模式,如图:
准确度高:允许使用GPS,无线网络,蓝牙或移动网络来定位。
耗电量低:允许使用无线网络,蓝牙或移动网络来定位。
仅限设备:仅允许GPS来定位。
开启了GPS的功能,接下来我们在initLocation()对百度LBS SDK的模式进行指定,总共3种模式:Hight_Accuracy(高精确度模式,默认的),Battery_Saving(节电模式)和Device_Sensors(传感器模式)。
强制只使用GPS定位,修改MainActivity中的代码如下:
public class MainActivity extends AppCompatActivity {
...
private void initLocation() {
LocationClientOption option = new LocationClientOption(); //创建LocationClientOption对象
option.setLocationMode(LocationClientOption.LocationMode.Device_Sensors); //指定定位模式
mLocationClient.setLocOption(option);
}
...
}
这里我们指定为Device_Sensors,只能使用GPS定位。重新运行程序,拿出你的手机走出室外,你就会发现定位方式改变了(GPS)。
11.3.4 看得懂的位置信息
百度LBS SDK提供一些简单的接口获取当前的位置地址信息,修改MainActivity中的代码如下:
public class MainActivity extends AppCompatActivity {
...
private void initLocation() {
LocationClientOption option = new LocationClientOption(); //创建LocationClientOption对象
option.setScanSpan(5000);
option.setIsNeedAddress(true); //获取当前位置的详情信息
mLocationClient.setLocOption(option);
}
...
public class MyLocationListener implements BDLocationListener {
@Override
public void onReceiveLocation(BDLocation bdLocation) {
final StringBuilder currentPosition = new StringBuilder();
currentPosition.append("纬度:").append(bdLocation.getLatitude()).append("\n");
currentPosition.append("经度:").append(bdLocation.getLongitude()).append("\n");
//设置当前信息
currentPosition.append("国家:").append(bdLocation.getCountry()).append("\n");
currentPosition.append("省:").append(bdLocation.getProvince()).append("\n");
currentPosition.append("市:").append(bdLocation.getCity()).append("\n");
currentPosition.append("区:").append(bdLocation.getDistrict()).append("\n");
currentPosition.append("街道:").append(bdLocation.getStreet()).append("\n");
currentPosition.append("定位方式:");
if (bdLocation.getLocType() == BDLocation.TypeGpsLocation) {
currentPosition.append("GPS");
} else if (bdLocation.getLocType() == BDLocation.TypeNetWorkLocation) {
currentPosition.append("网络");
}
runOnUiThread(new Runnable() {
@Override
public void run() {
tv_position.setText(currentPosition);
}
});
}
...
}
}
运行程序,如图:
11.4 使用百度顶图
在自己的应用程序中加入百度地图。
11.4.1 让地图显示出来
修改activity_main.xml中的代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_position"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone" />
<com.baidu.mapapi.map.MapView
android:id="@+id/bmapView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"></com.baidu.mapapi.map.MapView>
</LinearLayout>
接下来修改MainActivity中的代码如下:
public class MainActivity extends AppCompatActivity {
public LocationClient mLocationClient;
private TextView tv_position;
private MapView mapView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mLocationClient = new LocationClient(getApplicationContext());
mLocationClient.registerLocationListener(new MyLocationListener());
SDKInitializer.initialize(getApplicationContext()); //首先初始化操作
setContentView(R.layout.activity_main);
mapView = (MapView) findViewById(R.id.bmapView);
...
}
private void requestLocation() {
initLocation();
mLocationClient.start();
}
private void initLocation() {
LocationClientOption option = new LocationClientOption(); //创建LocationClientOption对象
option.setScanSpan(5000);
option.setIsNeedAddress(true); //获取当前位置的详情信息
mLocationClient.setLocOption(option);
}
@Override
protected void onResume() {
super.onResume();
mapView.onResume();
}
@Override
protected void onPause() {
super.onPause();
mapView.onPause();
}
@Override
protected void onDestroy() {
super.onDestroy();
mLocationClient.stop(); //销毁的时候,停止定位,防止手机耗电
mapView.onDestroy();
}
...
}
重写onResume(),onPause(),onDestroy()对MapView进行管理,以保证资源能够及时的释放。
运行程序,如图:
11.4.2 移动到我的位置
在地图中快速移动到自己的位置上。
百度LBS SDK的提供了一个BaiduMap类(地图的总控制器),调用MapView的getMap()就能获取到BaiduMap实例,如下:
BaiduMap baiduMap = mapView.getMap();
通过获取到的BaiduMap实例来设置对地图的各种操作,比如设置地图的缩级别(取值范围3-19,可取小数点,值越大,地图显示的信息越详细)以及将地图移动到某一个经纬度上。
MapStatusUpdate update = MapStatusUpdateFacotry.zoomTo(12.5f); //float
baiduMap.animateMapStatus(update); //设置缩放
LatLng类将地图移动到某一个经纬度,主要存放经纬度的值,接收两个参数的构造,第一个参数纬度值,第二个参数经度值。之后调用MapStatusUpdateFacotry.newLatlng()将LatLng将对象传入返回MapStatusUpdate 对象,传入到BaiduMap的animateMapStatus()方法中,写法如下:
LatLng ll = new LatLng(39.815,112.304);
MapStatusUpdate update = MapStatusUpdateFacotry.newLatLng(ll);
baiduMap.animateMapStatus(update); //设置缩放
移动到我的位置的,修改MainActivity中的代码如下:
public class MainActivity extends AppCompatActivity {
private BaiduMap baiduMap;
private boolean isFirstLocate = true;
...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mLocationClient = new LocationClient(getApplicationContext());
mLocationClient.registerLocationListener(new MyLocationListener());
SDKInitializer.initialize(getApplicationContext());
setContentView(R.layout.activity_main);
mapView = (MapView) findViewById(R.id.bmapView);
baiduMap = mapView.getMap();
...
}
...
private void navigateTo(BDLocation bdLocation) {
if (isFirstLocate) {
LatLng ll = new LatLng(bdLocation.getLatitude(), bdLocation.getLongitude());
MapStatusUpdate update = MapStatusUpdateFactory.newLatLng(ll);
baiduMap.animateMapStatus(update);
update = MapStatusUpdateFactory.zoomBy(16f);
baiduMap.animateMapStatus(update);
isFirstLocate = false;
}
}
public class MyLocationListener implements BDLocationListener {
@Override
public void onReceiveLocation(BDLocation bdLocation) {
if (bdLocation.getLocType() == BDLocation.TypeNetWorkLocation
|| bdLocation.getLocType() == BDLocation.TypeGpsLocation) {
navigateTo(bdLocation);
}
}
@Override
public void onConnectHotSpotMessage(String s, int i) {
}
}
}
调用navigateTo()获取当前位置,把onReceiveLocation()中的对象传入到navigateTo()中,显示自己的位置。
运行程序,如图:
11.4.3 让“我”显示在地图上
小光标,显示自己的位置(随着移动而移动)。
百度LBS SDK中提供了MyLocationData.Builder类(封装当前所在的位置,传入经纬度到这类相应的方法),如下:
MyLocationData.Builder locationBuilder = MyLocationData.Builder();
locationBuilder.latitude(39.314);
locationBuilder.longitude(115.689);
设置完之后,调用它的build()方法来生成MyLocationData的实例,将实例传入到BitMap中的setMyLocationData()方法当中。如下:
MyLocationData locationData = locationBuilder.build();
baiduMap.serMyLocationData(locationData);
接下来,我们修改MainActivity中的代码如下:
public class MainActivity extends AppCompatActivity {
public LocationClient mLocationClient;
private BaiduMap baiduMap;
private boolean isFirstLocate = true;
private TextView tv_position;
private MapView mapView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mLocationClient = new LocationClient(getApplicationContext());
mLocationClient.registerLocationListener(new MyLocationListener());
SDKInitializer.initialize(getApplicationContext());
setContentView(R.layout.activity_main);
mapView = (MapView) findViewById(R.id.bmapView);
baiduMap = mapView.getMap();
baiduMap.setMyLocationEnabled(true); //开启显示设备位置
tv_position = (TextView) findViewById(R.id.tv_position);
List<String> permissionList = new ArrayList<>();
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
permissionList.add(Manifest.permission.ACCESS_FINE_LOCATION);
}
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_PHONE_STATE)
!= PackageManager.PERMISSION_GRANTED) {
permissionList.add(Manifest.permission.READ_PHONE_STATE);
}
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
if (!permissionList.isEmpty()) {
String[] permissions = permissionList.toArray(new String[permissionList.size()]);
ActivityCompat.requestPermissions(MainActivity.this, permissions, 1);
} else {
requestLocation();
}
}
private void requestLocation() {
initLocation();
mLocationClient.start();
}
private void initLocation() {
LocationClientOption option = new LocationClientOption(); //创建LocationClientOption对象
option.setScanSpan(5000);
option.setIsNeedAddress(true); //获取当前位置的详情信息
mLocationClient.setLocOption(option);
}
@Override
protected void onResume() {
super.onResume();
mapView.onResume();
}
@Override
protected void onPause() {
super.onPause();
mapView.onPause();
}
@Override
protected void onDestroy() {
super.onDestroy();
mLocationClient.stop(); //销毁的时候,停止定位,防止手机耗电
mapView.onDestroy();
baiduMap.setMyLocationEnabled(false); //关闭显示设备位置
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case 1:
if (grantResults.length > 0) {
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "必须同意所有权限才能够使用本程序", Toast.LENGTH_SHORT).show();
finish();
return;
}
}
requestLocation();
} else {
Toast.makeText(this, "发生未知错误", Toast.LENGTH_SHORT).show();
finish();
}
break;
default:
}
}
private void navigateTo(BDLocation bdLocation) {
if (isFirstLocate) {
LatLng ll = new LatLng(bdLocation.getLatitude(), bdLocation.getLongitude());
MapStatusUpdate update = MapStatusUpdateFactory.newLatLng(ll);
baiduMap.animateMapStatus(update);
update = MapStatusUpdateFactory.zoomBy(16f);
baiduMap.animateMapStatus(update);
isFirstLocate = false;
}
MyLocationData.Builder locationBuilder = new MyLocationData.Builder();
locationBuilder.latitude(bdLocation.getLatitude());
locationBuilder.longitude(bdLocation.getLongitude());
MyLocationData locationData = locationBuilder.build();
baiduMap.setMyLocationData(locationData);
}
public class MyLocationListener implements BDLocationListener {
@Override
public void onReceiveLocation(BDLocation bdLocation) {
if (bdLocation.getLocType() == BDLocation.TypeNetWorkLocation
|| bdLocation.getLocType() == BDLocation.TypeGpsLocation) {
navigateTo(bdLocation);
}
}
@Override
public void onConnectHotSpotMessage(String s, int i) {
}
}
}
运行程序,如图:
深入研究LBS的各种用法,请查看官网开发指南:http://lbsyun.baidu.com。
11.5 Git时间——版本控制工具的高级使用
11.5.1 分支的用法
11.5.2 与远程版本库协作
11.6 小结与点评
学习了基于位置服务的工作原理和用法没,随时随地获取当前经纬度信息,显示具体所在的位置信息。并且将自己所在的位置显示在地图上。