慎教员 發表於 2025-7-23 00:17:00

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

<h2 id="前言">前言</h2>
<p>最近AI小智对话机器人实在是太火了,于是我就把我之前的一个吃灰的安卓桌面机器人给拿出来玩了,我想着基于安卓的系统开发一些自己的软件操作它,我翻了下官方文档也是有提供SDK的,于是我就开始了这个开发尝试。机器人本身是有丰富的传感器,也有完整的麦克风摄像头可以用,那做个会动的小智机器人刚刚好,第一步肯定是先让它能够按我的操作动起来。</p>
<p>这个过程虽然有一些小坑,但最终成功实现了完整的硬件控制功能。今天就来分享一下这次Android库绑定的完整经历,希望能帮助到有类似需求的小伙伴们。<br>
<img src="https://img2024.cnblogs.com/blog/1690009/202507/1690009-20250722222724175-764295086.png" alt="img" loading="lazy"></p>
<p><video src="https://github.com/user-attachments/assets/90188217-c282-43c4-83c8-fe7d5c28a4f8" controls="" width="480"></video></p>
<h2 id="问题解答">问题解答</h2>
<p><strong>Q: 为什么选择.NET MAUI来进行开发?</strong></p>
<p>A: .NET MAUI本身是支持跨平台开发的,这是选择它的主要原因之一。还有就是我之前比较熟悉WinUI开发,对xaml的语法也算是比较熟悉,当然跨平台还有Avalonia UI,这个社区活跃度比.NET MAUI还高,但是由于MAUI能够满足我的需求,暂时还没尝试这个框架,大家有兴趣的可以试试它。</p>
<p><img src="https://img2024.cnblogs.com/blog/1690009/202507/1690009-20250722154139799-1584368419.png" alt="img" loading="lazy"></p>
<h2 id="名词解释">名词解释</h2>
<ul>
<li><strong>.NET MAUI</strong>:.NET 多平台应用 UI (.NET MAUI) 是一个跨平台框架,用于使用 C# 和 XAML 创建本机移动和桌面应用。使用 .NET MAUI,可以从单个共享代码库开发可在 Android、iOS、macOS 和 Windows 上运行的应用。<br>
<img src="https://img2024.cnblogs.com/blog/1690009/202507/1690009-20250722150111024-855091689.png" alt="img" loading="lazy"></li>
</ul>
<h2 id="准备工作">准备工作</h2>
<p>在开始编码之前,我们需要准备以下环境:</p>
<h3 id="软件环境">软件环境</h3>
<ul>
<li>Visual Studio 2022</li>
<li>.NET 9 SDK</li>
<li>Visual Studio 2022要安装MAUI的工作负载,并且记得创建安卓虚拟机。</li>
</ul>
<p><img src="https://img2024.cnblogs.com/blog/1690009/202507/1690009-20250722150550418-1768830805.png" alt="img" loading="lazy"></p>
<h2 id="项目背景">项目背景</h2>
<p>这次要集成的是一个机器人控制SDK(RobotSDK),它以AAR格式提供,包含了机器人的运动控制、传感器监听、表情控制、语音播放等功能。我们的目标是在.NET MAUI应用中使用这些原生功能,实现跨平台的机器人控制应用。</p>
<h2 id="技术选型和架构设计">技术选型和架构设计</h2>
<h3 id="整体架构">整体架构</h3>
<pre><code>┌─────────────────────┐    ┌──────────────────────┐    ┌─────────────────────┐
│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)   │
                           └──────────────────────┘
</code></pre>
<h3 id="核心技术栈">核心技术栈</h3>
<ul>
<li><strong>.NET 9.0 MAUI</strong> - 跨平台UI框架</li>
<li><strong>Android Binding Library</strong> - AAR库绑定</li>
<li><strong>Dependency Injection</strong> - 服务注册和平台特定实现</li>
<li><strong>MVVM模式</strong> - 数据绑定和状态管理</li>
</ul>
<h2 id="第一步创建android绑定库项目">第一步:创建Android绑定库项目</h2>
<p>官方参考文档如下:<br>
Binding a Java library</p>
<p>首先创建一个专门的Android绑定库项目来包装原生AAR文件:</p>
<p>使用下面的指令进行项目的创建</p>
<pre><code>dotnet new android-bindinglib
</code></pre>
<pre><code class="language-xml">&lt;Project Sdk="Microsoft.NET.Sdk"&gt;
&lt;PropertyGroup&gt;
    &lt;TargetFramework&gt;net9.0-android&lt;/TargetFramework&gt;
    &lt;Nullable&gt;enable&lt;/Nullable&gt;
    &lt;ImplicitUsings&gt;enable&lt;/ImplicitUsings&gt;
    &lt;SupportedOSPlatformVersion&gt;24.0&lt;/SupportedOSPlatformVersion&gt;
&lt;/PropertyGroup&gt;

&lt;ItemGroup&gt;
    &lt;AndroidLibrary Include="Jars\RobotSdk-release-2.5.aar" /&gt;
