Xamarin可以使用C#语言开发原生的Android和iOS应用,被微软收购后更是热度不减;而SignalR是微软推出的实时通信技术。之前在项目中用过SignalR,而Xamarin则是首次接触,本次是想结合二者开发一个Windows、Android、iOS平台间实时通信的示例。
开发环境 | 说明 |
---|---|
操作系统 | Windows 10 professional |
编译器 | VS 2015 update2 |
插件 | Xamarin for VisualStudio |
其他 | Android SDK,Android Emulater |
本实例参考了以下文章:
[ How To Use SignalR in iOS and Android Apps ]
[ Running SignalR on Mono]
[ Using SignalR in native Android and iOS apps]
[ Xamarin.Android快速入门]
[Using SignalR in WinForms and WPF]
感谢以上文章的作者!
1.创建服务端
由于之前写过WPF的服务端,所以直接拿来用了。
1.1新建项目ServerApp
选择Windows>WPF应用程序,添加新项目ServerApp。这里框架依然沿用以前的.NET Framework 4.5,并未使用最新的4.6.1 。
1.2引用SignalR包
选择“工具>NuGet包管理器>管理解决方案的NuGet程序包”
搜索“SignalR”,相应的包全部安装上(有些包服务端用不上,但是客户端后面会用上)。安装成功后会在项目引用中看到SignalR的几个引用已经添加进来了。
1.3添加启动入口和Hub
添加Startup.cs作为程序启动的入口。
using Owin;//必需的程序集引用
namespace ServerApp
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR();
}
}
}
添加MyHub.cs作为主要服务处理程序。MyHub继承于Hub并实现其接口,IMyHub是自己写的接口,目前只有一个方法“Task SendPlatform(string platform,string message)”。方法SendPlatform就是后面要用到的通信方法。
using Microsoft.AspNet.SignalR;
using System.Collections.Generic;
using System.Data;
using System.Threading.Tasks;
using System.Windows;
using Model;
namespace ServerApp
{
public class MyHub:Hub,IMyHub
{
public override Task OnConnected()
{
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
return base.OnDisconnected(stopCalled);
}
public override Task OnReconnected()
{
return base.OnReconnected();
}
public Task SendPlatform(string platform,string message)
{
return Clients.All.sendplatform(platform, message);
}
}
}
1.4添加启动服务方法
在主页面的后台MainWindow.cs添加控制服务启动及停止的方法。
namespace ServerApp
{
public partial class MainWindow : Window
{
#region 通信设置
public IDisposable SignalR { get; set; }
static string ServerURI = ConfigurationManager.AppSettings["ServiceUri"]; //全局通信链接,地址为服务端所在主机IP地址,端口号可任意指定
#endregion
public MainWindow()
{
InitializeComponent();
StartServer();
}
private void StartServer()
{
try
{
SignalR = WebApp.Start(ServerURI);
}
catch (TargetInvocationException)
{
WriteToConsole("已存在运行的服务:" + ServerURI);
this.Dispatcher.Invoke(() => btn_Start.IsEnabled = true);
return;
}
this.Dispatcher.Invoke(() => btn_Stop.IsEnabled = true);
WriteToConsole("服务正在运行:" + ServerURI);
}
}
}
服务URI在配置文件app.config中设置。
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<!--通信连接地址-->
<add key="ServiceUri" value="http://127.0.0.1:46900/" />
</appSettings>
</configuration>
2创建共享项目ShareMe
选择“Windows>共享项目”,添加项目ShareMe,用于公共方法共享,Android、iOS、Windows客户端都用的到。
添加类文件OSClient.cs
using System;
using Microsoft.AspNet.SignalR.Client;
using System.Threading.Tasks;
namespace ShareMe
{
public class OSClient
{
private readonly string _platform;
private readonly HubConnection _connection;
private readonly IHubProxy _proxy;
public event EventHandler<string> OnSendPlatform;
public OSClient(string platform)
{
_platform = platform;
_connection = new HubConnection("http://127.0.0.1:46900");//url要与服务端的ServerUri保持一致,否则通信不上
_proxy = _connection.CreateHubProxy("MyHub");//创建Hub,与服务端MyHub类名一致
}
public async Task Connect()
{
await _connection.Start();
_proxy.On("sendplatform",(string platform,string message)=> {
if (OnSendPlatform!=null)
{
OnSendPlatform(this, string.Format("{0}:{1}",platform,message));
}
});
Send("Connected");
}
public Task Send(string message)
{
return _proxy.Invoke("SendPlatform", _platform, message;//调用方法名与服务端方法名一致
}
}
}
3创建安卓客户端AndroidApp
3.1新建项目
选择“Android>Blank App”新建项目AndroidApp。
3.2添加相关引用
首先添加共享项目ShareMe的引用
然后添加SignalR程序包的引用。在添加SignalR程序包的过程中遇到了比较棘手的问题,使用NuGet为项目添加SignalR引用时总是报错。
考虑是由于Mono还不支持的当前SignalR的版本,在网上查阅了相关资料后发现,SignalR是可以用于Mono的,但是需要使用portbale版本,可以在解决方案根目录下“packages\Microsoft.AspNet.SignalR.Client.2.2.0\lib\portable-net45+sl5+netcore45+wp8+wp81”找到,手动添加引用。
添加引用后编译会出错,把错误提示中出现的未引用程序包通过NuGet添加下(有Newton.Json及System.Net.Http等)。其中可能会有System.Net.Http.Extensions也需要手动添加引用。所有引用添加完后再次编译提示
这当中还有一个小插曲,就是引用添加完之后,编译总是失败并提示:
"aapt.exe"已退出,代码为“-1073741819”
尝试了N多次,即使新建项目后什么都不做依然会报错,这就比较奇怪了。后来发现了一篇Xamarin作者之一的文章,说到是由于Android SDK Bulid-tools 22.0.1自身问题所致,移除这个版本的编译工具即可。但是我在Android SDK Manager中没有安装这个版本,所以更加疑惑,后来向别人请教说可能是Android SDK Bulid-tools 24有问题。于是把这个版本的也卸载了,问题解决。
3.3添加客户端方法
添加完成后的项目文件结构如下:
Resources是项目资源所在,其中drawable主要是图片,layout是布局,values是静态资源,这些文件夹都是项目创建时自动生成的。后台处理主要包含在MainActivity.cs中。
打开布局文件“Resources\layout\Main.xml”,在设计器中添加一个EditText和ListView(把原来的删掉,直接从工具箱里拖,然后再属性中修改id)
添加完成后重新生成一下,不然资源文件在后台找不到。修改完成后的布局文件Main.xml代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/Input" />
<ListView
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/Messages" />
</LinearLayout>
在后台文件MainActivity.cs中添加相应的方法:
using Android.App;
using Android.Widget;
using Android.OS;
using ShareMe;//共享项目引用
using Android.Views.InputMethods;
using System.Collections.Generic;
namespace AndroidApp
{
[Activity(Label = "AndroidApp", MainLauncher = true, Icon = "@drawable/icon")]
public class MainActivity : Activity
{
int count = 1;
protected override async void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
// Get our button from the layout resource,
// and attach an event to it
var client = new OSClient("Android");
var input = FindViewById<EditText>(Resource.Id.Input);
var messages = FindViewById<ListView>(Resource.Id.Messages);
var inputManager = (InputMethodManager)GetSystemService(InputMethodService);
var adapter = new ArrayAdapter<string>(this, Android.Resource.Layout.SimpleListItem1, new List<string>());
messages.Adapter = adapter;
await client.Connect();
input.EditorAction += delegate
{
inputManager.HideSoftInputFromWindow(input.WindowToken, HideSoftInputFlags.None);
if (string.IsNullOrEmpty(input.Text))
{
return;
}
client.Send(input.Text);
input.Text = "";
};
client.OnSendPlatform+= (sender, message) => RunOnUiThread(() =>adapter.Add(message));
}
}
}
最后启动服务端,在模拟器上运行AndroidApp查看效果。
后记
写这个示例中间不少曲折,但也收获很多。写的过程中查找好多的文章都是几年前的,时效性可能有折扣,还是要多想多看多学多用。