65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (43投票s)

2013年8月20日

CPOL

5分钟阅读

viewsIcon

113870

downloadIcon

2649

使用快速二进制序列化(Protocol Buffers)代替 XML 或 JSON,提高 Android 和 .NET 之间进程间通信的性能。

相关文章

引言

跨平台通信(例如,Android 和 .NET 之间)的进程间通信面临的挑战之一是如何对消息进行编码(序列化),以便两个平台都能理解。平台提供的默认二进制序列化器不兼容,因此常见的解决方案是将消息编码为某种文本格式,例如 XML 或 JSON。在许多情况下,这完全没问题,但对于对性能有较高期望的应用程序来说,使用文本格式可能不是最佳解决方案。

下面的示例演示了如何在 Android 和 .NET 应用程序之间的进程间通信中使用 Protocol Buffers 二进制序列化。

您需要下载

为了构建示例源代码,您需要将相关库的引用添加到项目中。要获取这些库,您可以下载

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。)

  1. 在项目中创建一个名为 'libs' 的新文件夹。(请务必使用 libs 这个名称)
  2. 右键单击 'libs' 并选择 'Import...' -> 'General/File System' -> 'Next'
  3. 然后单击 'From directory' 旁边的 'Browser' 按钮,导航到包含您要添加的库的目录。
  4. 选中您要添加的库的复选框。
  5. 按 '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 相同。)

  1. 在 'proto' 文件中声明消息。
  2. 将 'proto' 文件编译为源代码(C# 和 Java)。它会将声明的消息转换为包含指定字段和序列化功能的类。
  3. 将生成的源文件包含到 C# 和 Java 项目中。
  4. 初始化 Eneter 通信组件以使用 EneterProtoBufSerializer

640249/UsingProtoBuf.png

示例代码

下面的示例与我之前的文章 Android:如何通过 TCP 与 .NET 应用程序通信 中的示例完全相同。唯一的区别是本文中的代码使用了 EneterProtoBufSerializer 而不是 XmlStringSerializer

如果您需要有关如何在 Android 上使用 TCP 以及如何在模拟器中设置 IP 地址的详细信息,请参考 Android:如何通过 TCP 与 .NET 应用程序通信

640249/CommunicationBetweenAndroidandNETProtoBuf.png

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);
        }
    }
}
© . All rights reserved.