Android:使用 Protocol Buffers 与 .NET 进行快速通信






4.90/5 (43投票s)
使用快速二进制序列化(Protocol Buffers)代替 XML 或 JSON,提高 Android 和 .NET 之间进程间通信的性能。
相关文章
引言
跨平台通信(例如,Android 和 .NET 之间)的进程间通信面临的挑战之一是如何对消息进行编码(序列化),以便两个平台都能理解。平台提供的默认二进制序列化器不兼容,因此常见的解决方案是将消息编码为某种文本格式,例如 XML 或 JSON。在许多情况下,这完全没问题,但对于对性能有较高期望的应用程序来说,使用文本格式可能不是最佳解决方案。
下面的示例演示了如何在 Android 和 .NET 应用程序之间的进程间通信中使用 Protocol Buffers 二进制序列化。
您需要下载
为了构建示例源代码,您需要将相关库的引用添加到项目中。要获取这些库,您可以下载
- Eneter.ProtoBuf.Serializer - Eneter 的 Protocol Buffer 序列化器,它还包含已编译的 Protocol Buffer 库和用于 'proto' 文件的实用程序。
- Eneter.Messaging.Framework - 通信框架。
Protocol Buffers 库是开源项目,可以在以下位置找到:
- protobuf - Google 的 Protocol Buffers 实现,适用于 Java、C++ 和 Python。
- protobuf-net - Marc Gravell 为 .NET 平台实现的 Protocol Buffers。
- Eneter.ProtoBuf.Serializer - 用于集成 Protocol Buffers 和 Eneter Messaging Framework 的开源项目。
将以下引用添加到您的项目中
到 .NET 项目
- protobuf-net.dll - 由 Marc Gravell 开发的 .NET、Windows Phone、Silverlight 和 Compact Framework 的 Protocol Buffers 序列化器。
- Eneter.ProtoBuf.Serializer.dll - 使用 protobuf-net.dll 为 Eneter Messaging Framework 实现的序列化器。
- Eneter.Messaging.Framework.dll - 用于进程间通信的轻量级跨平台框架。
到 Android 项目
- protobuf.jar - 由 Google 开发的 Java 和 Android 的 Protocol Buffers 序列化器。
- eneter-protobuf-serializer.jar - 使用 Google 的 protobuf.jar 为 Eneter Messaging Framework 实现的序列化器。
- eneter-messaging.jar - 用于进程间通信的轻量级跨平台框架。
重要:请按照此过程(适用于 Eclipse)将库添加到 Android 项目
(要将库添加到项目中,您需要导入它,而不是通过项目属性添加。
另外,请确保 Java 编译级别设置为 6.0。Properties -> Java Compiler -> JDK Compliance -> 1.6。)
- 在项目中创建一个名为 'libs' 的新文件夹。(请务必使用 libs 这个名称)
- 右键单击 'libs' 并选择 'Import...' -> 'General/File System' -> 'Next'。
- 然后单击 'From directory' 旁边的 'Browser' 按钮,导航到包含您要添加的库的目录。
- 选中您要添加的库的复选框。
- 按 'Finish'
Protocol Buffers
Protocol Buffers 最初是由 Google 开发的二进制序列化,用于在不同语言(如 Java、C++ 和 Python)开发的应用程序之间共享数据。它已经成为开源的,并被移植到其他语言和平台。
Protocol Buffers 的最大优势在于其性能
和多平台可用性
,这使其成为设计应用程序之间通信时值得考虑的替代方案。
如果您感兴趣,可以在 https://code.google.com/p/eneter-protobuf-serializer/wiki/PerformanceMeasurements找到简单的性能测量。
使用 Protocol Buffers
以下过程针对定义跨平台通信消息进行了优化
(如果您只想在 .NET 中使用 Protocol Buffers,则无需通过 'proto' 文件声明消息,而是可以直接在源代码中声明它们,通过为类添加属性来实现 - 与使用 DataContractSerializer 相同。)
- 在 'proto' 文件中声明消息。
- 将 'proto' 文件编译为源代码(C# 和 Java)。它会将声明的消息转换为包含指定字段和序列化功能的类。
- 将生成的源文件包含到 C# 和 Java 项目中。
- 初始化 Eneter 通信组件以使用
EneterProtoBufSerializer
。
示例代码
下面的示例与我之前的文章 Android:如何通过 TCP 与 .NET 应用程序通信 中的示例完全相同。唯一的区别是本文中的代码使用了 EneterProtoBufSerializer 而不是 XmlStringSerializer
。
如果您需要有关如何在 Android 上使用 TCP 以及如何在模拟器中设置 IP 地址的详细信息,请参考 Android:如何通过 TCP 与 .NET 应用程序通信。
proto 文件
'proto' 文件代表一个描述将用于交互的消息的契约。消息以平台中立的 protocol buffer 语言
声明 - 有关语法详细信息,您可以参考 https://developers.google.com/protocol-buffers/docs/proto。
我们示例中的消息声明在 MessageDeclarations.proto 文件中
// Request Message
message MyRequest
{
required string Text = 1;
}
// Response Message
message MyResponse
{
required int32 Length = 1;
}
然后将 'proto' 文件编译为 C# 和 Java 源代码。声明的消息被转换为包含声明字段和序列化功能的类。
在我们的示例中,用于编译 'proto' 文件使用的命令如下:
protogen.exe -i:MessageDeclarations.proto -o:MessageDeclarations.cs protoc.exe -I=./ --java_out=./ ./MessageDeclarations.proto
Android 客户端应用程序
Android 客户端是一个非常简单的应用程序,允许用户输入一些文本消息,然后发送请求给服务以获取文本的长度。
收到响应消息后,必须将其编组到 UI 线程以显示结果。
客户端使用 EneterProtoBufSerializer
。它在 openConnection()
方法中实例化序列化器,并将其引用放入 DuplexTypedMessagesFactory
,从而确保消息发送者使用 Protocol Buffers。
整个实现非常简单
package net.client;
import message.declarations.MessageDeclarations.*;
import eneter.messaging.dataprocessing.serializing.ISerializer;
import eneter.messaging.diagnostic.EneterTrace;
import eneter.messaging.endpoints.typedmessages.*;
import eneter.messaging.messagingsystems.messagingsystembase.*;
import eneter.messaging.messagingsystems.tcpmessagingsystem.TcpMessagingSystemFactory;
import eneter.net.system.EventHandler;
import eneter.protobuf.ProtoBufSerializer;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.*;
public class AndroidNetCommunicationClientActivity extends Activity
{
// UI controls
private Handler myRefresh = new Handler();
private EditText myMessageTextEditText;
private EditText myResponseEditText;
private Button mySendRequestBtn;
// Sender sending MyRequest and as a response receiving MyResponse.
private IDuplexTypedMessageSender<MyResponse, MyRequest> mySender;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Get UI widgets.
myMessageTextEditText = (EditText) findViewById(R.id.messageTextEditText);
myResponseEditText = (EditText) findViewById(R.id.messageLengthEditText);
mySendRequestBtn = (Button) findViewById(R.id.sendRequestBtn);
// Subscribe to handle the button click.
mySendRequestBtn.setOnClickListener(myOnSendRequestClickHandler);
// Open the connection in another thread.
// Note: From Android 3.1 (Honeycomb) or higher
// it is not possible to open TCP connection
// from the main thread.
Thread anOpenConnectionThread = new Thread(new Runnable()
{
@Override
public void run()
{
try
{
openConnection();
}
catch (Exception err)
{
EneterTrace.error("Open connection failed.", err);
}
}
});
anOpenConnectionThread.start();
}
@Override
public void onDestroy()
{
// Stop listening to response messages.
mySender.detachDuplexOutputChannel();
super.onDestroy();
}
private void openConnection() throws Exception
{
// Instantiate Protocol Buffer based serializer.
ISerializer aSerializer = new ProtoBufSerializer();
// Create sender sending MyRequest and as a response receiving MyResponse
// The sender will use Protocol Buffers to serialize/deserialize messages.
IDuplexTypedMessagesFactory aSenderFactory = new DuplexTypedMessagesFactory(aSerializer);
mySender = aSenderFactory.createDuplexTypedMessageSender(MyResponse.class, MyRequest.class);
// Subscribe to receive response messages.
mySender.responseReceived().subscribe(myOnResponseHandler);
// Create TCP messaging for the communication.
// Note: 10.0.2.2 is a special alias to the loopback (127.0.0.1)
// on the development machine.
IMessagingSystemFactory aMessaging = new TcpMessagingSystemFactory();
IDuplexOutputChannel anOutputChannel
= aMessaging.createDuplexOutputChannel("tcp://10.0.2.2:8060/");
//= aMessaging.createDuplexOutputChannel("tcp://192.168.1.102:8060/");
// Attach the output channel to the sender and be able to send
// messages and receive responses.
mySender.attachDuplexOutputChannel(anOutputChannel);
}
private void onSendRequest(View v)
{
// Create the request message using ProtoBuf builder pattern.
final MyRequest aRequestMsg = MyRequest.newBuilder()
.setText(myMessageTextEditText.getText().toString())
.build();
// Send the request message.
try
{
mySender.sendRequestMessage(aRequestMsg);
}
catch (Exception err)
{
EneterTrace.error("Sending the message failed.", err);
}
}
private void onResponseReceived(Object sender,
final TypedResponseReceivedEventArgs<MyResponse> e)
{
// Display the result - returned number of characters.
// Note: Marshal displaying to the correct UI thread.
myRefresh.post(new Runnable()
{
@Override
public void run()
{
myResponseEditText.setText(Integer.toString(e.getResponseMessage().getLength()));
}
});
}
private EventHandler<TypedResponseReceivedEventArgs<MyResponse>> myOnResponseHandler
= new EventHandler<TypedResponseReceivedEventArgs<MyResponse>>()
{
@Override
public void onEvent(Object sender,
TypedResponseReceivedEventArgs<MyResponse> e)
{
onResponseReceived(sender, e);
}
};
private OnClickListener myOnSendRequestClickHandler = new OnClickListener()
{
@Override
public void onClick(View v)
{
onSendRequest(v);
}
};
}
.NET 服务应用程序
该 .NET 服务是一个简单的控制台应用程序,监听 TCP 并接收计算给定文本长度的请求。
该服务使用 EneterProtoBufSerializer
。它实例化序列化器,并将其引用放入 DuplexTypedMessagesFactory
,从而确保消息接收者使用 Protocol Buffers 来反序列化传入消息并序列化响应消息。
整个实现非常简单
using System;
using Eneter.Messaging.DataProcessing.Serializing;
using Eneter.Messaging.EndPoints.TypedMessages;
using Eneter.Messaging.MessagingSystems.MessagingSystemBase;
using Eneter.Messaging.MessagingSystems.TcpMessagingSystem;
using Eneter.ProtoBuf;
using message.declarations;
namespace ServiceExample
{
class Program
{
private static IDuplexTypedMessageReceiver<MyResponse, MyRequest> myReceiver;
static void Main(string[] args)
{
// Instantiate Protocol Buffer based serializer.
ISerializer aSerializer = new ProtoBufSerializer();
// Create message receiver receiving 'MyRequest' and receiving 'MyResponse'.
// The receiver will use Protocol Buffers to serialize/deserialize messages.
IDuplexTypedMessagesFactory aReceiverFactory =
new DuplexTypedMessagesFactory(aSerializer);
myReceiver = aReceiverFactory.CreateDuplexTypedMessageReceiver<MyResponse, MyRequest>();
// Subscribe to handle messages.
myReceiver.MessageReceived += OnMessageReceived;
// Create TCP messaging.
IMessagingSystemFactory aMessaging = new TcpMessagingSystemFactory();
IDuplexInputChannel anInputChannel
= aMessaging.CreateDuplexInputChannel("tcp://127.0.0.1:8060/");
// Attach the input channel and start to listen to messages.
myReceiver.AttachDuplexInputChannel(anInputChannel);
Console.WriteLine("The service is running. To stop press enter.");
Console.ReadLine();
// Detach the input channel and stop listening.
// It releases the thread listening to messages.
myReceiver.DetachDuplexInputChannel();
}
// It is called when a message is received.
private static void OnMessageReceived(object sender,
TypedRequestReceivedEventArgs<MyRequest> e)
{
Console.WriteLine("Received: " + e.RequestMessage.Text);
// Create the response message.
MyResponse aResponse = new MyResponse();
aResponse.Length = e.RequestMessage.Text.Length;
// Send the response message back to the client.
myReceiver.SendResponseMessage(e.ResponseReceiverId, aResponse);
}
}
}