业务场景介绍

在打车、运动、导航类应用中,基于当前位置信息在地图上进行展示,并根据位置信息的变化,绘制运动轨迹的场景,现在非常流行。直观的运动轨迹展示,可以给用户带来更好的用户体验。

通过本篇Codelab,我们将基于华为定位服务(Location Kit)、华为地图服务(Map Kit),以用户跑步运动的应用场景为例,详细的为开发者讲解实现运动轨迹这个业务场景的开发过程。

您将建立什么

在这个Codelab中,您将实现通过华为定位服务、地图服务服务实现:

您将会学到什么

您需要什么

硬件要求

软件要求

集成HUAWEI HMS Core能力,需要完成以下准备工作

具体操作,请按照《HUAWEI HMS Core集成准备》中详细说明来完成。

华为定位服务和地图服务可以通过如下两种方式开通,具体开通实现如下:

方式一

AppGallery Connect中选择"我的项目",在项目列表中选择创建的应用,在"项目设置"页面中选择"API管理"。

打开Location KitMap Kit服务。

方式二

华为开发者联盟控制台中,在页面侧边栏,选择"HMS API服务"->"API库",并选择对应项目。

在"API库"中找到定位服务地图服务

进入卡片中,点击"启用"按钮,即可开通服务。

获取配置文件

打开华为开发者联盟AppGallery Connect选择"我的项目",在项目列表中选择创建的应用,在"项目设置"页面中选择"常规",并在项目数据存储位置点击设置按钮。

下载应用中的"agconnect-services.json"文件。

将 "agconnect-services.json"文件移至Android Studio开发工程应用模块的根目录。

配置HMS Core SDK依赖

打开项目级的build.gradle文件。

项目级build.gradle中代码如下:

