做⼀个⾼德地图的 iOS / Android MAUI 控件(下)

我们已经介绍了如何通过 .NET 绑定 iOS 原⽣库 ,本篇开始介绍⼀下如何通过 .NET 绑定 Android 原⽣库。

Android 的库

Android 的库以 .jar 做打包, 通过⼯具你可以将多个 .jar 完成绑定,然后通过 C# 调⽤原⽣的 Java 库。对⽐ 起 iOS , Android 的库绑定简单很多。 

从上图可以看到 Xamarin.Android / .NET for Android 通过使⽤托管可调⽤包装器 (MCW) 实现绑定。MCW 是⼀个 JNI 桥,在托管代码需要调⽤ Java 代码时会使⽤它。托管可调⽤包装器还⽀持对 Java 类型进⾏⼦类 化以及覆盖 Java 类型的虚拟⽅法。同样,每当 Android 运⾏时 (ART) 代码需要调⽤托管代码时,它都会通 过另⼀个称为 Android 可调⽤包装器 (ACW) 的 JNI 桥来实现。

创建⼀个 Android 原⽣库绑定项⽬

通过命令⾏创建⼀个 Android 原⽣库绑定项⽬

dotnet new android-bindinglib -o Droid.AMap

进⼊该项⽬我们看看⽂件结构

项⽬⾥⾯有 Transforms ⽂件夹有对应的三个 xml ⽂件,分别是 EnumFields.xml ,EnumMethods.xml , Metadata.xml , 各⾃作⽤如下 :

  • MetaData.xml – 允许对最终 API 进⾏更改,例如更改⽣成的绑定的命名空间。 

  • EnumFields.xm– 包含 Java int 常量与 C# enums 之间的映射。

  • EnumMethods.xml – 允许将⽅法参数和返回类型从 Java int 常量更改为 C# enums 

其中 MetaData.xml ⽂件是这些⽂件中的最常⻅的导⼊,因为它允许对绑定进⾏⼀般⽤途的更改,例如:重命名命名空间、类、⽅法或字段,使其遵循 .NET 约定。 

删除不需要的命名空间、类、⽅法或字段。 

将类移到不同的命名空间。 

添加其他⽀持类以使绑定的设计遵循 .NET 框架模式。 

▌把 jar ⽂件添加到绑定项⽬ 

在项⽬中添加 Jars ⽬录 ,把⾼德地图的 jar 包添加到该⽬录下 ,并把 arm64-v8a ,armeabi-v7a ,x86_64 这三个⽬录添加进来

添加完成后,修改 .csproj ⽂件 

<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net6.0-android</TargetFramework> <SupportedOSPlatformVersion>21</SupportedOSPlatformVersion> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> </PropertyGroup> <ItemGroup> <EmbeddedNativeLibrary Include="Jars\arm64-v8a\libAMapSDK_MAP_v9_3_0.so" /> <EmbeddedNativeLibrary Include="Jars\armeabiv7a\libAMapSDK_MAP_v9_3_0.so" /> <EmbeddedNativeLibrary Include="Jars\x86_64\libAMapSDK_MAP_v9_3_0.so"/> </ItemGroup> <ItemGroup> <TransformFile Include="Transforms\Metadata.xml" /> <TransformFile Include="Transforms\EnumFields.xml" /> <TransformFile Include="Transforms\EnumMethods.xml" /> </ItemGroup> <ItemGroup> <EmbeddedJarInclude="Jars\AMap3DMap_9.3.0_AMapSearch_9.2.0_AMapLocation_6.1.0_20220608.jar" /> </ItemGroup></Project>

这样就把项⽬添加好了,没有像 iOS 原⽣库绑定那么繁琐。然后编译⼀下 ,凡尔赛 + 星⾠⼤海了

排雷工作

看⻅这么多错,真的要考虑⼀下是不是放弃,其实这也⾮常治愈,我们逐个来排雷。

  • 'PoiCreator' does not implement interface member 'IParcelableCreator.NewArray(int)'. 

  • 'PoiCreator.NewArray(int)' cannot implement 'IParcelableCreator.NewArray(int)' 

错误对应的是这个⽅法 ,实际就是返回类型出错了,我们先根据源⽂件看看 path 路径就可以解决 

// Metadata.xml XPath method reference:path="/api/package[@name='com.amap.api.maps.model']/class[@name='PoiCreator']/method[@name='newArray' and count(parameter)=1 and parameter[1][@type='int']]"[Register ("newArray", "(I)[Lcom/amap/api/maps/model/Poi;","GetNewArray_IHandler")]public virtual unsafe global::Com.Amap.Api.Maps.Model.Poi[]? NewArray (intp0){
   
   const string __id = "newArray.(I)[Lcom/amap/api/maps/model/Poi;";try {
   
   JniArgumentValue* __args = stackalloc JniArgumentValue [1];__args [0] = new JniArgumentValue (p0);var __rm = _members.InstanceMethods.InvokeVirtualObjectMethod(__id, this, __args);return (global::Com.Amap.Api.Maps.Model.Poi[]?)JNIEnv.GetArray (__rm.Handle, JniHandleOwnership.TransferLocalRef, typeof(global::Com.Amap.Api.Maps.Model.Poi));} finally {
   
   }}

