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

前言

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

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

问题解答

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

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

名词解释

  • .NET MAUI :.NET 多平台应用 UI (.NET MAUI) 是一个跨平台框架,用于使用 C# 和 XAML 创建本机移动和桌面应用。使用 .NET MAUI,可以从单个共享代码库开发可在 Android、iOS、macOS 和 Windows 上运行的应用。

准备工作

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

软件环境

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

项目背景

这次要集成的是一个机器人控制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
xml 复制代码
<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="Jars\RobotSdk-release-2.5.aar" />
  </ItemGroup>
  
  <ItemGroup>
    <TransformFile Include="Transforms\Metadata.xml" />
    <TransformFile Include="Transforms\EnumFields.xml" />
    <TransformFile Include="Transforms\EnumMethods.xml" />
  </ItemGroup>
</Project>

关键配置说明

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

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

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

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

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

自定义绑定

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

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

服务接口定义

csharp 复制代码
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平台实现的核心要点

csharp 复制代码
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#事件,我设计了一个回调桥接类:

csharp 复制代码
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绑定库:

xml 复制代码
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
  <ProjectReference Include="..\RobotSDK.Android.Binding\RobotSDK.Android.Binding.csproj" />
</ItemGroup>

服务注册和平台特定实现

MauiProgram.cs中配置依赖注入:

csharp 复制代码
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是一个很重要的设计决策:

csharp 复制代码
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设计也做了特殊适配:

xaml 复制代码
<!-- 圆形屏幕容器 (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>

这种设计的特点:

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

总结感悟

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

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

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

参考资料


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

相关推荐
Maybe_ch2 小时前
.NET依赖注入IOC你了解吗?
开发语言·c#·.net
吹牛不交税8 小时前
.NET使用EPPlus导出EXCEL的接口中,文件流缺少文件名信息
.net·excel
追逐时光者18 小时前
一款基于 .NET 开源免费、轻量快速、跨平台的 PDF 阅读器
后端·.net
深盾科技1 天前
从基础到实战:.NET 反射机制的进阶用法与最佳实践
windows·microsoft·.net
唐青枫1 天前
C#.NET 并发令牌 详解
c#·.net
FreeDw资源库2 天前
【2025最新】 .NET FrameWork微软离线运行库合集,一键安装版
microsoft·.net
喵叔哟2 天前
28.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--单体转微服务--币种服务(二)
java·微服务·.net
追逐时光者2 天前
C#/.NET/.NET Core技术前沿周刊 | 第 47 期(2025年7.14-7.20)
后端·.net