由于开发需要,简单学习了基于百度地图的GPS定位,实现了获取当地经纬度及详细地址。并附有官方demo。需要注意的是,必须先注册百度开发者账号,(http://developer.baidu.com),然后获取API Key http://lbsyun.baidu.com/apiconsole/key 。 将获得的API Key写入AndroidManifest.xml文件。
工程目录如下:
注意:将下载的SDK压缩包解压,然后将SDK里的jar包复制到BaiduMapTest\app\libs文件夹下,如果没有libs文件夹,手动创建;
然后将SDK里的.so文件复制到BaiduMapTest\app\src\main\jniLibs文件夹下,同样如没有jniLibs文件夹,手动创建,一定要注意路径不要错。
在build.gradle(Module:app)中添加jar包依赖:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}
具体代码如下:
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.xiaobailong24.baidumaptest">
<uses-permission android:name="com.android.launcher.permission.READ_SETTINGS" />
<!-- 这个权限用于进行网络定位 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- 这个权限用于访问GPS定位 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- 用于访问wifi网络信息,wifi信息会用于进行网络定位 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!-- 获取运营商信息,用于支持提供运营商信息相关的接口 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_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" />
<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="sWGvG5y3d66fvEYIlHyGkwwYS8HxbkvZ" />
<supports-screens
android:anyDensity="true"
android:largeScreens="true"
android:normalScreens="false"
android:resizeable="true"
android:smallScreens="true" />
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<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" >
<intent-filter>
<action android:name="com.baidu.location.service_v2.2" >
</action>
</intent-filter>
</service>
</application>
</manifest>
MainActivity.java
package com.xiaobailong24.baidumaptest;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.widget.TextView;
import com.baidu.location.BDLocation;
import com.baidu.location.BDLocationListener;
import com.baidu.location.LocationClient;
import com.baidu.location.LocationClientOption;
import com.baidu.location.Poi;
import com.baidu.mapapi.SDKInitializer;
import com.baidu.mapapi.map.BaiduMap;
import com.baidu.mapapi.map.BitmapDescriptor;
import com.baidu.mapapi.map.MapStatusUpdate;
import com.baidu.mapapi.map.MapStatusUpdateFactory;
import com.baidu.mapapi.map.MapView;
import com.baidu.mapapi.map.MyLocationConfiguration;
import com.baidu.mapapi.map.MyLocationData;
import com.baidu.mapapi.model.LatLng;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private MapView mMapView = null;
private BaiduMap mBaiduMap = null;
private LocationClient mLocationClient = null;
private BDLocationListener myListener = new MyLocationListener();
private MyLocationConfiguration.LocationMode mCurrentMode;
private BitmapDescriptor mCurrentMarker;
private MyLocationData locData;
String address;
double latitude, longitude;
float radius;
TextView latitudeText, longitudeText, locationText;
private static final int OK_LOCATION = 0; //定位成功
private static final int GPS_LOCATION = 1;
private static final int NETWORK_LOCATION = 2;
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// this.obtainMessage();
switch (msg.what) {
case OK_LOCATION:
latitudeText.setText("本地纬度" + latitude);
longitudeText.setText("本地经度" + longitude);
mCurrentMode = MyLocationConfiguration.LocationMode.NORMAL;
// 设置定位数据
mBaiduMap.setMyLocationData(locData);
//把地图移动到当前点
LatLng ll = new LatLng(latitude, longitude);
MapStatusUpdate u = MapStatusUpdateFactory.newLatLng(ll);
mBaiduMap.animateMapStatus(u);
// 设置定位图层的配置(定位模式,是否允许方向信息,用户自定义定位图标)
// mCurrentMarker = BitmapDescriptorFactory
// .fromResource(R.drawable.ic_launcher);
MyLocationConfiguration config = new MyLocationConfiguration(mCurrentMode, true, null);
mBaiduMap.setMyLocationConfigeration(config);
break;
case GPS_LOCATION:
locationText.setText("本地位置" + address);
break;
case NETWORK_LOCATION:
locationText.setText("本地位置" + address);
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//在应用程序创建时初始化 SDK引用的Context 全局变量
requestWindowFeature(Window.FEATURE_NO_TITLE);
// 在使用SDK各组件之前初始化context信息,传入ApplicationContext
// 注意该方法要再setContentView方法之前实现
SDKInitializer.initialize(getApplicationContext());
setContentView(R.layout.activity_main);
// 获取地图控件引用
mMapView = (MapView) findViewById(R.id.id_bmapView);
mBaiduMap = mMapView.getMap();
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
//初始化LocationClient类
mLocationClient = new LocationClient(getApplicationContext()); //声明LocationClient类
mLocationClient.registerLocationListener(myListener); //注册监听函数
initLocation();
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "开始定位", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
if (!mLocationClient.isStarted()) {
mLocationClient.start();
} else {
mLocationClient.stop();
mLocationClient.start();
}
}
});
locationText = (TextView) findViewById(R.id.location);
latitudeText = (TextView) findViewById(R.id.latitude);
longitudeText = (TextView) findViewById(R.id.longitude);
}
/**
* 配置定位SDK参数
* 设置定位参数包括:定位模式(高精度定位模式,低功耗定位模式和仅用设备定位模式),
* 返回坐标类型,是否打开GPS,是否返回地址信息、位置语义化信息、POI信息等等。
*/
private void initLocation() {
LocationClientOption option = new LocationClientOption();
option.setLocationMode(LocationClientOption.LocationMode.Hight_Accuracy
);//可选,默认高精度,设置定位模式,高精度,低功耗,仅设备
option.setCoorType("bd09ll");//可选,默认gcj02,设置返回的定位结果坐标系
int span = 0;
option.setScanSpan(span);//可选,默认0,即仅定位一次,设置发起定位请求的间隔需要大于等于1000ms才是有效的
option.setIsNeedAddress(true);//可选,设置是否需要地址信息,默认不需要
option.setOpenGps(true);//可选,默认false,设置是否使用gps
option.setLocationNotify(true);//可选,默认false,设置是否当gps有效时按照1S1次频率输出GPS结果
option.setIsNeedLocationDescribe(true);//可选,默认false,设置是否需要位置语义化结果,可以在BDLocation.getLocationDescribe里得到,结果类似于“在北京天安门附近”
option.setIsNeedLocationPoiList(true);//可选,默认false,设置是否需要POI结果,可以在BDLocation.getPoiList里得到
option.setIgnoreKillProcess(false);//可选,默认true,定位SDK内部是一个SERVICE,并放到了独立进程,设置是否在stop的时候杀死这个进程,默认不杀死
option.SetIgnoreCacheException(false);//可选,默认false,设置是否收集CRASH信息,默认收集
option.setEnableSimulateGps(false);//可选,默认false,设置是否需要过滤gps仿真结果,默认需要
mLocationClient.setLocOption(option);
// 开启定位图层
mBaiduMap.setMyLocationEnabled(true);
}
/**
* 实现BDLocationListener接口
* BDLocationListener接口有1个方法需要实现: 1.接收异步返回的定位结果,参数是BDLocation类型参数
*/
public class MyLocationListener implements BDLocationListener {
@Override
public void onReceiveLocation(BDLocation location) {
//Receive Location
StringBuffer sb = new StringBuffer(256);
sb.append("time : ");
sb.append(location.getTime());
sb.append("\nerror code : ");
sb.append(location.getLocType());
sb.append("\nlatitude : ");
sb.append(location.getLatitude());
sb.append("\nlongitude : ");
sb.append(location.getLongitude());
sb.append("\nradius : ");
sb.append(location.getRadius());
//发送消息更新UI
Message msg = mHandler.obtainMessage();
msg.what = 0;
latitude = location.getLatitude();
longitude = location.getLongitude();
radius = location.getRadius();
// 构造定位数据
locData = new MyLocationData.Builder()
.accuracy(radius)
// 此处设置开发者获取到的方向信息,顺时针0-360
.direction(100).latitude(latitude)
.longitude(longitude).build();
mHandler.dispatchMessage(msg);
if (location.getLocType() == BDLocation.TypeGpsLocation) {// GPS定位结果
sb.append("\nspeed : ");
sb.append(location.getSpeed());// 单位:公里每小时
sb.append("\nsatellite : ");
sb.append(location.getSatelliteNumber());
sb.append("\nheight : ");
sb.append(location.getAltitude());// 单位:米
sb.append("\ndirection : ");
sb.append(location.getDirection());// 单位度
sb.append("\naddr : ");
sb.append(location.getAddrStr());
sb.append("\ndescribe : ");
sb.append("gps定位成功");
//
msg.what = GPS_LOCATION;
address = location.getAddrStr();
mHandler.dispatchMessage(msg);
} else if (location.getLocType() == BDLocation.TypeNetWorkLocation) {// 网络定位结果
sb.append("\naddr : ");
sb.append(location.getAddrStr());
//运营商信息
sb.append("\noperationers : ");
sb.append(location.getOperators());
sb.append("\ndescribe : ");
sb.append("网络定位成功");
msg.what = NETWORK_LOCATION;
address = location.getAddrStr();
mHandler.dispatchMessage(msg);
} else if (location.getLocType() == BDLocation.TypeOffLineLocation) {// 离线定位结果
sb.append("\ndescribe : ");
sb.append("离线定位成功,离线定位结果也是有效的");
} else if (location.getLocType() == BDLocation.TypeServerError) {
sb.append("\ndescribe : ");
sb.append("服务端网络定位失败,可以反馈IMEI号和大体定位时间到[email protected],会有人追查原因");
} else if (location.getLocType() == BDLocation.TypeNetWorkException) {
sb.append("\ndescribe : ");
sb.append("网络不同导致定位失败,请检查网络是否通畅");
} else if (location.getLocType() == BDLocation.TypeCriteriaException) {
sb.append("\ndescribe : ");
sb.append("无法获取有效定位依据导致定位失败,一般是由于手机的原因,处于飞行模式下一般会造成这种结果,可以试着重启手机");
}
sb.append("\nlocationdescribe : ");
sb.append(location.getLocationDescribe());// 位置语义化信息
List<Poi> list = location.getPoiList();// POI数据
if (list != null) {
sb.append("\npoilist size = : ");
sb.append(list.size());
for (Poi p : list) {
sb.append("\npoi= : ");
sb.append(p.getId() + " " + p.getName() + " " + p.getRank());
}
}
Log.i("BaiduLocationApiDem", sb.toString());
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 在activity执行onDestroy时执行mMapView.onDestroy(),实现地图生命周期管理
mMapView.onDestroy();
mMapView = null;
if (mLocationClient != null && mLocationClient.isStarted()) {
mLocationClient.stop();
mLocationClient = null;
}
// 关闭定位图层
mBaiduMap.setMyLocationEnabled(false);
}
@Override
protected void onResume() {
super.onResume();
// 在activity执行onResume时执行mMapView. onResume (),实现地图生命周期管理
mMapView.onResume();
if (mLocationClient.isStarted())
mLocationClient.stop();
}
@Override
protected void onPause() {
super.onPause();
// 在activity执行onPause时执行mMapView. onPause (),实现地图生命周期管理
mMapView.onPause();
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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"
android:fitsSystemWindows="true"
tools:context="com.xiaobailong24.baidumaptest.MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_main" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center|end"
android:layout_margin="@dimen/fab_margin"
android:src="@mipmap/ic_room_black_24dp" />
</android.support.design.widget.CoordinatorLayout>
content_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="com.xiaobailong24.baidumaptest.MainActivity"
tools:showIn="@layout/activity_main">
<TextView
android:id="@+id/location"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="本地位置:"
android:textSize="24sp"/>
<TextView
android:id="@+id/latitude"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="本地纬度:"
android:textSize="24sp"/>
<TextView
android:id="@+id/longitude"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="本地经度:"
android:textSize="24sp"/>
<com.baidu.mapapi.map.MapView
android:id="@+id/id_bmapView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:clickable="true"/>
</LinearLayout>
遇到的坑
SDK 初始化问题
问题描述
MapView 初始化失败,无法获得 MapView 对象。
android.view.InflateException: Binary XML file line #7: Error inflating class com.baidu.mapapi.map.MapView
原因
官方建议 SDK 初始化放在setContentView方法之前实现。
解决方案
在SDK各功能组件使用之前都需要调用
SDKInitializer.initialize(getApplicationContext());
因此将SDK初始化方法放在Application的初始化方法(onCreate)中。
Android 6.0 地图定位显示空白
原因
由于Android 6.0 采用动态权限管理,将权限分为 Normal Permissions 和 Dangerous Permissions,具体权限分析可参考 Android 6.0 运行时权限处理完全解析 这篇文章。由于无法获取权限,导致定位失败,调试中发现没有获取到当前位置的准确经纬度,从而地图无法显示。
解决方案
在地图显示前,先获取系统API版本,若API >= 23,则动态获取以下权限:
<!-- 这个权限用于进行网络定位 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!-- 这个权限用于访问GPS定位 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<!-- 用于读取手机当前的状态 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<!-- 写入扩展存储,向扩展卡写入数据,用于写入离线定位数据 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
具体实现方式,采用了 MPermissions这个开源库。
Github:
https://github.com/xiaobailong24-library/BaiduMapTest