&lt;/ItemGroup&gt;

&lt;ItemGroup&gt;
    &lt;TransformFile Include="Transforms\Metadata.xml" /&gt;
    &lt;TransformFile Include="Transforms\EnumFields.xml" /&gt;
    &lt;TransformFile Include="Transforms\EnumMethods.xml" /&gt;
&lt;/ItemGroup&gt;
&lt;/Project&gt;
</code></pre>
<h3 id="关键配置说明">关键配置说明</h3>
<ol>
<li><strong>目标框架</strong>:使用<code>net9.0-android</code>确保与MAUI项目兼容</li>
<li><strong>最低Android版本</strong>:设置为API 24,确保设备兼容性</li>
<li><strong>AAR文件引用</strong>:通过<code>AndroidLibrary</code>引用原生库文件</li>
<li><strong>转换文件</strong>:用于处理Java到C#的类型映射</li>
</ol>
<h2 id="第二步处理绑定过程中的常见问题">第二步:处理绑定过程中的常见问题</h2>
<p>在绑定过程中,经常会遇到一些类型映射和命名冲突问题,这时候就需要用到Transforms文件夹中的配置文件:</p>
<p>由于目前的项目比较简单,这部分的映射文件我就使用了项目默认生成的了。</p>
<p>大家有需要可以看官方文档的一些注意事项。</p>
<p>自定义绑定</p>
<h2 id="第三步设计服务接口和平台实现">第三步:设计服务接口和平台实现</h2>
<p>为了保证代码的可测试性和平台兼容性,我设计了一套清晰的服务接口:</p>
<h3 id="服务接口定义">服务接口定义</h3>
<pre><code class="language-csharp">public interface IRobotControlService : IRobotSensorEvents
{
    // 基础控制
    Task&lt;bool&gt; 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;
}
</code></pre>
<h3 id="android平台实现的核心要点">Android平台实现的核心要点</h3>
<pre><code class="language-csharp">public class AndroidRobotControlService : IRobotControlService
{
    private readonly ILogger&lt;AndroidRobotControlService&gt; _logger;
    private readonly Context _context;
    private RobotService? _robotService;
    private SensorCallbackImpl? _sensorCallback;
   
    public async Task&lt;bool&gt; InitializeAsync()
    {
      try
      {
            _logger.LogInformation("初始化Android机器人服务...");
            
            // 获取原生SDK实例
            _robotService = RobotService.GetInstance(_context);
            
            if (_robotService == null)
            {
                _logger.LogError("无法获取RobotService实例");
                return false;
            }
            
            // 创建回调桥接
            _sensorCallback = new SensorCallbackImpl(
                onTap: () =&gt; TapDetected?.Invoke(this, EventArgs.Empty),
                onDoubleTap: () =&gt; DoubleTapDetected?.Invoke(this, EventArgs.Empty),
                onLongPress: () =&gt; 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;
      }
    }
}
</code></pre>
<h3 id="回调桥接的巧妙设计">回调桥接的巧妙设计</h3>
<p>为了将Java回调转换为C#事件,我设计了一个回调桥接类:</p>
<pre><code class="language-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() =&gt; _onTap?.Invoke();
    public void OnDoubleTapResponse() =&gt; _onDoubleTap?.Invoke();
    public void OnLongPressResponse() =&gt; _onLongPress?.Invoke();
    // ... 其他方法
}
</code></pre>
<h2 id="第四步maui项目集成和依赖注入配置">第四步:MAUI项目集成和依赖注入配置</h2>
<h3 id="项目引用配置">项目引用配置</h3>
<p>在MAUI项目的csproj文件中,需要有条件地引用Android绑定库:</p>
<pre><code class="language-xml">&lt;ItemGroup Condition="$(::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'"&gt;
&lt;ProjectReference Include="..\RobotSDK.Android.Binding\RobotSDK.Android.Binding.csproj" /&gt;
&lt;/ItemGroup&gt;
</code></pre>
<h3 id="服务注册和平台特定实现">服务注册和平台特定实现</h3>
<p>在<code>MauiProgram.cs</code>中配置依赖注入:</p>
<pre><code class="language-csharp">public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
      var builder = MauiApp.CreateBuilder();
      builder
            .UseMauiApp&lt;App&gt;()
            .ConfigureFonts(fonts =&gt;
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
            });

      // 注册服务
      builder.Services.AddSingleton&lt;MainPageViewModel&gt;();
      
      // 平台特定服务注册
#if ANDROID
      builder.Services.AddSingleton&lt;IRobotControlService, AndroidRobotControlService&gt;();
#else
      builder.Services.AddSingleton&lt;IRobotControlService, DefaultRobotControlService&gt;();