buildscript { ext.kotlin_version = "1.4.10" repositories { google() jcenter() maven {url 'http://developer.huawei.com/repo/'} } dependencies { classpath "com.android.tools.build:gradle:4.1.1" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.huawei.agconnect:agcp:1.4.0.300' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { google() jcenter() maven {url 'http://developer.huawei.com/repo/'} } }

打开应用级的build.gradle文件。

在"dependencies"中添加定位服务地图服务依赖

dependencies { implementation 'com.huawei.hms:location:{version}' implementation 'com.huawei.hms:maps:{version}' }

点击Sync Now 同步工程。

配置混淆脚本

1.打开Android Studio工程的混淆配置文件proguard-rules.pro
2.加入混淆配置。

-ignorewarnings -keepattributes *Annotation* -keepattributes Exceptions -keepattributes InnerClasses -keepattributes Signature -keepattributes SourceFile,LineNumberTable -keep class com.huawei.hianalytics.**{*;} -keep class com.huawei.updatesdk.**{*;} -keep class com.huawei.hms.**{*;}

3.如果开发者使用了AndResGuard,需要在混淆配置文件中加入AndResGuard允许清单。

"R.string.hms*", "R.string.connect_server_fail_prompt_toast", "R.string.getting_message_fail_prompt_toast", "R.string.no_available_network_prompt_toast", "R.string.third_app_*", "R.string.upsdk_*", "R.layout.hms*", "R.layout.upsdk_*", "R.drawable.upsdk*", "R.color.upsdk*", "R.dimen.upsdk*", "R.style.upsdk*", "R.string.agc*"

地图服务说明

地图服务(Map Kit)给您提供一套地图开发调用的SDK,地图数据覆盖超过200个国家和地区,支持一百多种语言,方便您轻松地在应用中集成地图相关的功能,全方位提升用户体验。

我们将使用Map Kit,来绘制一个基础地图。

地图容器

目前华为地图SDK支持的地图容器有两种,SupportMapFragment、MapView:

MapView是Android View类的子类,允许您将地图放置在Android视图中。与SupportMapFragment非常相似,MapView充当地图的容器,通过HuaweiMap对象展示核心地图功能。在常规地图交互模式下调用API时,MapView类的使用者必须在对应的Activity/Fragment的方法中调用以下方法:onCreate(),onStart(),onResume(),onPause(),onStop(),onDestroy(),onSaveInstanceState(Bundle outState)和onLowMemory()。

在本篇Codelab中,我们使用MapView来创建一个Map示例。

1. 创建MapView布局

我们先创建一个MapActivity,在Activity的布局文件中添加一个MapView,并通过XML文件设置Map的属性。

布局代码

<com.huawei.hms.maps.MapView android:id="@+id/hw_mapview" android:layout_width="match_parent" android:layout_height="match_parent" map:mapType="normal" map:uiCompass="true" map:uiZoomControls="true"/>

2. 实现回调接口并声明需要的对象

MapActivity中,需要实现OnMapReadyCallback接口,并声明MapView对象、HuaweiMap对象、地图上绘制折线所用到的PolylineOptions对象,。

Java代码

public class MapActivity extends AppCompatActivity implements OnMapReadyCallback { private MapView mMapView; private HuaweiMap mHwMap; private PolylineOptions mPolylineOptions; private LocationSource.OnLocationChangedListener mListener; }

Kotlin代码

class MapActivity : AppCompatActivity(), OnMapReadyCallback { private lateinit var mMapView: MapView private var mHwMap: HuaweiMap? = null private var mPolylineOptions: PolylineOptions? = null private var mListener: LocationSource.OnLocationChangedListener? = null }

3.创建MapView实例

MapActivityonCreate()方法中加载MapView,并使用getMapAsync()方法来注册回调方法。

Java代码

// init MapView mMapView = findViewById(R.id.hw_mapview); Bundle mapViewBundle = null; if (savedInstanceState != null) { mapViewBundle = savedInstanceState.getBundle(MAPVIEW_BUNDLE_KEY); } mMapView.onCreate(mapViewBundle); mMapView.getMapAsync(this);

Kotlin代码

// init MapView mMapView = findViewById(R.id.hw_mapview) var mapViewBundle: Bundle? = null if (savedInstanceState != null) { mapViewBundle = savedInstanceState.getBundle(MAPVIEW_BUNDLE_KEY) } mMapView.onCreate(mapViewBundle) mMapView.getMapAsync(this)

4.在OnMapReady回调中,获取HuaweiMap对象。

Java代码

@Override public void onMapReady(HuaweiMap huaweiMap) { mHwMap = huaweiMap; }

Kotlin代码

override fun onMapReady(huaweiMap: HuaweiMap?) { mHwMap = huaweiMap }

5.添加MapView的方法

实现onStart(),onResume(),onPause(),onStop(),onDestroy()和onLowMemory()方法中调用MapView对应的方法。。

Java代码

@Override protected void onStart() { super.onStart(); mMapView.onStart(); } @Override protected void onResume() { super.onResume(); mMapView.onResume(); } @Override protected void onPause() { super.onPause(); mMapView.onPause(); } @Override protected void onStop() { super.onStop(); mMapView.onStop(); } @Override protected void onDestroy() { super.onDestroy(); mMapView.onDestroy(); } @Override public void onLowMemory() { super.onLowMemory(); mMapView.onLowMemory(); }

Kotlin代码

companion object { private const val MAPVIEW_BUNDLE_KEY = "MapViewBundleKey" } override fun onStart() { super.onStart() mMapView.onStart() } override fun onResume() { super.onResume() mMapView.onResume() } override fun onPause() { super.onPause() mMapView.onPause() } override fun onStop() { super.onStop() mMapView.onStop() } override fun onDestroy() { super.onDestroy() mMapView.onDestroy() } override fun onLowMemory() { super.onLowMemory() mMapView.onLowMemory() }

至此基础地图我们已经创建完毕了,现在应用是这个样子的。

定位服务(Location Kit)采用GNSS、Wi-Fi、基站等多途径的混合定位模式进行定位,赋予应用开发者快速、精准地获取用户位置信息的能力,构建全球定位服务能力,助力您发展全球业务。当前定位服务的主要能力包含三个部分:融合定位、活动识别和地理围栏。
本篇Codelab中,我们使用到了Location Kit的融合定位能力。

1. 指定应用权限,

<!-- Location kit needs Location permission--> <uses-permission android:name="android.permission.ACCESS_COARES_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <!-- 调试使用--> <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>

2. 代码中动态申请一下权限(Android 6.0危险权限要求)

Java代码

// SDK<=28 所需权限动态申请 if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { Log.i(TAG, "sdk <= 28 Q"); if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { String[] strings = {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION}; ActivityCompat.requestPermissions(this, strings, 1); } } else { // SDK>28 所需权限动态申请,需添加"android.permission.ACCESS_BACKGROUND_LOCATION"权限 if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, "android.permission.ACCESS_BACKGROUND_LOCATION") != PackageManager.PERMISSION_GRANTED) { String[] strings = {android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_COARSE_LOCATION, "android.permission.ACCESS_BACKGROUND_LOCATION"}; ActivityCompat.requestPermissions(this, strings, 2); } }

Kotlin代码

// SDK ≤ 28 Dynamically Applying for Required Permissions if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { Log.i(TAG, "sdk <= 28 Q") if (ActivityCompat.checkSelfPermission( this, Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( this, Manifest.permission.ACCESS_COARSE_LOCATION ) != PackageManager.PERMISSION_GRANTED ) { val strings = arrayOf( Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION ) ActivityCompat.requestPermissions(this, strings, 1) } } else { // //Dynamicly apply for permissions required for SDK > 28. Add the android.permission.ACCESS_BACKGROUND_LOCATION permission. if (ActivityCompat.checkSelfPermission( this, Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( this, Manifest.permission.ACCESS_COARSE_LOCATION ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( this, Manifest.permission.ACCESS_BACKGROUND_LOCATION ) != PackageManager.PERMISSION_GRANTED ) { val strings = arrayOf( Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_BACKGROUND_LOCATION ) ActivityCompat.requestPermissions(this, strings, 2) } }

3. 在MapActivity中创建一个定位服务实例

Java代码

protected void onCreate(@Nullable Bundle savedInstanceState) { fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this); }

Kotlin代码

override fun onCreate(savedInstanceState: Bundle?) { fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this) }

