记一次.NET MAUI项目中绑定Android库实现硬件控制的开发经历

前言

最近AI小智对话机器人实在是太火了,于是我就把我之前的一个吃灰的安卓桌面机器人给拿出来玩了,我想着基于安卓的系统开发一些自己的软件操作它,我翻了下官方文档也是有提供SDK的,于是我就开始了这个开发尝试。机器人本身是有丰富的传感器,也有完整的麦克风摄像头可以用,那做个会动的小智机器人刚刚好,第一步肯定是先让它能够按我的操作动起来。

这个过程虽然有一些小坑,但最终成功实现了完整的硬件控制功能。今天就来分享一下这次Android库绑定的完整经历,希望能帮助到有类似需求的小伙伴们。
记一次.NET MAUI项目中绑定Android库实现硬件控制的开发经历

问题解答

Q: 为什么选择.NET MAUI来进行开发?

A: .NET MAUI本身是支持跨平台开发的,这是选择它的主要原因之一。还有就是我之前比较熟悉WinUI开发,对xaml的语法也算是比较熟悉,当然跨平台还有Avalonia UI,这个社区活跃度比.NET MAUI还高,但是由于MAUI能够满足我的需求,暂时还没尝试这个框架,大家有兴趣的可以试试它。

记一次.NET MAUI项目中绑定Android库实现硬件控制的开发经历

名词解释

  • .NET MAUI:.NET 多平台应用 UI (.NET MAUI) 是一个跨平台框架,用于使用 C# 和 XAML 创建本机移动和桌面应用。使用 .NET MAUI,可以从单个共享代码库开发可在 Android、iOS、macOS 和 Windows 上运行的应用。
    记一次.NET MAUI项目中绑定Android库实现硬件控制的开发经历

准备工作

在开始编码之前,我们需要准备以下环境:

软件环境

  • Visual Studio 2022
  • .NET 9 SDK
  • Visual Studio 2022要安装MAUI的工作负载,并且记得创建安卓虚拟机。

记一次.NET MAUI项目中绑定Android库实现硬件控制的开发经历

项目背景

这次要集成的是一个机器人控制SDK(RobotSDK),它以AAR格式提供,包含了机器人的运动控制、传感器监听、表情控制、语音播放等功能。我们的目标是在.NET MAUI应用中使用这些原生功能,实现跨平台的机器人控制应用。

技术选型和架构设计

整体架构

┌─────────────────────┐    ┌──────────────────────┐    ┌─────────────────────┐ │  MAUI UI Layer      │    │  Service Interface   │    │  Platform Services  │ │  (MainPage.xaml)    │◄──►│  IRobotControlService│◄──►│  AndroidRobotControl│ │  ViewModels         │    │                      │    │  DefaultRobotControl│ └─────────────────────┘    └──────────────────────┘    └─────────────────────┘                                       │                                       ▼                            ┌──────────────────────┐                            │ RobotSDK.Android     │                            │ Binding Library      │                            │ (AAR Wrapper)        │                            └──────────────────────┘                                       │                                       ▼                            ┌──────────────────────┐                            │ Native Android       │                            │ RobotSDK AAR         │                            │ (Hardware Control)   │                            └──────────────────────┘ 

核心技术栈

  • .NET 9.0 MAUI - 跨平台UI框架
  • Android Binding Library - AAR库绑定
  • Dependency Injection - 服务注册和平台特定实现
  • MVVM模式 - 数据绑定和状态管理

第一步:创建Android绑定库项目

官方参考文档如下:
Binding a Java library

首先创建一个专门的Android绑定库项目来包装原生AAR文件:

使用下面的指令进行项目的创建

dotnet new android-bindinglib 
<Project Sdk="Microsoft.NET.Sdk">   <PropertyGroup>     <TargetFramework>net9.0-android</TargetFramework>     <Nullable>enable</Nullable>     <ImplicitUsings>enable</ImplicitUsings>     <SupportedOSPlatformVersion>24.0</SupportedOSPlatformVersion>   </PropertyGroup>      <ItemGroup>     <AndroidLibrary Include="JarsRobotSdk-release-2.5.aar" />   </ItemGroup>      <ItemGroup>     <TransformFile Include="TransformsMetadata.xml" />     <TransformFile Include="TransformsEnumFields.xml" />     <TransformFile Include="TransformsEnumMethods.xml" />   </ItemGroup> </Project> 

