Related articles:
Introduction
One of challenges in the interprocess communication when communicating across platforms (e.g. between Android and .NET) is how to encode (serialize) messages so that they can be understood by both platforms. Default binary serializers provided by platforms are not compatible and so the common solution is to encode messages into some text format e.g. XML or JSON.
This can be perfectly ok in many cases but for applications with high performance expectations using the text format may not be an optimal solution.
The example bellow demonstrates how to use Protocol Buffers binary serialization in the inter-process communication between Android and .NET applications.
You Need to Download
In order to build the example source code you will need to add references to related libraries into the project. To get these libraries you can download:
Protocol Buffers libraries are open source projects that can be found at:
- protobuf - Google implementation of Protocol Buffers for Java, C++ and Python.
- protobuf-net - Protocol Buffers implementation from Marc Gravell for .NET platforms.
- Eneter.ProtoBuf.Serializer - Open source project to integrate Protocol Buffers and Eneter Messaging Framework.
Add Following References into your Project
Into .NET project:
- protobuf-net.dll - protocol buffers serializer for .NET, Windows Phone, Silverlight and Compact Framework developed by Marc Gravell.
- Eneter.ProtoBuf.Serializer.dll - implements serializer for Eneter Messaging Framework using protobuf-net.dll.
- Eneter.Messaging.Framework.dll - lightweight cross-platform framework for inter-process communication.
Into Android project:
- protobuf.jar - protocol buffers serializer for Java and Android developed by Google.
- eneter-protobuf-serializer.jar - implements serializer for Eneter Messaging Framework using protobuf.jar from Google.
- eneter-messaging.jar - lightweight cross-platform framework for inter-process communication.
Important: please follow this procedure (for Eclipse) to add libraries into the Android project:
(To add a library into the project you need to import it instead of adding it via project properties.
Also ensure Java compliance level is set to 6.0. Properties -> Java Compiler -> JDK Compliance -> 1.6.)
- Create a new folder 'libs' in your project. (use exactly name libs)
- Right click on 'libs' and choose 'Import...' -> 'General/File System' -> 'Next'.
- Then click 'Browser' button for 'From directory' and navigate to directory with libraries you want to add.
- Select check boxes for libraries you want to add.
- Press 'Finish'
Protocol Buffers
Protocol Buffers is a binary serialization originally developed by Google to share data among applications developed in different languages like Java, C++ and Python. It became the open source and was ported to other languages and platforms too.
The biggest advantage of Protocol Buffers is its performance
and availability on multiple platforms
what makes it an alternative to consider when designing the communication between applications.
If you are interested a simple performance measurement is available at https://code.google.com/p/eneter-protobuf-serializer/wiki/PerformanceMeasurements.
Working With Protocol Buffers
The following procedure is optimized for defining messages for cross-platform communication:
(If you want to use Protocol Buffers only in .NET you do not have to declare messages via the 'proto' file but you can declare them directly in the source code by attributing classes - same way as using DataContractSerializer.)
- Declare messages in the 'proto' file.
- Compile the 'proto' file into the source code (C# and Java). It transforms declared messages to classes containing specified fields and the serialization functionality.
- Include generated source files into C# and Java projects.
- Initialize Eneter communication components to use
EneterProtoBufSerializer
.
Example Code
The example bellow is exactly the same as in my previous article Android: How to communicate with .NET application via TCP.
The only difference is the code in this article uses EneterProtoBufSerializer instead of XmlStringSerializer
.
Please refer to Android: How to communicate with .NET application via TCP if you need details about how to use TCP on Android and how to setup the IP address in the emulator.
proto File
The 'proto' file represents a contract describing messages that shall be used for the interaction.
Messages are declared in the platform neutral protocol buffer language
- for the syntax details you can refer to https://developers.google.com/protocol-buffers/docs/proto.
Messages in our example are declared in the file MessageDeclarations.proto:
message MyRequest
{
required string Text = 1;
}
message MyResponse
{
required int32 Length = 1;
}
The 'proto' file is then compiled to C# and Java source code. Declared messages are transformed to classes containing declared fields and serialization functionality.
The following commands were used in our example to compile the 'proto' file:
protogen.exe -i:MessageDeclarations.proto -o:MessageDeclarations.cs
protoc.exe -I=./ --java_out=./ ./MessageDeclarations.proto
Android Client Application
The Android client is a very simple application allowing user to put some text message and send the request to the service to get back the length of the text.
When the response message is received it must be marshaled to the UI thread to display the result.
The client uses EneterProtoBufSerializer
. It instantiates the serializer in the openConnection()
method and puts its reference to the DuplexTypedMessagesFactory
ensuring so the message sender will use Protocol Buffers.
The whole implementation is very simple:
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
{
private Handler myRefresh = new Handler();
private EditText myMessageTextEditText;
private EditText myResponseEditText;
private Button mySendRequestBtn;
private IDuplexTypedMessageSender<MyResponse, MyRequest> mySender;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
myMessageTextEditText = (EditText) findViewById(R.id.messageTextEditText);
myResponseEditText = (EditText) findViewById(R.id.messageLengthEditText);
mySendRequestBtn = (Button) findViewById(R.id.sendRequestBtn);
mySendRequestBtn.setOnClickListener(myOnSendRequestClickHandler);
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()
{
mySender.detachDuplexOutputChannel();
super.onDestroy();
}
private void openConnection() throws Exception
{
ISerializer aSerializer = new ProtoBufSerializer();
IDuplexTypedMessagesFactory aSenderFactory = new DuplexTypedMessagesFactory(aSerializer);
mySender = aSenderFactory.createDuplexTypedMessageSender(MyResponse.class, MyRequest.class);
mySender.responseReceived().subscribe(myOnResponseHandler);
IMessagingSystemFactory aMessaging = new TcpMessagingSystemFactory();
IDuplexOutputChannel anOutputChannel
= aMessaging.createDuplexOutputChannel("tcp://10.0.2.2:8060/");
mySender.attachDuplexOutputChannel(anOutputChannel);
}
private void onSendRequest(View v)
{
final MyRequest aRequestMsg = MyRequest.newBuilder()
.setText(myMessageTextEditText.getText().toString())
.build();
try
{
mySender.sendRequestMessage(aRequestMsg);
}
catch (Exception err)
{
EneterTrace.error("Sending the message failed.", err);
}
}
private void onResponseReceived(Object sender,
final TypedResponseReceivedEventArgs<MyResponse> e)
{
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 Service Application
The .NET service is a simple console application listening to TCP and receiving requests to calculate the length of a given text.
The service uses EneterProtoBufSerializer
. It instantiates the serializer and puts its reference to the DuplexTypedMessagesFactory
ensuring so the message receiver will use Protocol Buffers to deserialize incoming messages and serialize response messages.
The whole implementation is very simple:
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)
{
ISerializer aSerializer = new ProtoBufSerializer();
IDuplexTypedMessagesFactory aReceiverFactory =
new DuplexTypedMessagesFactory(aSerializer);
myReceiver = aReceiverFactory.CreateDuplexTypedMessageReceiver<MyResponse, MyRequest>();
myReceiver.MessageReceived += OnMessageReceived;
IMessagingSystemFactory aMessaging = new TcpMessagingSystemFactory();
IDuplexInputChannel anInputChannel
= aMessaging.CreateDuplexInputChannel("tcp://127.0.0.1:8060/");
myReceiver.AttachDuplexInputChannel(anInputChannel);
Console.WriteLine("The service is running. To stop press enter.");
Console.ReadLine();
myReceiver.DetachDuplexInputChannel();
}
private static void OnMessageReceived(object sender,
TypedRequestReceivedEventArgs<MyRequest> e)
{
Console.WriteLine("Received: " + e.RequestMessage.Text);
MyResponse aResponse = new MyResponse();
aResponse.Length = e.RequestMessage.Text.Length;
myReceiver.SendResponseMessage(e.ResponseReceiverId, aResponse);
}
}
}