首发于 地图说
5 分钟掌握如何用 Mapbox Android SDK 制作导航 App(上) | 附源码

5 分钟掌握如何用 Mapbox Android SDK 制作导航 App(上) | 附源码


如今,许多应用程序都具有定位功能,比如叫个外卖啊,打个车啊,甚至洗个衣服都能用到 App,在这类应用中,地图自然是不可缺少的;除此之外,越来越多的 App 开发者都在思考如何在应用中结合位置服务,挖掘更深层次的用户场景。

但是,听起来给 App 加地图,那些英文文档,接口,API...简直太复杂了。

别担心!本文将会帮你摆脱种种顾虑,通过一个开源案例 Where2Go,让你入门 Mapbox SDK,并快速构建一个具有位置功能的炫酷 Android 应用。



你将会了解到下面的内容:

  • 如何在工程中添加 Mapbox 库
  • 在地图上展示使用者目前的位置
  • 在地图上添加标记,并实现从当前位置到标记位置的导航功能


先行知识

本教程假设您了解使用 Kotlin 进行 Android 开发的基础知识。

如果您是 Android 开发的新手,请通过 Kotlin 的 Beginning Android Development 了解基础知识。


如果您是 Kotlin 的新手,请查看 Introduction to Kotlin 教程。

材料下载和 Android 环境配置

在了解 Mapbox 之前,请您先下载好入门材料和最终项目,具体下载方法是: 扫描下方二维码或关注公众号Mapbox(Mapbox_China)并回复「技术」



启动 Android Studio 3.3 或更高版本,选择 Open an existing Android Studio project 选项,然后选择刚才下载好的项目文件夹并启动。



当 Gradle 加载完成后,你可以直接运行一下,将会在模拟器中看到这样的画面。



这是空的,因为还没添加地图。下面就看看如何添加地图。

注册 Mapbox 账户

在使用 Mapbox SDK 之前,您需要做的第一件事是注册一个帐户。然后,一旦您成功注册,您将获得访问令牌(Access Token)。此令牌是您使用 Mapbox SDK 所需的密钥。

注册地址

到达 Mapbox 网站后,单击红色箭头指示的登录按钮。



您将被定向到另一个具有登录形式的页面,单击注册Mapbox,如红色框中所示。



在注册界面填写自己的信息后,点击 Get started。



注册是免费的,使用只有超过一定额度才会开始收费(一般个人开发者都达不到这个额度), 具体价格请查看这里

添加 Mapbox 依赖库

首先,打开 build.gradle (Module:app) 文件,在 ‘android’ 中添加 ‘compileOptions’ 。

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

然后,在 ‘dependencies’ 中加入 Mapbox 和 Google Play Service Location 依赖库。