确认好后,需要在 Metadata.xml 做添加 

<attrpath="/api/package[@name='com.amap.api.maps.model']/class[@name='PoiCreator']/method[@name='newArray' and count(parameter)=1 and parameter[1][@type='int']]" name="managedReturn">Java.Lang.Object[]</attr>
  • The type 'AMap' already contains a definition for 'MarkerDragEnd' 

这个是重复定义导致的,只需要添加如下代码删除就可以了,如

<remove-nodepath="/api/package[@name='com.amap.api.maps']/interface[@name='AMap.OnCameraChangeListener']" />
  • 'BusLineSearch': member names cannot be the same as their enclosing type 

重命名导致的错误 ,把名字修改⼀下即可,如 

<attrpath="/api/package[@name='com.amap.api.services.busline']/class[@name='BusLineSearch']" name="managedName">AmapBusLineSearch</attr>
  • cannot change access modifiers when overriding 'protected' 

重载的时候出现权限问题,这个时候你需要的是把权限修正好,如

<attrpath="/api/package[@name='com.amap.api.maps.model']/class[@name='PolygonOptions']/method[@name='getUpdateFlags' and count(parameter)=0]"name="visibility">protected</attr>

解决上述的所有问题,基本上就可以治愈了,当编译通过⼀刻你会⾮常兴奋

找个 .NET for Android 项⽬看看

 ⼤家可以去我的 GitHub 下载该示例

  • https://github.com/kinfey/AMapMAUIControls/tree/main/samples/Droid.Bindings 

Android 的原⽣绑定⽐ iOS 的简单得多,所以更容易⼊⼿。希望各位⼩伙伴能多动⼿,有时候也是⼀个很好的体验。经过学习,相信⼤家也掌握了如何⽤ .NET 绑定 iOS 和 Android 的原⽣库了。

我们知道 MAUI 是开发跨平台应⽤的解决⽅案 ,⽤ C# 可以直接把 iOS , Android , Windows , macOS , Linux ,Tizen 等应⽤开发出来。那我们在这个框架除了⽤底层⾃定义的 UI 控件外,如果我们要⽤如⾼德地图这样的 第三⽅控件,要如何做呢?接下来我就和⼤家介绍⼀下。

如果你还没有学习原⽣库绑定的知识,可以到以下链接学习相关内容:

在上⾯两个例⼦中我们学习到把原⽣⾼德的 iOS / Android SDK 绑定,也⽤ .NET for iOS 和 .NET for Android 进⾏了调⽤。但要⽤ MAUI 就意味着调⽤⽅式改变, ⼀次性编写多平台使⽤。要实现这个效果,先看看 MAUI 的基础架构。

我们可以清楚看到, MAUI 除了公⽤的 xmal ⽂件外, 实际上也把特定平台的⼀些设定放置到 Platforms 的⽂件夹内, Platforms 的⾃⽂件夹就是对应的平台。我们知道可以根据不同平台去渲染平台界⾯。这就是我们 常说的⾃定义平台控件了。 

在前⾔部分我们也提到了 MAUI 采⽤ Handler 模式去设定平台界⾯。如果我们要实现⼀个⾼德地图的 MAUI 控件,具体的架构是这样的

我们需要去创建如上图的⼀个结构,我们需要为 AMap 添加⼀个共享⽂件 AMap.shared.cs ,这个⽂件 AMapHandler 继⾃ ViewHandler。

 public interface IAMap : IView {
   
    } public class AMap : View, IAMap {
   
    } partial class AMapHandler {
   
    public static IPropertyMapper<AMap, AMapHandler> MapMapper = newPropertyMapper<AMap, AMapHandler>(ViewHandler.ViewMapper) { }; public AMapHandler() : base(MapMapper) { } }

然后在 Platforms 下的 Android 和 iOS ⽂件夹添加各⾃的平台 AMap 调⽤⽅法。

为 AMap.Android.cs 添加 Android 环境下⾼德地图的渲染⽅式

namespace AMap.UI.Apps{
   
    public partial class AMapHandler : ViewHandler<IAMap, MapView> {
   