4. 检查当前设备的定位设置

定位服务提供了检查设备定位相关设置的能力。我们先创建一个检测方法checkLocationSettings(),通过LocationServicesgetSettingsClient(Activity activity)获取SettingsClient实例,然后调用checkLocationSettings(LocationSettingsRequest locationSettingsRequest)接口获取定位设置结果。

Java代码

private void checkLocationSettings() { LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder(); LocationSettingsRequest locationSettingsRequest = builder.build(); SettingsClient settingsClient = LocationServices.getSettingsClient(this); settingsClient.checkLocationSettings(locationSettingsRequest) .addOnSuccessListener(new OnSuccessListener<LocationSettingsResponse>() { @Override public void onSuccess(LocationSettingsResponse locationSettingsResponse) { requestLocationUpdate(); } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { int statusCode = ((ApiException) e).getStatusCode(); switch (statusCode) { case LocationSettingsStatusCodes.RESOLUTION_REQUIRED: try { ResolvableApiException rae = (ResolvableApiException) e; rae.startResolutionForResult(MapActivity.this, 0); } catch (IntentSender.SendIntentException sie) { } break; } } }); }

Kotlin代码

private fun checkLocationSettings() { val builder: LocationSettingsRequest.Builder = LocationSettingsRequest.Builder() val locationSettingsRequest: LocationSettingsRequest = builder.build() val settingsClient: SettingsClient = LocationServices.getSettingsClient(this) settingsClient.checkLocationSettings(locationSettingsRequest) .addOnSuccessListener { requestLocationUpdate() }.addOnFailureListener { e -> when ((e as ApiException).statusCode) { LocationSettingsStatusCodes.RESOLUTION_REQUIRED -> try { val rae: ResolvableApiException = e as ResolvableApiException rae.startResolutionForResult(this@MapActivity, 0) } catch (sie: SendIntentException) { } } } }