关键配置说明

  1. 目标框架:使用net9.0-android确保与MAUI项目兼容
  2. 最低Android版本:设置为API 24,确保设备兼容性
  3. AAR文件引用:通过AndroidLibrary引用原生库文件
  4. 转换文件:用于处理Java到C#的类型映射

第二步:处理绑定过程中的常见问题

在绑定过程中,经常会遇到一些类型映射和命名冲突问题,这时候就需要用到Transforms文件夹中的配置文件:

由于目前的项目比较简单,这部分的映射文件我就使用了项目默认生成的了。

大家有需要可以看官方文档的一些注意事项。

自定义绑定

第三步:设计服务接口和平台实现

为了保证代码的可测试性和平台兼容性,我设计了一套清晰的服务接口:

服务接口定义

public interface IRobotControlService : IRobotSensorEvents {     // 基础控制     Task<bool> InitializeAsync();     bool IsServiceAvailable { get; }          // 传感器控制     Task StartSensorMonitoringAsync();     Task StopSensorMonitoringAsync();          // 运动控制     Task MoveForwardAsync(int speed = 3, int steps = 1);     Task MoveBackwardAsync(int speed = 3, int steps = 1);     Task TurnLeftAsync(int speed = 3, int steps = 1);     Task TurnRightAsync(int speed = 3, int steps = 1);          // 表情和语音     Task ShowExpressionAsync(string expression);     Task SpeakAsync(string text);     Task SpeakWithExpressionAsync(string text, string expression);          // 硬件控制     Task EnableMotorAsync();     Task DisableMotorAsync();     Task SetAntennaLightAsync(int color);     Task MoveAntennaAsync(int cmd, int step, int speed, int angle); }  public interface IRobotSensorEvents {     event EventHandler? TapDetected;     event EventHandler? DoubleTapDetected;     event EventHandler? LongPressDetected;     event EventHandler? FallBackwardDetected;     event EventHandler? FallForwardDetected;     event EventHandler? FallRightDetected;     event EventHandler? FallLeftDetected;     event EventHandler? TofDetected; } 

Android平台实现的核心要点

public class AndroidRobotControlService : IRobotControlService {     private readonly ILogger<AndroidRobotControlService> _logger;     private readonly Context _context;     private RobotService? _robotService;     private SensorCallbackImpl? _sensorCallback;          public async Task<bool> InitializeAsync()     {         try         {             _logger.LogInformation("初始化Android机器人服务...");                          // 获取原生SDK实例             _robotService = RobotService.GetInstance(_context);                          if (_robotService == null)             {                 _logger.LogError("无法获取RobotService实例");                 return false;             }                          // 创建回调桥接             _sensorCallback = new SensorCallbackImpl(                 onTap: () => TapDetected?.Invoke(this, EventArgs.Empty),                 onDoubleTap: () => DoubleTapDetected?.Invoke(this, EventArgs.Empty),                 onLongPress: () => LongPressDetected?.Invoke(this, EventArgs.Empty),                 // ... 其他传感器事件             );                          // 自动启用电机             _robotService.RobotOpenMotor();             await Task.Delay(500);                          _isInitialized = true;             _logger.LogInformation("Android机器人服务初始化成功");             return true;         }         catch (Exception ex)         {             _logger.LogError(ex, "初始化Android机器人服务失败");             return false;         }     } } 

回调桥接的巧妙设计

为了将Java回调转换为C#事件,我设计了一个回调桥接类:

public class SensorCallbackImpl : Java.Lang.Object, ISensorCallback {     private readonly Action _onTap;     private readonly Action _onDoubleTap;     private readonly Action _onLongPress;     // ... 其他事件委托          public SensorCallbackImpl(         Action onTap,         Action onDoubleTap,         Action onLongPress,         // ... 其他参数     )     {         _onTap = onTap;         _onDoubleTap = onDoubleTap;         _onLongPress = onLongPress;         // ... 赋值操作     }          // 实现Java接口方法,转发到C#委托     public void OnTapResponse() => _onTap?.Invoke();     public void OnDoubleTapResponse() => _onDoubleTap?.Invoke();     public void OnLongPressResponse() => _onLongPress?.Invoke();     // ... 其他方法 } 

第四步:MAUI项目集成和依赖注入配置

项目引用配置

在MAUI项目的csproj文件中,需要有条件地引用Android绑定库:

<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">   <ProjectReference Include="..RobotSDK.Android.BindingRobotSDK.Android.Binding.csproj" /> </ItemGroup> 

服务注册和平台特定实现

MauiProgram.cs中配置依赖注入:

public static class MauiProgram {     public static MauiApp CreateMauiApp()     {         var builder = MauiApp.CreateBuilder();         builder             .UseMauiApp<App>()             .ConfigureFonts(fonts =>             {                 fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");             });          // 注册服务         builder.Services.AddSingleton<MainPageViewModel>();                  // 平台特定服务注册 #if ANDROID         builder.Services.AddSingleton<IRobotControlService, AndroidRobotControlService>(); #else         builder.Services.AddSingleton<IRobotControlService, DefaultRobotControlService>(); #endif          // 添加调试日志         builder.Logging.AddDebug();          return builder.Build();     } } 

为什么要有Default实现?

创建DefaultRobotControlService是一个很重要的设计决策:

public class DefaultRobotControlService : IRobotControlService {     private readonly ILogger<DefaultRobotControlService> _logger;      public bool IsServiceAvailable => false;      public Task<bool> InitializeAsync()     {         _logger.LogWarning("机器人控制服务仅在Android平台可用");         return Task.FromResult(false);     }          public Task MoveForwardAsync(int speed = 3, int steps = 1)     {         _logger.LogWarning("动作控制仅在Android平台可用");         return Task.CompletedTask;     }          // ... 其他方法的空实现 } 

这样做的好处:

  1. 开发效率:可以在Windows上进行UI开发和测试
  2. 代码安全:避免运行时出现服务注册失败
  3. 团队协作:团队成员无需Android设备即可进行开发

第五步:UI设计和圆形屏幕适配

考虑到目标设备是圆形屏幕的机器人,UI设计也做了特殊适配:

<!-- 圆形屏幕容器 (480x480) --> <Grid>     <!-- 圆形边框指示器 -->     <Ellipse Fill="Transparent"               Stroke="DarkGray"               StrokeThickness="2"              Margin="10" />          <!-- 冰糖葫芦式垂直滚动容器 -->     <ScrollView x:Name="MainScrollView"                  Orientation="Vertical"                  HorizontalScrollBarVisibility="Never"                 VerticalScrollBarVisibility="Never"                 BackgroundColor="Transparent"                 Padding="0,0,0,50">                  <StackLayout Spacing="0" BackgroundColor="Transparent">             <!-- 第1个圆形区域 - 状态和连接控制 -->             <Grid HeightRequest="480" WidthRequest="480" BackgroundColor="Transparent">                 <Ellipse Fill="#1A1A2E"                           Stroke="#16213E"                           StrokeThickness="3"                          Margin="40" />                                  <!-- 内容区域 -->                 <StackLayout Spacing="25" Margin="60" VerticalOptions="Center">                     <!-- UI内容 -->                 </StackLayout>             </Grid>         </StackLayout>     </ScrollView> </Grid> 

这种设计的特点:

  • 圆形适配:所有内容都在圆形区域内显示
  • 分页滚动:采用"冰糖葫芦"式的垂直分页
  • 视觉层次:使用深色主题和圆角设计
  • 响应式布局:自动适配不同屏幕尺寸

记一次.NET MAUI项目中绑定Android库实现硬件控制的开发经历

记一次.NET MAUI项目中绑定Android库实现硬件控制的开发经历

总结感悟

在调试的时候遇到一个小坑,明明代码是根据机器人官方的SDK文档进行的初始化,但是不生效,机器人的舵机就是动不了,后面发现是因为代码要加一些延时,不然机器反应不过来就控制不了了。后来想想不同类别的开发,思考问题的角度还是不太一样。

AI发展速度真的是太快了,这个项目我是自己通过调试简单的代码,然后通过让AI反编译aar的文件,最后整理了一些文档,再让AI根据整理的文档实现的代码是很详细了,节省了大量的时间,感觉有了AI效率提高很多了,你们对AI写代码是怎么看待的,欢迎评论区讨论讨论。

希望这篇文章能够为大家在.NET MAUI项目中集成Android原生库提供一些参考和帮助。如果在实践过程中遇到问题,欢迎在评论区交流讨论!

参考资料


本文示例代码已上传至GitHub,欢迎大家参考学习。如果觉得有帮助,请给个Star支持一下!

发表评论

评论已关闭。

相关文章