    private AMapHelper _mapHelper; private MapView mapView; internal static Bundle Bundle { get; set; } public AMapHandler(IPropertyMapper mapper, CommandMappercommandMapper = null) : base(mapper, commandMapper) {
   
    } protected override MapView CreatePlatformView() {
   
    mapView = new Com.Amap.Api.Maps.MapView(Context);03.UIControls.md 7/5/20224 / 8 return mapView; } protected override void ConnectHandler(MapView platformView) {
   
    base.ConnectHandler(platformView); AMapLocationClient.UpdatePrivacyAgree(Context, true); AMapLocationClient.UpdatePrivacyShow(Context, true, true); _mapHelper = new AMapHelper(Bundle, platformView); mapView = _mapHelper.CallCreateMap(); } } class AMapHelper : Java.Lang.Object {
   
    private Bundle _bundle; private MapView _mapView; public event EventHandler MapIsReady; public MapView Map { get; set; } public AMapHelper(Bundle bundle, MapView mapView) {
   
    _bundle = bundle; _mapView = mapView; } public MapView CallCreateMap() {
   
    _mapView.OnCreate(_bundle); return _mapView; } }}

为 AMap.iOS.cs 添加 iOS 环境下⾼德地图的渲染⽅式

namespace AMap.UI.Apps{
   
    public partial class AMapHandler : ViewHandler<IAMap, MAMapView> {
   
    public AMapHandler(IPropertyMapper mapper, CommandMappercommandMapper = null) : base(mapper, commandMapper) {
   
    } protected override MAMapView CreatePlatformView() {
   
    MAMapView.UpdatePrivacyShow(AMapPrivacyShowStatus.DidShow,AMapPrivacyInfoStatus.DidContain); MAMapView.UpdatePrivacyAgree(AMapPrivacyAgreeStatus.DidAgree); AMapServices.SharedServices.ApiKey = ""; AMapServices.SharedServices.EnableHTTPS = true; //try //{
   
    MAMapView map = new MAMapView(); map.SetShowsUserLocation(true); map.SetUserTrackingMode(MAUserTrackingMode.Follow); return map; } protected override void ConnectHandler(MAMapView PlatformView) { } protected override void DisconnectHandler(MAMapView PlatformView) {
   
    if (PlatformView.Delegate != null) {
   
    PlatformView.Delegate.Dispose(); PlatformView.Delegate = null; } PlatformView.RemoveFromSuperview(); } }}

这⾥我们要修改 MAUI 的项⽬⽂件,这⾥有⼏个设定需要注意的

1. 因为这个控件只是针对 iOS / Android 两个平台,所以我们只保留 net6.0-android 和 net6.0-ios 

2. ⾼德 SDK 运⾏建议在真机下使⽤,特别是 iOS ,需要制定版本号,还有编译环境,我的环境是在 Apple silicon 下所以也要设定好 RuntimeIdentifier,还有就是编译的时候,我花了特别多时间在这⾥, ⼤家可以参考我这个在 GitHub 上⾃问⾃答的 Issue

  • https://github.com/xamarin/xamarin- macios/issues/15372

 <PropertyGroup Condition="$(TargetFramework.Contains('-ios'))"> <RuntimeIdentifier>ios-arm64</RuntimeIdentifier> <UseMSBuildEngine>true</UseMSBuildEngine> <WarningLevel>4</WarningLevel> <MtouchLink>SdkOnly</MtouchLink> <SupportedOSPlatformVersion>13.0</SupportedOSPlatformVersion> <DeviceSpecificBuild>true</DeviceSpecificBuild> <MtouchDebug>true</MtouchDebug> <MtouchFastDev>true</MtouchFastDev> <MtouchProfiling>true</MtouchProfiling> <MtouchUseSGen>true</MtouchUseSGen> <MtouchUseRefCounting>true</MtouchUseRefCounting> <MtouchFloat32>true</MtouchFloat32> </PropertyGroup>

3. 记得按照平台引⼊原⽣库的绑定

 <ItemGroup Condition=" '$(TargetPlatformIdentifier)' == 'ios' "> <ProjectReferenceInclude="..\iOS.AmapSDK.Foundation\iOS.AmapSDK.Foundation.csproj" /> <ProjectReference Include="..\iOS.AmapSDK.3D\iOS.AmapSDK.3D.csproj" />  </ItemGroup> <ItemGroup Condition=" '$(TargetPlatformIdentifier)' == 'android' "> <ProjectReference Include="..\Droid.AmapSDK\Droid.AmapSDK.csproj" /> </ItemGroup>

最后⼀步就是在 MauiProgram.cs 上注册 

builder.UseMauiApp<App>().ConfigureFonts(fonts =>{
   
   fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");}) .ConfigureMauiHandlers(handlers => {
   
    handlers.AddHandler(typeof(AMap), typeof(AMapHandler));});

选择编译运⾏就可以看到⾼德地图终于可以在 MAUI 环境下跑起来了

小结

对于很多⼈来说或者都是初步接触了 MAUI ,实际上要做好多平台的兼容还要有⾮常⻓的路要⾛。希望通过这个系列的⽂章,能给⼀些第三⽅⼚商和开发者⼀些帮助,能尽快提供 MAUI 的⽀持。这样才能为这个新的技术注⼊活⼒。

相关资料:

关注微软中国MSDN了解更多

点击了解MAUI~

 

猜你喜欢

转载自blog.csdn.net/helendemeng/article/details/125809998