implementation 'com.mapbox.mapboxsdk:mapbox-android-navigation-ui:0.26.0'
implementation ('com.mapbox.mapboxsdk:mapbox-android-sdk:6.8.1') {
exclude group: 'group_name', module: 'module_name'
implementation 'com.google.android.gms:play-services-location:16.0.0'

最后,打开 build.gradle (Project) 文件,在 ‘allprojects’ 的 ‘jcenter()’ 下加入如下代码。

mavenCentral()
maven { url 'https://mapbox.bintray.com/mapbox' }

点击 Sync now 同步工程。



开始使用 Mapbox

这一小节,我们先从获得 Mapbox 的访问令牌(Access Token)开始,接下来看看如何获得用户的位置,并把它们显示在地图上。

获得 Mapbox 访问令牌(Access Token)
成功注册 Mapbox 后,点击你的头像并选择 Account。



在 Account 页面中,您可以创建一个全新的 access token,或者使用默认的 public token.



配置并显示 Mapbox 地图
打开 activity_main.xml 文件。

首先,您需要在布局文件‘RelativeLayout’ 中添加 ‘xmlns:mapbox’。

xmlns:mapbox="http://schemas.android.com/apk/res-auto"

这样您才可以访问 Mapbox 属性。

接下来,添加下面的代码来展示 Mapbox 地图。

<com.mapbox.mapboxsdk.maps.MapView
android:id="@+id/mapbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
mapbox:mapbox_styleUrl="@string/mapbox_style_mapbox_streets"/>

改变 StyleUrl 的属性,就可以改变地图的样式。在这里我们使用了 Mapbox 街景图,您还可以添加 Mapbox Studio 中自定义的各种地图样式,只要把样式对应的 StyleUrl 粘贴在相应的代码位置即可。 具体 Mapbox Studio 的使用可以参考这篇文章

现在打开 AndroidManifest.xml 文件并添加下述许可。

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>


添加 android.permission.ACCESS_FINE_LOCATION 许可后,Mapbox SDK 才能检测您的位置并进行地图展现;添加 android.permission.FOREGROUND_SERVICE 许可后,才能开启导航功能。

下面请打开 MainActivity.kt 文件,在调用 setContentView 之前,添加下面的代码。

Mapbox.getInstance(this, "Your Mapbox access token")

这里要注意,请把你在 Account 中的 access token 替换 Your Mapbox access token。

因为您将会使用 Kotlin Android Extensions, 您可以通过 XML 中定义的 id 属性轻松引用视图。为了初始化 Mapbox 视图,在 setContentView 行下方添加这行代码。

mapbox.onCreate(savedInstanceState)

处理 Mapbox 生命周期非常重要,因此在 onCreate 函数下面添加以下代码以完成初始化和清理过程。

@SuppressWarnings("MissingPermission")
override fun onStart() {
super.onStart()
mapbox.onStart()
override fun onResume() {
super.onResume()
mapbox.onResume()
override fun onPause() {
super.onPause()
mapbox.onPause()
override fun onStop() {
super.onStop()
mapbox.onStop()
override fun onDestroy() {
super.onDestroy()
mapbox.onDestroy()
override fun onLowMemory() {
super.onLowMemory()
mapbox.onLowMemory()
override fun onSaveInstanceState(outState: Bundle?) {
super.onSaveInstanceState(outState)
if (outState != null) {
mapbox.onSaveInstanceState(outState)
}

Mapbox 有自己的生命周期方法来管理 Android openGL 生命周期。您必须直接从 containing activity 中调用这些方法。

小贴士:
您需要在onStart和其他方法上面添加@SuppressWarnings(“MissingPermission”),因为这些方法要求您实现许可处理。但是,这里不需要,因为 Mapbox 会为您处理。

您的 App 中已经可以显示 Mapbox 地图了!



检测用户当前位置
打开 MainActivity.kt 文件并改变类声明使它实现以下接口。

class MainActivity : AppCompatActivity(), PermissionsListener, LocationEngineListener, OnMapReadyCallback

您需要使用 PermissionsListener 来处理运行 Android Marshmallow 及更高版本的设备的位置权限,同时您需要使用 LocationEngineListener 来定位用户的位置。最后,您需要 OnMapReadyCallback,因为这将涵盖 PermissionsListener 和 LocationEngineListener,以便于定位用户所在的位置并在地图上显示该位置。

Android Studio 现在会报错,并告诉您需要导入的的元素,可以把鼠标悬停在错误上,点击 Implement members。



确保选中所有成员并点击 OK。



Android Studio 将会在 MainActivity.kt 中自动添加下面的代码。

//1
override fun onExplanationNeeded(permissionsToExplain: MutableList<String>?) {
override fun onPermissionResult(granted: Boolean) {
override fun onLocationChanged(location: Location?) {
@SuppressWarnings("MissingPermission")
override fun onConnected() {
override fun onMapReady(mapboxMap: MapboxMap?) {
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
}


一起来看下上面方法的具体含义:

  • onExplanationNeeded:向用户说明您需要权限的原因。
  • onPermissionResult:检查用户是否授予了权限,以及应用程序将如何处理该操作。
  • onLocationChanged:监控用户位置更改。
  • onConnected:这是应用程序完全连接的位置,可以接收位置更新。
  • onMapReady:地图已准备就绪,您可以执行与位置相关的活动。
  • onRequestPermissionsResult:Override 这个额外的方法,因为它是处理所有权限相关工作的方法。


现在我们来添加需要的变量,可以写在开头 onCreat 方法之前。

//1
val REQUEST_CHECK_SETTINGS = 1
var settingsClient: SettingsClient? = null
lateinit var map: MapboxMap
lateinit var permissionManager: PermissionsManager
var originLocation: Location? = null
var locationEngine: LocationEngine? = null
var locationComponent: LocationComponent? = null

我们使用 SettingsClient API 显示 AlertDialog 框,您将要求用户打开 GPS 以找到用户的位置。这些变量用于 Mapbox,并且都与获取用户的位置有关。


接下来,您需要创建4个方法。

  • enableLocation
  • initializeLocationEngine
  • setCameraPosition
  • initializeLocationComponent


//1
fun enableLocation() {
@SuppressWarnings("MissingPermission")
fun initializeLocationEngine() {
@SuppressWarnings("MissingPermission")
fun initializeLocationComponent() {
fun setCameraPosition(location: Location) {
}


上面的代码中,具体来说:

  • enableLocation:启用位置跟踪以查找用户当前位置。
  • initializeLocationEngine&initializeLocationComponent:这两个函数负责定位用户位置。
  • setCameraPosition:此函数处理放大用户在地图中的位置。


在 onCreate 函数中添加以下代码。

mapbox.getMapAsync(this)


这是一个回调,将在Mapbox地图准备就绪时触发。

接下来,您需要初始化 SettingsClient API。

settingsClient = LocationServices.getSettingsClient(this)

现在您需要把下面的代码加入 ‘onMapReady‘。

//1
map = mapboxMap ?: return
val locationRequestBuilder = LocationSettingsRequest.Builder().addLocationRequest(LocationRequest()
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY))
val locationRequest = locationRequestBuilder?.build()
settingsClient?.checkLocationSettings(locationRequest)?.run {
addOnSuccessListener {
enableLocation()
addOnFailureListener {
val statusCode = (it as ApiException).statusCode
if (statusCode == LocationSettingsStatusCodes.RESOLUTION_REQUIRED) {
val resolvableException = it as? ResolvableApiException
resolvableException?.startResolutionForResult(this@MainActivity, REQUEST_CHECK_SETTINGS)
}

上述代码与 SettingsClient API 相关,我们一步步地来看一下。

  • 首先,初始化 map 变量,因为稍后您将使用它。
  • 接下来,初始化 locationRequestBuilder 并传递应用程序将使用的位置请求。
  • 最后,您使用 locationRequest 来构建位置请求,然后将其传递给 settingsClient 并附加两种类型的侦听器:
    • addOnSuccessListener:请求成功后,您将调用 enableLocation 来启动位置跟踪。
    • addOnFailureListener:当请求失败时,通过查看异常状态代码来检查原因。如果异常是 LocationSettingsStatusCodes.RESOLUTION_REQUIRED,则应用程序应通过调用 startResolutionForResult 来处理该异常。


接下来,您需要通过覆盖 onActivityResult 来处理异常结果。在 onCreate 下面添加以下代码。

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CHECK_SETTINGS) {
if (resultCode == Activity.RESULT_OK) {