Xamarin.Forms 使用高德地图SDK
效果视频:
高德地图SDK版本
3DMap-7.4.0-地图
Location-4.9.0-位置
Navi-3DMap-7.4.0-导航与地图
Search-7.3.0-搜索
Track-Track1.3.0Location4.9.0-猎鹰与位置
教程
(详细代码解释参看文章底部阅读原文)
1.Xamarin项目Android端安装安卓nuget包(支持按需求分包安装)
*注Navi-3DMap与3DMap不能同时安装,因为导航sdk已经含有地图sdk
Track与Loaction不能同时安装,因为猎鹰sdk已经含有位置sdk
Install-Package XamarinLibrary.Xamarin.Android.Amap.Api.Location -Version 4.9.0<br>Install-Package XamarinLibrary.Xamarin.Android.Amap.Api.Search -Version 7.3.0<br>Install-Package XamarinLibrary.Xamarin.Android.Amap.Api.Track -Version 1.3.0<br>Install-Package XamarinLibrary.Xamarin.Android.Amap.Api.3DMap -Version 7.4.0<br>Install-Package XamarinLibrary.Xamarin.Android.Amap.Api.Navi3DMap -Version 7.4.0
2.参考高德地图sdk文档配置Xamarin.Android/Properties/AndroidManifest.xml文件的key与权限,SDK用法对照调用C#相应函数;
3.在Xamarin方面将高德地图相关控件使用Renderer方案在Xamarin.Forms显示;
4.涉及Xamarin.Forms、Xamarin.Android的layout.xml以及Renderer的使用时,可以参考源码示例的代码进行相应处理。(涉及Android的Inflate函数相关知识)
5.涉及Xamarin.Forms、Xamarin.Android间消息的通讯可以参考源码示例,使用Xamarin.Forms提供的MessagingCenter处理。(涉及消息发布订阅相关知识)
其它资料
Xamarin.Forms 演示高德地图SDK的Sample源码:
https://github.com/jingliancui/XamarinFormsAMapSDKSample
原生Android的高德地图SDK的使用方法:
https://lbs.amap.com/
微软官方Renderer教程示例:
https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/custom-renderer/video-player/
Xamarin.Forms消息发布与订阅教程
https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/messaging-center
消息发布与订阅教程
教程:
发布消息
Xamarin.Forms.MessagingCenter.Send
消息订阅
Xamarin.Forms.MessagingCenter.Subscribe
取消消息订阅
Xamarin.Forms.MessagingCenter.Unsubscribe
tips:
注意renderer进行pub/sub的时候要进行相关的unsubscribe,否则app会crash
后话:
Xamarin.Forms库本身已经带有支持消息发布订阅模式(Pub/Sub)的实现了。
一个比较正常的用法是直接在share层(.Net Standard引用了Xamarin.Forms的一层)内进行业务开发,那传递参数的类型都可以是各种类,如.net standard类或者forms的ui类。
另外一个场景是,当我们在进行层与层之间的消息触发的时候,需要注意使用通用的类,比如object、string、int等元类,也可以用自定义类,注意不要使用native独有的类即可,因为假设我们的解决方案是Xamarin.Forms+iOS+Android,每一个native层特有的原生类是Share层没有的,例如需要iOS的NSString数据发送到.Net Standard,因为.Net Standard是没有mono的NSString的。这时候可能会想到让.Net Standard层引用native层达到两层的类都共用,但ide会报错不能互相套娃,因为native层已经引用.Net Standard层了。
另外一个需要注意的是教程提到renderer内使用pub/sub注意的事项。
Xamarin.Forms使用Pub/Sub发布订阅进行native层与share层的通讯
Part 1 高德地图SDK与Nuget相关的资料
高德地图SDK在原生Android方面,提供定制下载与使用gradle从Maven或JCenter集成使用,定制下载的方式浏览这个网址:相关下载-Android 地图SDK | 高德地图API,使用gradle集成的方式浏览这个网址:Android Studio 配置工程-创建工程-开发指南-Android 地图SDK。
定制下载的方式是开发者勾选需要集成的功能后,高德的网站自动将选择的功能合成一个jar包或aar包,而gradle集成的方式像Nuget安装库一样,安装开发者自己选择的库。在高德SDK没有支持gradle集成前只能使用前者,而前者的缺点就是当所有包分开完成绑定工作后,在App端编译会遇到api冲突的异常。而gradle方式则没有这种问题。
不过要注意的是导航SDK已经包含了地图SDK、猎鹰SDK已经包含了定位SDK;所以导航SDK与地图SDK不能同时集成在APP中,只能两者选一;猎鹰SDK与定位SDK不能同时集成在APP中只能两者选一。
Install-Package XamarinLibrary.Xamarin.Android.Amap.Api.Location -Version 4.9.0 Install-Package XamarinLibrary.Xamarin.Android.Amap.Api.Search -Version 7.3.0 Install-Package XamarinLibrary.Xamarin.Android.Amap.Api.Track -Version 1.3.0 Install-Package XamarinLibrary.Xamarin.Android.Amap.Api.3DMap -Version 7.4.0 Install-Package XamarinLibrary.Xamarin.Android.Amap.Api.Navi3DMap -Version 7.4.0
Part 2 调用SDK时Xamarin.Android与Xamarin.Forms协调交互的方式
- 代码项目层级关系
SampleApp这一层其实是.Net Standard类库,而SampleApp.Android这一层则可以看作是main函数所在的一层,通常会把这一层叫做native层。查看native层的Reference会看到它引用了SampleApp这一层,记住这个层级关系有利于分清楚集成第三方SDK与层级调用的关系。
而熟悉.Net Core开发工作的开发者就比较熟悉.Net Standard的作用–跨平台,所以.Net Standard支持的像一系列异步接口、HttpClient、System.Text.Json等这些常用的功能也能直接或通过Nuget安装的方式使用。
但是要注意的是,也因为.Net Standard的跨平台性质,不要将native层的类放到.Net Standard中,例如Android Native的Android.App.Activity类。因为Xamarin.Android能引用.Net Standard,那其他native层如Xamarin.iOS、UWP、WPF也能引用。
所以很多入门Xamarin的开发者的一个问题是Forms的东西怎么在Android或者iOS显示,Android或者iOS的东西怎么在Forms显示。下文讲到的Renderer与MessagingCenter就是其中的两个解决方案。
- 集成SDK
示例代码中包含了全功能的演示,所以引用了导航SDK(含地图SDK)、猎鹰SDK(含位置SDK)与搜索SDK
根据高德官方文档配置key和配置权限这两节教程,配置native层Properties/AndroidManifest.xml文件即可
- Renderer
界面控件在Android中可以通过xml或者代码创建:
MapView:
mapView = new MapView(this);
AMapNaviView:
<com.amap.api.navi.AMapNaviView android:id="@+id/navi_view" android:layout_width="match_parent" android:layout_height="match_parent"/>
而Xamarin开发需要将这些原生的控件显示到Forms层中,就可以用到Renderer方案,重点是要在override的OnElementChanged函数中将控件设值。
protected override void OnElementChanged(ElementChangedEventArgs<XAMapNaviView> e) { mRelativeLayout = (Context as MainActivity).naviRelativeLayout; (Context as MainActivity).navi.StartNavi(NaviType.Emulator); SetNativeControl(mRelativeLayout); }
以上代码中没有使用基类提供的Inflate函数去获取xml的控件,而是放在MainActivity中使用Inflate获取,是因为SDK中的控件需要进行生命周期管理。一般情况下没有生命周期管理的控件都可以在Renderer中使用Inflate函数获取。
- MessagingCenter
除了原生控件需要在Forms层显示,还需要数据在Forms层与Native层间互相传递,协调代码工作。
这里以高德SDK中查询天气数据为案例进行讲解。
熟悉WPF或者UWP开发工作的.Net开发者比较熟悉以下Forms层中的Xaml代码
<StackLayout> <Label Text="其他数据搜索功能请参照官方文档自行实现"></Label> <Label Text="本示例只作天气查询演示"></Label> <Button Text="Weather Query" x:Name="WeatherBtn" Clicked="WeatherBtn_Clicked"></Button> <Label Text="北京天气"></Label> <Label x:Name="ReportTimeLabel"></Label> <Label x:Name="WeatherLabel"></Label> <Label x:Name="TemperatureLabel"></Label> <Label x:Name="WindDirectionLabel"></Label> <Label x:Name="WindPowerLabel"></Label> <Label x:Name="HumidityLabel"></Label> </StackLayout>
用户点击Button,触发了这个Button的Clicked事件
public const string QueryWeather = "QueryWeather"; private void WeatherBtn_Clicked(object sender, EventArgs e) { MessagingCenter.Send(new object(), QueryWeather); }
而事件函数的函数体内只是使用了MessagingCenter的Send函数
通过查找QueryWeather常量的引用可以看到,消息从SampleApp层Send,在SampleApp.Android层Subscribe
这就是MessagingCenter的用法,MessagingCenter是Pub/Sub设计模式(发布订阅设计模式)的一种实现的类。
订阅了这个消息的native层,在forms层发布消息的时候就会收到触发调用的通知。
继续将业务往下发展
MessagingCenter.Subscribe<object>(this, SearchPage.QueryWeather, sender => { //检索参数为城市和天气类型,实况天气为WEATHER_TYPE_LIVE、天气预报为WEATHER_TYPE_FORECAST var mquery = new WeatherSearchQuery("北京", WeatherSearchQuery.WeatherTypeLive); var mweathersearch = new WeatherSearch(this); mweathersearch.SetOnWeatherSearchListener(new OnWeatherSearchListener()); mweathersearch.Query=mquery; mweathersearch.SearchWeatherAsyn(); //异步搜索 });
当订阅者收到通知后触发查询天气的函数(高德SDK查询天气相关的函数)
查询天气的函数执行成功或者失败后,都会触发OnWeatherSearchListener类内的回调函数OnWeatherLiveSearched
如果天气查询成功了可以使用MessagingCenter将天气数据打包后发送回给forms层,为什么是打包后发送而不是将高德sdk的result变量发送呢,因为高德sdk的result变量不是跨平台的,是不存在于.Net Standard中的,是带有java平台相关的类或者成员的。
所以我们可以像服务器开发工作一般的处理方法一样,使用DTO(data transfer object)传递数据,DTO在这里就只是.Net Standard中的一个自定义类
public class WeatherModel { public string ReportTime { get; set; } public string Weather { get; set; } public string Temperature { get; set; } public string WindDirection { get; set; } public string WindPower { get; set; } public string Humidity { get; set; } }
var weatherlive = weatherLiveResult.LiveResult; var model = new WeatherModel { ReportTime= weatherlive.ReportTime, Humidity= weatherlive.Humidity, Temperature= weatherlive.Temperature, Weather= weatherlive.Weather, WindDirection= weatherlive.WindDirection, WindPower= weatherlive.WindPower }; //发回给forms层 MessagingCenter.Send(new object(), SearchPage.QueryWeatherOk, model);
再通过查找QueryWeatherOK常量的引用,这时会看到接收者就是Sample.App层,而发送者就是Sample.Android层
最后在Forms层将数据设置到Xaml中的Label控件完成整个天气的查询业务
protected override void OnAppearing() { MessagingCenter.Subscribe<object, WeatherModel>(this, QueryWeatherOk, (sender, args) => { Xamarin.Essentials.MainThread.BeginInvokeOnMainThread(() => { ReportTimeLabel.Text = $"{args.ReportTime}发布"; WeatherLabel.Text = args.Weather; TemperatureLabel.Text = $"{args.Temperature}°"; WindDirectionLabel.Text = $"吹{args.WindDirection}风"; WindPowerLabel.Text = $"风力{args.WindPower}级"; HumidityLabel.Text = $"湿度{args.Humidity}"; }); }); }
总结一下整个代码的流程如下图,Forms层发送消息,Native层订阅消息后查询天气,天气查询的回调函数将结果回发到Forms层,Forms层通过UI控件显示数据。
App代码参看:https://github.com/jingliancui/XamarinFormsAMapSDKSample
后记
对比之前转换的全功能的包整体太大,会导致有些不会被用到的库也会添加到代码项目中,现在使用gradle的包进行重新的转换工作,开发者可以根据项目的需求下载对应的nuget包进行安装使用了。
本文综合微信公众号移动开发和人工智能、知乎文章。
知乎原文:https://zhuanlan.zhihu.com/p/139712723