5. 持续的获取位置信息

如果您希望应用可以持续获取设备位置,可以使用华为定位服务提供的requestLocationUpdates()接口。该接口通过调用您已经定义的LocationCallback类中onLocationResult()回调方法返回一个包含位置信息的LocationResult对象。

Java

private void requestLocationUpdate() { mLocationRequest = new LocationRequest(); mLocationRequest.setInterval(5000); mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); mLocationCallback = new LocationCallback() { @Override public void onLocationResult(LocationResult locationResult) { super.onLocationResult(locationResult); writeGpsData2Sdcard(locationResult.getLastLocation()); if (mIsRunning) { updateLocation(locationResult.getLastLocation()); } } @Override public void onLocationAvailability(LocationAvailability locationAvailability) { super.onLocationAvailability(locationAvailability); } }; fusedLocationProviderClient .requestLocationUpdates(mLocationRequest, mLocationCallback, Looper.getMainLooper()) .addOnSuccessListener(new OnSuccessListener<Void>() { @Override public void onSuccess(Void aVoid) { Log.i(TAG, "request location updates success"); } }) .addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { Log.e(TAG, "request location updates failed, error: " + e.getMessage()); } }); }

Kotlin

private fun requestLocationUpdate() { mLocationRequest = LocationRequest() mLocationRequest!!.interval = 5000 mLocationRequest!!.priority = LocationRequest.PRIORITY_HIGH_ACCURACY mLocationCallback = object : LocationCallback() { override fun onLocationResult(locationResult: LocationResult) { super.onLocationResult(locationResult) writeGpsData2Sdcard(locationResult.lastLocation) if (mIsRunning) { processLocationChange(locationResult.lastLocation) } } override fun onLocationAvailability(locationAvailability: LocationAvailability?) { super.onLocationAvailability(locationAvailability) } } fusedLocationProviderClient ?.requestLocationUpdates(mLocationRequest, mLocationCallback, Looper.getMainLooper()) ?.addOnSuccessListener { Log.i(TAG, "request location updates success") } ?.addOnFailureListener { e -> Log.e( TAG, "request location updates failed, error: " + e.message ) } }

当我们不再需要接收位置更新时,应当停止位置更新,以便于降低功耗。要停止位置更新,可以调用removeLocationUpdates(),传入与requestLocationUpdates()接口相对应的LocationCallback。

Java代码

private void removeLocationUpdatesWithCallback() { try { Task<Void> voidTask = fusedLocationProviderClient.removeLocationUpdates(mLocationCallback); voidTask.addOnSuccessListener(new OnSuccessListener<Void>() { @Override public void onSuccess(Void aVoid) { } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { } }); } catch (Exception e) { } }

Kotlin代码

private fun removeLocationUpdatesWithCallback() { try { val voidTask: Task<Void> = fusedLocationProviderClient!!.removeLocationUpdates(mLocationCallback) voidTask.addOnSuccessListener { }.addOnFailureListener { } } catch (e: Exception) { } }

在前两个步骤中,我们已经完成了地图服务定位服务的基础功能实现。下面我们将要将两种能力结合起来,实现跑步运动时记录运动轨迹,并计算距离速度的场景。

1. 设置我的位置图层的位置源

在OnMapReady回调中,添加位置源,

JAVA代码

// Add Location Source mHwMap.setLocationSource(new LocationSource() { @Override public void activate(OnLocationChangedListener onLocationChangedListener) { mListener = onLocationChangedListener; } @Override public void deactivate() { } });

Kotlin代码

//Add Location Source mHwMap?.setLocationSource(object : LocationSource { override fun activate(onLocationChangedListener: LocationSource.OnLocationChangedListener?) { mListener = onLocationChangedListener } override fun deactivate() {} })