#endif

      // 添加调试日志
      builder.Logging.AddDebug();

      return builder.Build();
    }
}
</code></pre>
<h3 id="为什么要有default实现">为什么要有Default实现?</h3>
<p>创建<code>DefaultRobotControlService</code>是一个很重要的设计决策:</p>
<pre><code class="language-csharp">public class DefaultRobotControlService : IRobotControlService
{
    private readonly ILogger&lt;DefaultRobotControlService&gt; _logger;

    public bool IsServiceAvailable =&gt; false;

    public Task&lt;bool&gt; InitializeAsync()
    {
      _logger.LogWarning("机器人控制服务仅在Android平台可用");
      return Task.FromResult(false);
    }
   
    public Task MoveForwardAsync(int speed = 3, int steps = 1)
    {
      _logger.LogWarning("动作控制仅在Android平台可用");
      return Task.CompletedTask;
    }
   
    // ... 其他方法的空实现
}
</code></pre>
<p>这样做的好处:</p>
<ol>
<li><strong>开发效率</strong>:可以在Windows上进行UI开发和测试</li>
<li><strong>代码安全</strong>:避免运行时出现服务注册失败</li>
<li><strong>团队协作</strong>:团队成员无需Android设备即可进行开发</li>
</ol>
<h2 id="第五步ui设计和圆形屏幕适配">第五步:UI设计和圆形屏幕适配</h2>
<p>考虑到目标设备是圆形屏幕的机器人,UI设计也做了特殊适配:</p>
<pre><code class="language-xaml">&lt;!-- 圆形屏幕容器 (480x480) --&gt;
&lt;Grid&gt;
    &lt;!-- 圆形边框指示器 --&gt;
    &lt;Ellipse Fill="Transparent"
             Stroke="DarkGray"
             StrokeThickness="2"
             Margin="10" /&gt;
   
    &lt;!-- 冰糖葫芦式垂直滚动容器 --&gt;
    &lt;ScrollView x:Name="MainScrollView"
                Orientation="Vertical"
                HorizontalScrollBarVisibility="Never"
                VerticalScrollBarVisibility="Never"
                BackgroundColor="Transparent"
                Padding="0,0,0,50"&gt;
      
      &lt;StackLayout Spacing="0" BackgroundColor="Transparent"&gt;
            &lt;!-- 第1个圆形区域 - 状态和连接控制 --&gt;
            &lt;Grid HeightRequest="480" WidthRequest="480" BackgroundColor="Transparent"&gt;
                &lt;Ellipse Fill="#1A1A2E"
                         Stroke="#16213E"
                         StrokeThickness="3"
                         Margin="40" /&gt;
               
                &lt;!-- 内容区域 --&gt;
                &lt;StackLayout Spacing="25" Margin="60" VerticalOptions="Center"&gt;
                  &lt;!-- UI内容 --&gt;
                &lt;/StackLayout&gt;
            &lt;/Grid&gt;
      &lt;/StackLayout&gt;
    &lt;/ScrollView&gt;
&lt;/Grid&gt;
</code></pre>
<p>这种设计的特点:</p>
<ul>
<li><strong>圆形适配</strong>:所有内容都在圆形区域内显示</li>
<li><strong>分页滚动</strong>:采用"冰糖葫芦"式的垂直分页</li>
<li><strong>视觉层次</strong>:使用深色主题和圆角设计</li>
<li><strong>响应式布局</strong>:自动适配不同屏幕尺寸</li>
</ul>
<p><img src="https://img2024.cnblogs.com/blog/1690009/202507/1690009-20250722163852634-482545286.png" alt="img" loading="lazy"></p>
<p><img src="https://img2024.cnblogs.com/blog/1690009/202507/1690009-20250722162825456-1056805266.png" alt="img" loading="lazy"></p>
<h2 id="总结感悟">总结感悟</h2>
<p>在调试的时候遇到一个小坑,明明代码是根据机器人官方的SDK文档进行的初始化,但是不生效,机器人的舵机就是动不了,后面发现是因为代码要加一些延时,不然机器反应不过来就控制不了了。后来想想不同类别的开发,思考问题的角度还是不太一样。</p>
<p>AI发展速度真的是太快了,这个项目我是自己通过调试简单的代码,然后通过让AI反编译aar的文件,最后整理了一些文档,再让AI根据整理的文档实现的代码是很详细了,节省了大量的时间,感觉有了AI效率提高很多了,你们对AI写代码是怎么看待的,欢迎评论区讨论讨论。</p>
<p>希望这篇文章能够为大家在.NET MAUI项目中集成Android原生库提供一些参考和帮助。如果在实践过程中遇到问题,欢迎在评论区交流讨论!</p>
<h2 id="参考资料">参考资料</h2>
<ul>
<li>Binding a Java library</li>
<li>Microsoft Docs - Binding Android Libraries</li>
<li>.NET MAUI Documentation</li>
<li>Android AAR Format Specification</li>
<li>示例代码</li>
<li>自定义绑定</li>
</ul>
<hr>
<p><em>本文示例代码已上传至GitHub,欢迎大家参考学习。如果觉得有帮助,请给个Star支持一下!</em></p><br><br>
来源:https://www.cnblogs.com/GreenShade/p/18998698
頁: [1]
查看完整版本: 记一次.NET MAUI项目中绑定Android库实现硬件控制的开发经历