记一次.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
<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>
关键配置说明
- 目标框架:使用
net9.0-android确保与MAUI项目兼容 - 最低Android版本:设置为API 24,确保设备兼容性
- AAR文件引用:通过
AndroidLibrary引用原生库文件 - 转换文件:用于处理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.Binding\RobotSDK.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;
}
// ... 其他方法的空实现
}
这样做的好处:
- 开发效率:可以在Windows上进行UI开发和测试
- 代码安全:避免运行时出现服务注册失败
- 团队协作:团队成员无需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>
这种设计的特点:
- 圆形适配:所有内容都在圆形区域内显示
- 分页滚动:采用"冰糖葫芦"式的垂直分页
- 视觉层次:使用深色主题和圆角设计
- 响应式布局:自动适配不同屏幕尺寸


总结感悟
在调试的时候遇到一个小坑,明明代码是根据机器人官方的SDK文档进行的初始化,但是不生效,机器人的舵机就是动不了,后面发现是因为代码要加一些延时,不然机器反应不过来就控制不了了。后来想想不同类别的开发,思考问题的角度还是不太一样。
AI发展速度真的是太快了,这个项目我是自己通过调试简单的代码,然后通过让AI反编译aar的文件,最后整理了一些文档,再让AI根据整理的文档实现的代码是很详细了,节省了大量的时间,感觉有了AI效率提高很多了,你们对AI写代码是怎么看待的,欢迎评论区讨论讨论。
希望这篇文章能够为大家在.NET MAUI项目中集成Android原生库提供一些参考和帮助。如果在实践过程中遇到问题,欢迎在评论区交流讨论!
参考资料
- Binding a Java library
- Microsoft Docs - Binding Android Libraries
- .NET MAUI Documentation
- Android AAR Format Specification
- 示例代码
- 自定义绑定
本文示例代码已上传至GitHub,欢迎大家参考学习。如果觉得有帮助,请给个Star支持一下!
记一次.NET MAUI项目中绑定Android库实现硬件控制的开发经历的更多相关文章
- 【C++模版之旅】项目中一次活用C++模板(traits)的经历 -新注解
问题与需求: 请读者先看这篇文章,[C++模版之旅]项目中一次活用C++模板(traits)的经历. 对于此篇文章提出的问题,我给出一个新的思路. talking is cheap,show me t ...
- 在项目中引用android.support.v7
在Android开发中,新建的项目可能因为缺少对sopport工程的引用而报错,可以这样解决. 1.项目右键 --> import --> Android --> Existing ...
- react项目中引入了redux后js控制路由跳转方案
如果你的项目中并没有用到redux,那本文你可以忽略 问题引入 纯粹的单页面react应用中,通过this.props.history.push('/list')就可以进行路由跳转,但是加上了redu ...
- 在项目中引用GreenDroid库
1.下载GreenDroid库 首先,我们得从Git上下载这个库,我用的是git for windows下载的.先下载,安装.安装完后,打开git for windows ,直接将浏览器中GreenD ...
- 【C++模版之旅】项目中一次活用C++模板(traits)的经历
曾经曾在一个项目中碰到过一个挺简单的问题,但一时又不能用普通常规的方法去非常好的解决,最后通过C++模板的活用,通过traits相对照较巧妙的攻克了这个问题.本文主要想重现问题发生,若干解决方式的比較 ...
- 项目中处理android 6.0权限管理问题
android 6.0对于权限管理比较收紧,因此在适配android 6.0的时候就很有必要考虑一些权限管理的问题. 如果你没适配6.0的设备并且权限没给的话,就会出现类似如下的问题: java.la ...
- iconfont 怎么在项目中使用图标库
iconfont是很多设计以及前后端人员编写页面时经常用到的网站,阿里不仅为我们提供了免费的图标库,并且有一套完整的图标库体系.很多初学者只知道从图标库中下载图标放入项目中,但在实际项目应用中,过多的 ...
- 如何在Android Studio项目中导入开源库?
前两天,谷歌发布了Android Studio 1.0的正式版,也有更多的人开始迁移到Android Studio进行开发.然而,网上很多的开源库,控件等还是以前的基于Eclipse进行开发,很多人不 ...
- [学习笔记] 在Eclipse中添加用户库 Add User Libraries ,在项目中引用用户库
如果还没有安装Eclipse, 则请参考前文: [学习笔记] 下载.安装.启动 Eclipse(OEPE) 添加用户库 本文主要介绍在项目中直接使用第三方库的情况.就是把第三方的jar文件直接放到某 ...
- 创建一个本地CocoaPods库 并在项目中使用该库
1.新建一个项目如下 2.往TestLib中添加两个文件 3.终端进入TestLib 生成git文件 然后提交到本地 git init git add . git commit -m '添加perso ...
随机推荐
- 词库过大导致的Redis超时问题-RedisCommandTimeoutException
问题 Redis缓存超时问题 报错内容 redis io.lettuce.core.RedisCommandTimeoutException: Command timed out after 10 s ...
- 用 Proxy 进一步提高 npm 安装速度
@charset "UTF-8"; .markdown-body { line-height: 1.75; font-weight: 400; font-size: 15px; o ...
- OpenIddict使用教程
OpenIddict是一个ASP.NET Core身份验证库,可帮助您添加OpenID Connect和OAuth 2.0支持到ASP.NET Core应用程序中.下面是OpenIddict使用教程的 ...
- Error while fetching metadata from server 'https://start.spring.io' Please check URL, network and proxy settings.
idea创建springboot项目失败,提示: Error while fetching metadata from server 'https://start.spring.io' Please ...
- git命令--拉取代码和切换分支
git一般有很多分支,我们clone到本地的代码都是master分支,那么如何切换到其它分支呢?本文介绍主要操作流程和命令,包括拉取仓库代码.查看分支和切换分支,至于如何提交代码,需要的童鞋自己查 ...
- Spring注解之@Autowired:Setter 方法上使用@Autowired注解
可以在 JavaBean中的 setter 方法中使用 @Autowired 注解.当 Spring遇到一个在 setter 方法中使用的 @Autowired 注解时,它会在方法中按照类型自动装配参 ...
- 【转载】DeltaFIFO源码分析
DeltaFIFO源码分析 介绍 我们已经知道 Reflector 中通过 ListAndWatch 获取到数据后传入到了本地的存储中,也就是 DeltaFIFO 中.从 DeltaFIFO 的名字可 ...
- Ingress学习笔记
Ingress 我们已经知道,Service对集群之外暴露服务的主要方式有两种:NodePort和LoadBalancer,但是这两种方式,都有一定的缺点: NodePort方式的缺点是会占用很多集群 ...
- vue的a-tree-select选择父节点回显的是子节点
正常来说当选择父节点时候,我们回显的应该就是父节点的信息比如: 可是现在我想回显子节点的信息如何处理? 很简单:在 a-tree-select组件里面去掉这一句: 这样回显的就是我们想要的结果了 ...
- 线程池中execute和submit的区别?
简要回答 execute只能提交Runnable类型的任务,无返回值.submit既可以提交Runnable类型的任务,也可以提交Callable类型的任务,会有一个类型为Future的返回值,但当任 ...