2. 获取当前的定位信息,更新Map的镜头

在OnMapReady回调中,添加获取位置信息的代码,并更新Map的镜头位置。

JAVA代码

//Obtains the current position and updates the map camera. try { Task<Location> lastLocation = fusedLocationProviderClient.getLastLocation(); lastLocation.addOnSuccessListener(new OnSuccessListener<Location>() { @Override public void onSuccess(Location location) { mListener.onLocationChanged(location); if (mListener != null) { mListener.onLocationChanged(location); CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom( new LatLng(location.getLatitude(), location.getLongitude()), 15f); mHwMap.animateCamera(cameraUpdate); } } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { } }); } catch (Exception e) { }

Kotlin代码

//Obtains the current position and updates the map camera. try { val lastLocation: Task<Location> = fusedLocationProviderClient!!.lastLocation lastLocation.addOnSuccessListener { location -> mListener!!.onLocationChanged(location) if (mListener != null) { mListener!!.onLocationChanged(location) val cameraUpdate: CameraUpdate = CameraUpdateFactory.newLatLngZoom( LatLng(location.latitude, location.longitude), 15f ) mHwMap!!.animateCamera(cameraUpdate) } }.addOnFailureListener { Log.d(TAG, "onMapReady: Obtains the current position failure") } } catch (e: Exception) { }

设置完位置源后,我们就可以在地图上,看见我们的位置了。

3. 添加跑步按钮及开关方法。

我们先添加展示运动数据的布局

<LinearLayout android:layout_width="match_parent" android:layout_height="100dp" android:layout_marginTop="60dp" android:layout_marginStart="15dp" android:layout_marginEnd="15dp" android:background="@color/bgWhite" android:orientation="horizontal"> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:layout_marginStart="20dp" android:layout_marginTop="20dp" android:orientation="vertical"> <TextView android:id="@+id/tv_distance" android:layout_width="match_parent" android:layout_height="wrap_content" android:ellipsize="end" android:gravity="center_horizontal" android:maxLength="8" android:text="0.00" android:textColor="#000000" android:textSize="25sp" android:textStyle="bold" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:layout_marginStart="6dp" android:layout_marginBottom="2.5dp" android:text="km" android:textColor="#88000000" android:textSize="18sp" /> </LinearLayout>

再在MapActivity中,添加展示运动数据界面
JAVA代码

// Add the exercise data display UI. mTvSpeed = findViewById(R.id.tv_speed); mTvDistance = findViewById(R.id.tv_distance); mTime = findViewById(R.id.cm_time); mTvStart = findViewById(R.id.tv_start); mTvStart.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { processStartClick(); } });

Kotlin代码

// Add the exercise data display UI. mTvSpeed = findViewById(R.id.tv_speed) mTvDistance = findViewById(R.id.tv_distance) mTime = findViewById(R.id.cm_time) mTvStart = findViewById(R.id.tv_start) mTvStart!!.setOnClickListener(View.OnClickListener { processStartClick() })

开始跑步时,在processStartClick()方法中添加以下代码

JAVA代码

mIsRunning = true; mPath.reset(); mPath.setStartTime(System.currentTimeMillis()); mHandler.post(mTimeRunnable); mTvStart.setText("Stop");

Kotlin代码

mIsRunning = true mPath.reset() mPath.startTime = System.currentTimeMillis() mHandler.post(mTimeRunnable) mTvStart!!.text = "Stop"

结束跑步时,在processStartClick()方法中添加以下代码

JAVA代码

