Tuesday, September 28, 2010

google protocol buffers in android

Lets face it... there is a good chance your android app will need some sort of mobile to server communication of some sort. You could go the route of raw sockets, with "packets" of your own design. You could post json, you could post xml, or you could build a web form and post to a url.

All of these are very valid options, however I've kind of decided I'm a fan of protocol buffers. What are protocol buffers you might ask? A language / platform independent framework for serializing / deserializing binary data that generally produces lightweight messages quickly.(read more about it here: http://code.google.com/p/protobuf/)

Protocol buffers

The general idea is you have a .proto file that defines your "protocol" in plain human readable text. It supports many data types, ability to not provide a property, and even send raw bytes.

I've tried both the usual protocol buffers implementation from url above with and without the -lite flag, and the protobuf implementation for j2me and frankly seems like so far the j2me implementation is faster and lighter weight, and hasn't yet been problematic for missing anything.  (http://code.google.com/p/protobuf-javame/)

I am going to create a very simple proto file that might represent a message you may be sending in your application.


message LocationSearch{
       required string searchterms = 1; // the text body of the search
       optional double latitude  = 2; //optional lat/lng coords... if its provided we'll use it.
       optional double longitude = 3;
}


I can run proto generator (more on this in a moment) on it and it will create LocationSearch.java on the android side and then proto generator on the .net side (my server will be .net in this case) and get LocationSearch.cs.

In android we need to add the reference, so right click project and then go to Build Path -> Add External Archives. Choose the location you downloaded protobuf-javame-1.0.0.jar. Copy the generated .java file to some place in your packages and we'll get started momentarily.

How you decide to delineate messages could be anything, but I've just implemented handlers so if I post to /LocationSearch on a specific server, I should get a response back. To do this, we'll need to download and reference (the same way we referenced protobuf) apache httpclient library v 4.02.

I've created a utility method that posts a simple message from byte[] and returns a byte[] representing the response. I've not read up on optimizing for android recently... so there are undoubtedly places that need to be final etc. First the method...


public static byte[] postMessage(String url,byte[] data, String filename) throws Exception
    {
  byte[] rData = new byte[0];
  HttpClient httpclient = new DefaultHttpClient();
  HttpPost httppost = new HttpPost(url);

  MultipartEntity reqEntity = new MultipartEntity(
   HttpMultipartMode.BROWSER_COMPATIBLE);

  reqEntity.addPart("message",
    new InputStreamKnownSizeBody(
      new ByteArrayInputStream(data),
      data.length, "application/octet-stream", "message"));
  Log.i("Sending:",new String(data));
  httppost.setEntity(reqEntity);

  System.out.println("executing request " + httppost.getRequestLine());
  HttpResponse response = httpclient.execute(httppost);
  HttpEntity resEntity = response.getEntity();

  if (resEntity != null) {
   
   ByteArrayOutputStream bos = new ByteArrayOutputStream();
   resEntity.writeTo(bos);
   rData = bos.toByteArray();
   Log.i("Response",new String(rData));

  }
  return rData;
 }

and a prerequisite class to handle being able to upload from a byte[] vs disk.

public class InputStreamKnownSizeBody extends InputStreamBody{
 private int lenght;

 public InputStreamKnownSizeBody(
   final InputStream in, final int lenght,
   final String mimeType, final String filename) {
  super(in, mimeType, filename);
  this.lenght = lenght;
 }

 @Override
 public long getContentLength() {
  return this.lenght;
 }
}

Ok... so now we have the facility to post a message and get a response back. Lets see what a round trip might look like...

//lets create a protocol buff object, and send it on its way.
 LocationSearch search = LocationSearch.newBuilder().setsearchterms("rob john*");  

 // remember lat/lng are optional
 byte[] response = postMessage("127.0.0.1/LocationSearch",search.toByteArray(),"dummy");

 //now if we had created a "LocationSearchResponse class with applicable data, we could do...
 LocationSearchResponse responseObj = LocationSearchResponse.parseFrom(response);

A couple notes about my examples
  • I assume the server portion of this you guys can set up, and handle parsing/sending a response. The general scenario is pretty much the same everywhere
  • You'll want to make sure all your basics are covered as far as exception handling and failing gracefully (some of these things get in the way of conveying concepts =)
  • Obviously if you aren't running a server on your local machine, the URL in the example above will simply not work.
  • Filename in the postmessage method above is currently not doing a thing (yet)

Homework

  1. Create the corresponding return message with whatever you think might be appropriate to receive from server. 
  2. Implement server side logic. Once you parse the form and get the "message" element which from the server appears as a posted file, .parseFrom into your message format. Create the response, and write the resulting bytes to the response.
I hope this example proved helpful if nothing else, than to know what another option is as far as device to server (or even device to device) communications.

Until next time!

1 comments:

Phil said...

Do you have any updated examples? You said that some of your code wasn't yet optimized. I am working on my first android app that utilizes a server. If it's easier, contact me on twitter at @thePeacedOut ... THANKS!