mIsRunning = false; mPath.setEndTime(System.currentTimeMillis()); mTvStart.setText("Start"); mHandler.removeCallbacks(mTimeRunnable); if (mPath.getPathLine().size() > 0) { mPath.setEndPoint(mPath.getPathLine().get(mPath.getPathLine().size() - 1)); if (null != mMarkerStart && null != mMarkerEnd) { mMarkerStart.remove(); mMarkerEnd.remove(); } MarkerOptions StartPointOptions = new MarkerOptions() .icon(BitmapDescriptorFactory.fromResource(R.mipmap.map_marker_bubble_azure_small)) .position(mPath.getStartPoint()); StartPointOptions.title("Start Point"); StartPointOptions.snippet("Start Point"); mMarkerStart = mHwMap.addMarker(StartPointOptions); MarkerOptions EndPointOptions = new MarkerOptions() .icon(BitmapDescriptorFactory.fromResource(R.mipmap.map_marker_bubble_azure_small)) .position(mPath.getEndPoint()); EndPointOptions.title("End Point"); EndPointOptions.snippet("End Point"); mMarkerEnd = mHwMap.addMarker(EndPointOptions); }

Kotlin代码

mIsRunning = false mPath.endTime = (System.currentTimeMillis()) mTvStart!!.text = "Start" mHandler.removeCallbacks(mTimeRunnable) if (mPath.mPathLinePoints!!.size > 0) { mPath.endPoint = (mPath.mPathLinePoints!!.get(mPath.mPathLinePoints!!.size - 1)) if (null != mMarkerStart && null != mMarkerEnd) { mMarkerStart!!.remove() mMarkerEnd!!.remove() } val startPointOptions: MarkerOptions = MarkerOptions() .icon(BitmapDescriptorFactory.fromResource(R.mipmap.map_marker_bubble_azure_small)) .position(mPath.mStartPoint) startPointOptions.title("Start Point") startPointOptions.snippet("Start Point") mMarkerStart = mHwMap!!.addMarker(startPointOptions) val endPointOptions: MarkerOptions = MarkerOptions() .icon(BitmapDescriptorFactory.fromResource(R.mipmap.map_marker_bubble_azure_small)) .position(mPath.mEndPoint) endPointOptions.title("End Point") endPointOptions.snippet("End Point") mMarkerEnd = mHwMap!!.addMarker(endPointOptions) }

将processStartClick()方法绑定到"Start"按钮上。

4. 根据当前位置信息变化,绘制运动轨迹

我们创建一个处理方法processLocationChange(Location location)。在这个方法中我们根据位置信息,在地图上绘制折线。
JAVA代码

private void processLocationChange(Location location) { LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude()); // Draw a motion track on a map mPolylineOptions.add(latLng); mHwMap.addPolyline(mPolylineOptions); // Updating Map Kit Camera if (mListener != null) { mListener.onLocationChanged(location); CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom( new LatLng(location.getLatitude(), location.getLongitude()), 15f); mHwMap.animateCamera(cameraUpdate); } }

Kotlin代码

private fun processLocationChange(location: Location) { val latLng = LatLng(location.latitude, location.longitude) if (mPath.mStartPoint == null) { mPath.mStartPoint = latLng } mPath.addPoint(latLng) val distance: Float = mPath.updateDistance() val sportMile = distance / 1000.0 if (mSeconds > 0) { val distribution = mSeconds.toDouble() / 60.0 / sportMile mPath.setDistribution(distribution) mTvSpeed!!.text = mDecimalFormat.format(distribution) mTvDistance!!.text = mDecimalFormat.format(sportMile) } else { mPath.setDistribution(0.0) mTvSpeed!!.text = "0.00" mTvDistance!!.text = "0.00" } // Draw a motion track on a map mPolylineOptions!!.add(latLng) mHwMap!!.addPolyline(mPolylineOptions) // Updating Map Kit Camera if (mListener != null) { mListener!!.onLocationChanged(location) val cameraUpdate: CameraUpdate = CameraUpdateFactory.newLatLngZoom( LatLng(location.latitude, location.longitude), 15f ) mHwMap!!.animateCamera(cameraUpdate) } }

在位置更新的回调方法requestLocationUpdate()中,我们调用processLocationChange()方法。
JAVA代码

private void requestLocationUpdate() { mLocationCallback = new LocationCallback() { @Override public void onLocationResult(LocationResult locationResult) { super.onLocationResult(locationResult); if (mIsRunning) { processLocationChange(locationResult.getLastLocation()); } } };

Kotlin代码

mLocationCallback = object : LocationCallback() { override fun onLocationResult(locationResult: LocationResult) { super.onLocationResult(locationResult) writeGpsData2Sdcard(locationResult.lastLocation) if (mIsRunning) { processLocationChange(locationResult.lastLocation) } } override fun onLocationAvailability(locationAvailability: LocationAvailability?) { super.onLocationAvailability(locationAvailability) } }

当我们持续获得实时位置更新的时候,就可以绘制运动轨迹了。

华为定位服务提供了模拟定位能力,依托这项能力,我们可以用于应用调试。

1. 打开模拟位置开关

我们需要在测试设备中的开发人员选项中,选择模拟位置信息的应用。

2. 加载模拟GPS数据

我们获取一组模拟GPS数据,本篇Codelab使用的Java GPS数据存放在MockGpsData.java中,Kotlin GPS数据保存在MockGpsData.kt中。
在我们创建地图及获取定位之前,加载模拟GPS数据,代码如下:
JAVA代码

Task<Void> voidTask = fusedLocationProviderClient.setMockMode(true); voidTask.addOnSuccessListener(new OnSuccessListener<Void>() { @Override public void onSuccess(Void aVoid) { Toast.makeText(MainActivity.this, "setMockMode onSuccess", Toast.LENGTH_SHORT).show(); mHandler.post(new Runnable() { int point = 0; @Override public void run() { if (point + 1 >= MockGpsData.POINTS.length) { return; } double latitude = MockGpsData.POINTS[point++]; double longitude = MockGpsData.POINTS[point++]; final Location mockLocation = new Location(LocationManager.GPS_PROVIDER); mockLocation.setLongitude(longitude); mockLocation.setLatitude(latitude); Task<Void> voidTask = fusedLocationProviderClient.setMockLocation(mockLocation); voidTask.addOnSuccessListener(new OnSuccessListener<Void>() { @Override public void onSuccess(Void aVoid) { } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { } }); mHandler.postDelayed(this, 100); } }); } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { Toast.makeText(MainActivity.this, "setMockMode onFailure:" + e.getMessage(), Toast.LENGTH_SHORT).show(); } });

Kotlin代码

val voidTask: Task<Void> = fusedLocationProviderClient!!.setMockMode(true) voidTask.addOnSuccessListener(object : OnSuccessListener<Void?> { override fun onSuccess(aVoid: Void?) { Toast.makeText(this@MainActivity, "setMockMode onSuccess", Toast.LENGTH_SHORT) .show() mHandler.post(object : Runnable { var point = 0 override fun run() { if (point + 1 >= MockGpsData.POINTS.size) { return } val latitude: Double = MockGpsData.POINTS.get(point++) val longitude: Double = MockGpsData.POINTS.get(point++) val mockLocation = Location(LocationManager.GPS_PROVIDER) mockLocation.longitude = longitude mockLocation.latitude = latitude val voidTask: Task<Void> = fusedLocationProviderClient!!.setMockLocation(mockLocation) voidTask.addOnSuccessListener(object : OnSuccessListener<Void?> { override fun onSuccess(aVoid: Void?) {} }).addOnFailureListener(object : OnFailureListener { override fun onFailure(e: Exception?) {} }) mHandler.postDelayed(this, 100) } }) } }).addOnFailureListener { e -> if (e != null) { Toast.makeText( this@MainActivity, "setMockMode onFailure:" + e.message, Toast.LENGTH_SHORT ).show() } }

加载完成后,我们便可以使用模拟GPS数据进行调试。

干得好,您已经成功完成了Codelab并学到了:

参考文档

您可以阅读下面链接,了解更多相关的信息。

您可以点击下方按钮下载源码。

源码下载

已复制代码