[Android] Communication between Android and Google AppEngine with serialization

15/03/2012

The purpose of this article is to make a benchmark of 3 frameworks that we could use as developers to make an Android application communicate with a Google AppEngine Server.

What is compared and how ?

I study 4 axes in this benchmark :

  1. Is it simple to implement ? it’s purely subjective but it could give a little idea about how is it complicated to integrate it in your projects.
  2. Could I manage complex objects ? it’s to evaluate the complexity of POJO we could send or retrieve to / from the server.
  3. What is the impact on final apk size ? it’s use to determine the impact of the framework on the apk final size. This is very important because the available place on Android phone is very limited sometimes.
  4. Is it effective ? we will look at the time between the call of server and the response. It is i think the most important criteria for the final choice.

 
For the benchmark, we will use those POJO :

IObjectA

This object show that we could have complex objects ownig other objects

package com.binomed.client;
 
import java.io.Serializable;
import java.util.List;
 
public interface IObjectA extends Serializable {
 
	T getObjectB();
 
	void setObjectB(T objectB);
 
	List getListObjectB();
 
	void setListObjectB(List listObjectB);
 
	String getName();
 
	void setName(String name);
 
}

IObjectB

This object show a very simple POJO

package com.binomed.client;
 
import java.io.Serializable;
 
public interface IObjectB extends Serializable {
 
	String getName();
 
	void setName(String name);
 
	int getNum();
 
	void setNum(int num);
 
}

IObjectBMap

This object show a simple POJO with an HashMap in order to test the serialization

package com.binomed.client;
 
import java.io.Serializable;
import java.util.HashMap;
 
public interface IObjectBMap extends IObjectB, Serializable {
 
	HashMap getMap();
 
	void setMap(HashMap map);
 
}

 

For this we are going to implements this application

 

This is a very simple application with 3 tabs. On each tab, there is 2 buttons.

  • The first button make a basic request which returns from server an instance of IObjectA.
  • The second button takes care of the field to the right by asking “n” IObjectB in the list of IObjectA (“n” is the number in the field)

What are the compare elements ?

I have selected 3 frameworks :

  1. java-json-rpc. I choose this framework because I want a neutral framework so I search somes java json rpc librairies and this one look pretty good and simple. The project is based on JSON-PRC 2.0 specifications. It is still in Alpha version and hasn’t evolved since may 2011.
  2. google request-factory. I choose this framework because it is the referenced framework use while you are using the plugin Appengine with android. Request Factory is a recent framework and is pretty popular in GWT projects.
  3. restlet. I choose this framework because it is one of the best implementation of REST which is a very popular framework used on severals servers.

I choose those 3 frameworks because the both of three are solutions to do serialization between java server and java client. They are all using json messages.

Architecture

This tutorial is composed by 2 projects :

  1. An android application
  2. An Google AppEngine application

The android application will comunicate with the AppEngine application in local. In order to minimize duplicate code, the AppEngine project will have a “shared” folder with the Android project.

Implementation

This tutorial will be done with eclipse 3.7, Google Plugin for indigo and android ADT plugin. All used jar are available on the github project of this tutorial (see at the end of article). First we have to create the android project : File->New -> Android Project

Select as minimal sdk : donuts. I didn’t test this tutorial with previous versions of sdk but I think it will work to.

Create the AppEngine project : File -> New -> Web Application Project

Create the shared folder. This folder will contain all shared interfaces and all POJOs that will be exchange between projects.

Right Click on AppEngineRpcProject -> New ->Source Folder. Call it “shared“.

Now we have to add it on android project :

Right Click on AndroidRpcProject -> Build Path -> Configure Build Path. Click on button “Link Source” and select the folder shared in your file system.


 
Normaly, you should have the same image for yours projects
 

First we have to create the IObjectA, IObjectB previously seen. Go on the AppEngineRpcProject in the shared folder and create a package with the name “com.binomed.client“.   In this package create the previous interface IObjectA, IObjectB and IObjectBMap.

Now we can create the squeleton of our android project :

Now we have to create  the main Android activity : AndroidRpcProjectActivity. Put the folowing code in the class :

package com.binomed.rpc;
 
public class AndroidRpcProjectActivity extends TabActivity implements OnClickListener {
 
	// In android when we call localhost, we have to set the adress 10.0.2.2
	public static final String LOCALHOST = "http://10.0.2.2:8888"; //$NON-NLS-1$
	private static final String TAG = "TestAndroidActivity";
 
	// the editTexts area where results will be puts
	private EditText requestFactory, jsonRpc, restlet;
	// the editText to define the number of objects to return
	private EditText nbParamRequestFactory, nbParamJsonRpc, nbParamRestlet;
	// Each button do a rpc call
	private Button btnRequestFactory, btnJsonRpc, btnRestlet, btnRequestFactoryParams, btnJsonRpcParams, btnRestletParams;
	private Context mContext;
 
	// Constants used for the abstract task define in this class
	private static final int JSON_RPC = 1;
	private static final int REQUEST_FACTORY = 2;
	private static final int REST = 3;
 
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
 
	}
 
	@Override
	protected void onResume() {
		super.onResume();
 
		setContentView(R.layout.tabs);
		mContext = this;
 
		TabHost tabHost = getTabHost();
 
		// Tab for Json Rpc
		TabSpec jsonRpcTab = tabHost.newTabSpec("JsonRpc");
		jsonRpcTab.setIndicator("Json Rpc");
		jsonRpcTab.setContent(R.id.jsonRpcLayout);
 
		// Tab for Request Factory
		TabSpec requestFactoryTab = tabHost.newTabSpec("RequestFactory");
		requestFactoryTab.setIndicator("Request Factory");
		requestFactoryTab.setContent(R.id.requestFactoryLayout);
 
		// Tab for Rest
		TabSpec restTab = tabHost.newTabSpec("Rest");
		restTab.setIndicator("Rest");
		restTab.setContent(R.id.restLayout);
 
		// Adding all TabSpec to TabHost
		tabHost.addTab(jsonRpcTab); // Adding json rpc tab
		tabHost.addTab(requestFactoryTab); // Adding request factory tab
		tabHost.addTab(restTab); // Adding rest tab
 
		// We get map all widgets
		requestFactory = (EditText) findViewById(R.id.requestFactory);
		nbParamRequestFactory = (EditText) findViewById(R.id.nbParamRequestFactory);
		btnRequestFactory = (Button) findViewById(R.id.btnRequestFactory);
		btnRequestFactoryParams = (Button) findViewById(R.id.btnRequestFactoryParams);
		jsonRpc = (EditText) findViewById(R.id.jsonRpc);
		nbParamJsonRpc = (EditText) findViewById(R.id.nbParamJsonRpc);
		btnJsonRpc = (Button) findViewById(R.id.btnJsonRpc);
		btnJsonRpcParams = (Button) findViewById(R.id.btnJsonParams);
		restlet = (EditText) findViewById(R.id.rest);
		nbParamRestlet = (EditText) findViewById(R.id.nbParamRest);
		btnRestlet = (Button) findViewById(R.id.btnRest);
		btnRestletParams = (Button) findViewById(R.id.btnRestParams);
 
		// We add the listener on buttons
		btnRequestFactory.setOnClickListener(this);
		btnRequestFactoryParams.setOnClickListener(this);
		btnJsonRpc.setOnClickListener(this);
		btnJsonRpcParams.setOnClickListener(this);
		btnRestlet.setOnClickListener(this);
		btnRestletParams.setOnClickListener(this);
 
	}
 
	@Override
	public void onClick(View v) {
                // generic method wich just call an AsyncTasj with correct parameters (the type of service, if parameters are passed or not)
		switch (v.getId()) {
		case R.id.btnJsonRpc: {
			new TaskTest(jsonRpc, btnJsonRpc, JSON_RPC, -1).execute();
			break;
		}
		case R.id.btnJsonParams: {
			new TaskTest(jsonRpc, btnJsonRpcParams, JSON_RPC, Integer.valueOf(nbParamJsonRpc.getText().toString())).execute();
			break;
		}
		case R.id.btnRequestFactory: {
			new TaskTest(requestFactory, btnRequestFactory, REQUEST_FACTORY, -1).execute();
			break;
		}
		case R.id.btnRequestFactoryParams: {
			new TaskTest(requestFactory, btnRequestFactoryParams, REQUEST_FACTORY, Integer.valueOf(nbParamRequestFactory.getText().toString())).execute();
			break;
		}
		case R.id.btnRest: {
			new TaskTest(restlet, btnRestlet, REST, -1).execute();
			break;
		}
		case R.id.btnRestParams: {
			new TaskTest(restlet, btnRestletParams, REST, Integer.valueOf(nbParamRestlet.getText().toString())).execute();
			break;
		}
		default:
			break;
		}
 
	}
 
	class TaskTest extends AsyncTask {
 
		private EditText text;
		private Button btn;
		private int type;
		private int nbParams;
		private IObjectA<? extends IObjectB> message;
		private long time;
 
		public TaskTest(EditText text, Button btn, int type, int nbParams) {
			this.text = text;
			this.btn = btn;
			this.type = type;
			this.nbParams = nbParams;
			btn.setEnabled(false);
			text.setText("contacting server");
		}
 
		@Override
		protected IObjectA<? extends IObjectB> doInBackground(Void... params) {
			// According to the type we do the correct call with appropriates methods and classes
                        time = System.currentTimeMillis();
			try {
				IObjectA<? extends IObjectB> result = null;
				switch (type) {
				case JSON_RPC: {
					// TODO
					break;
				}
				case REQUEST_FACTORY: {
					// TODO
					break;
				}
				case REST: {
					// TODO
					break;
				}
				default:
					break;
				}
 
				return result;
			} catch (Exception e) {
				Log.e(TAG, "Error during contacting server", e);
				return null;
			}
		}
 
		@Override
		protected void onPostExecute(IObjectA<? extends IObjectB> result) {
                        // Each time we manage the results with the same way.
			String typeName = null;
			switch (type) {
			case JSON_RPC:
				typeName = "Json Rpc";
				break;
			case REQUEST_FACTORY:
				typeName = "Request Fatory";
				break;
			case REST:
				typeName = "Restlet";
				break;
 
			default:
				break;
			}
			if (result != null) {
				StringBuilder str = new StringBuilder();
				str.append("Type : ").append(typeName).append("\n");
				str.append("Time : ").append(String.valueOf(System.currentTimeMillis() - time)).append("ms \n");
				str.append("Object A : ").append(result.getName()).append("\n");
				IObjectB objB = result.getObjectB();
				if (objB != null) {
					if (objB instanceof IObjectBMap) {
						str.append("Object B Map : ").append(objB.getName()).append("\n");
						if (((IObjectBMap) objB).getMap() != null) {
							for (Entry entry : ((IObjectBMap) objB).getMap().entrySet()) {
								str.append("-&gt; : Key").append(entry.getKey()).append(" : ").append(entry.getValue()).append("\n");
							}
						}
					} else {
						str.append("Object B: ").append(objB.getName()).append("\n");
					}
				}
				if ((result.getListObjectB() == null) || (result.getListObjectB().size() == 0)) {
					str.append("Nb objects B: ").append(0).append("\n");
				} else {
					str.append("Nb objects B: ").append(result.getListObjectB().size()).append("\n");
					for (IObjectB objBTmp : result.getListObjectB()) {
						str.append("--&gt; ").append(objBTmp.getName()).append("\n");
					}
				}
 
				text.setText(str.toString());
			} else {
				text.setText("Failure during getting result");
 
			}
			btn.setEnabled(true);
		}
 
	}
 
}

We have to create the tabs.xml in the layouts of our android app. Have a look at the source code

And we have to configure the AndroidManifest.xml with the activity and correct authorizations :

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.binomed.rpc"
    android:versionCode="1"
    android:versionName="1.0" >
 
    <uses-sdk android:minSdkVersion="4" />
    <uses-permission android:name="android.permission.INTERNET"/>
 
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".AndroidRpcProjectActivity" label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
 
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
 
</manifest>

We have to add the authorization “android.permission.INTERNET” in order to do the http calls with our server. Let’s now start to implement each solution

JSON  RPC

Required libraries

For Json RPC we need thoses libraries :

 

We have to add thoses libraries in the both projects. Right Click -> BuildPath -> Configure Build Path -> tab Libraries, button “add JARs…”


 

Server Side

Now let’s create all classes used for the data transfer.

The communication between the two entities for this framework is based on proxy class generated by the framework. So we have to define an interface to offer services to client. In order to the client could reuse this interface we have to create it in the shared folder on the server project.

package com.binomed.client.rpc.javajsonrpc;
 
public interface IJavaJsonRpcService {
 
	JavaJsonRpcObjectA getMessage() throws JsonParseException, JsonMappingException, IOException;
 
	JavaJsonRpcObjectA getMessageWithParameter(JavaJsonRpcObjectB parameter) throws JsonParseException, JsonMappingException, IOException;
 
}

This interface use 2 classes JavaJsonRpcObjectA and JavaJsonRpcObjectB which are classes that implement the previously seen interfaces. Those classes are serializable and are created on the shared folder on server side. The interface is mandatory. It is used also on the client side for the proxy.

package com.binomed.client.rpc.javajsonrpc.dto;
 
public class JavaJsonRpcObjectA implements IObjectA {
 
	public JavaJsonRpcObjectA() {
		super();
	}
 
	private JavaJsonRpcObjectB objectB;
 
	private List listObjectB;
 
	private String name;
 
	@Override
	public JavaJsonRpcObjectB getObjectB() {
		return objectB;
	}
 
	@Override
	public void setObjectB(JavaJsonRpcObjectB objectB) {
		this.objectB = objectB;
	}
 
	@Override
	public List getListObjectB() {
		return listObjectB;
	}
 
	@Override
	public void setListObjectB(List listObjectB) {
		this.listObjectB = listObjectB;
	}
 
	@Override
	public String getName() {
		return name;
	}
 
	@Override
	public void setName(String name) {
		this.name = name;
	}
 
}

and

package com.binomed.client.rpc.javajsonrpc.dto;
 
public class JavaJsonRpcObjectB implements IObjectBMap {
 
	public JavaJsonRpcObjectB() {
		super();
	}
 
	private String name;
	private int num;
 
	private HashMap map;
 
	@Override
	public String getName() {
		return name;
	}
 
	@Override
	public void setName(String name) {
		this.name = name;
	}
 
	@Override
	public HashMap getMap() {
		return map;
	}
 
	@Override
	public void setMap(HashMap map) {
		this.map = map;
	}
 
	@Override
	public int getNum() {
		return num;
	}
 
	@Override
	public void setNum(int num) {
		this.num = num;
 
	}
 
}

Normally at this moment you should have this package herachy :

 

 

 

We now have to create the service on server side and configure it :

We create an implementation of our service on the main src folder of server :

 

 

package com.binomed.server.rpc.javajsonrpc;
 
public class JavaJsonRpcService implements IJavaJsonRpcService {
 
	@Override
	public JavaJsonRpcObjectA getMessage() throws JsonParseException, JsonMappingException, IOException {
		JavaJsonRpcObjectB objB = new JavaJsonRpcObjectB();
		objB.setName("ObjectB");
		objB.setMap(new HashMap());
		objB.getMap().put("key", "value");
 
		JavaJsonRpcObjectA result = new JavaJsonRpcObjectA();
		result.setName("ObjectA");
		result.setListObjectB(new ArrayList());
		result.getListObjectB().add(objB);
		result.setObjectB(objB);
 
		return result;
	}
 
	@Override
	public JavaJsonRpcObjectA getMessageWithParameter(JavaJsonRpcObjectB parameter) throws JsonParseException, JsonMappingException, IOException {
		JavaJsonRpcObjectA result = new JavaJsonRpcObjectA();
		result.setName("WithParameter");
		result.setObjectB(parameter);
 
		if ((parameter != null) &amp;&amp; (parameter.getNum() &gt; 0)) {
			result.setListObjectB(new ArrayList());
			for (int i = 0; i &lt; parameter.getNum(); i++) {
				result.getListObjectB().add(parameter);
			}
		}
 
		return result;
	}
 
}

As you can see, the class isn’t a servlet, it is just a simple bean which implements the interface of our service. All the mapping is done in web.xml. Add this code in your web.xml :

 

<!-- Java-Json-RPC -->
 
	<servlet>
	<servlet-name>JsonRpcServlet</servlet-name>
		<servlet-class>cz.eman.jsonrpc.server.JsonRpcServlet</servlet-class>
		<init-param>
			<param-name>javajsonrpc</param-name>
			<param-value>com.binomed.server.rpc.javajsonrpc.JavaJsonRpcService</param-value>
		</init-param>
	</servlet>
	<servlet-mapping>
		<servlet-name>JsonRpcServlet</servlet-name>
		<url-pattern>/jsonrpc/*</url-pattern>
	</servlet-mapping>

As you can see, the framework has it’s own servlet which manage all redirections. We have only to declare all services that we expose.

That’s all for the server ! pretty easy isn’t it ? Let’s have a look at the client side now.

Client Side

As said previously, the framework Java JSON RPC use a proxy system. The Proxy class have to inherit from cz.eman.jsonrpc.client.AbstractClientProxy

 

package com.binomed.rpc.javajsonrpc;
 
public class JavaJsonRpcServiceProxy extends AbstractClientProxy implements IJavaJsonRpcService {
 
	public JavaJsonRpcServiceProxy(ClientProvider clientProvider) {
		super(IJavaJsonRpcService.class, clientProvider);
	}
 
	@Override
	public JavaJsonRpcObjectA getMessage() throws JsonParseException, JsonMappingException, IOException {
		return (JavaJsonRpcObjectA) super.callMethod("getMessage", new Object[] {});
	}
 
	@Override
	public JavaJsonRpcObjectA getMessageWithParameter(JavaJsonRpcObjectB parameter) throws JsonParseException, JsonMappingException, IOException {
		return (JavaJsonRpcObjectA) super.callMethod("getMessageWithParameter", new Object[] { parameter });
	}
 
}

You have to specify the name of server method to call and manage parameters like in java introspection. For example : for a method without parameters you have to passed an empty array.

To call the server, you just have to instanciate the proxy with an instance of cz.eman.jsonrpc.client.HttpJsonClient and called it. You have to integrate this code in AsyncTask code

 

case JSON_RPC: {
					JavaJsonRpcServiceProxy proxy = new JavaJsonRpcServiceProxy(new HttpJsonClient(new URL(LOCALHOST + "/jsonrpc/javajsonrpc")));
					if (nbParams == -1) {
						result = proxy.getMessage();
					} else {
						JavaJsonRpcObjectB paramB = new JavaJsonRpcObjectB();
						paramB.setName("Name From Json RPC");
						paramB.setMap(new HashMap());
						paramB.getMap().put("key", "value");
						paramB.setNum(nbParams);
						result = proxy.getMessageWithParameter(paramB);
 
					}
					break;
				}

As you can see the implementation is pretty simple.

Request Factory

All this part will be done by creating a project with appengine-android plugin and removing all unnecessary code and libraries.

Required libraries and configurations

For Request Factory we need the

The server have to be configure for GWT and AppEngine  : Rick Click ->Properties ->  Google -> AppEngine (Use SDK 1.6.1) and Rick Click ->Properties ->  Google -> Web Toolkit (Use 2.4)

If you want to use request factory you have to enable the gwt validation tools. This is mandatory for compilation and error resolution. Here is what you have to do for enabled this.

Validation Tools

On the both project you have to do this.

Rick Click -> Properties -> Java Compiler -> Annotation Processing

You have to have the same screen as this :

 

 

Don’t forget the key verbose, it is mandatory !

Now you have to referenced the factory path (path to jar that do the validation). On the same screen, click on “Factory Path” and choose the path to the jar “requestfactory-apt.jar” which is $YOUR_ECLIPSE_PATH/plugins/com.google.gwt.eclipse.sdkbundle_2.4.0.$NUMBER_OF_REVISION/gwt-2.4.0/requestfactory-apt.jar

Gwt configuration

As we are using the gwt plugin we have to delcare an entry point and an application :  Thoses classes are declared on the server side in the main src folder under com.binomed.server.requestfactory. See the source
 

Server Side

The request factory framework is based on interfaces. Those interfaces are the exposed part of server to the client. So all this code is in the shared folder. All the implentations are only on the server side (src part)

The main interface inherits from RequestFactory and list all services (other interfaces) offered by the server. Each service is an interface. Each manipulated bean is exposed as an interface.

package com.binomed.client.requestfactory;
 
public interface MyRequestFactory extends RequestFactory {
 
	@ServiceName("com.binomed.server.requestfactory.RequestFactoryObjectB")
	public interface HelloWorldRequest extends RequestContext {
		/**
		 * Retrieve a "Hello, World" message from the server.
		 */
		Request getMessage();
 
		InstanceRequest getMessageWithParameter();
 
	}
 
	HelloWorldRequest helloWorldRequest();
 
}

For this tutorial, our request factory only exposes one service HelloWorlRequest which has 2 methods.

As you can see there are annotations for specifying the implementation class of our service. The path describes the package and class name that implements the service.

package com.binomed.client.requestfactory.shared;
 
@ProxyForName("com.binomed.server.requestfactory.RequestFactoryObjectA")
public interface RequestFactoryObjectAProxy extends ValueProxy {
 
	RequestFactoryObjectBProxy getObjectB();
 
	void setObjectB(RequestFactoryObjectBProxy objectB);
 
	List getListObjectB();
 
	void setListObjectB(List listObjectB);
 
	String getName();
 
	void setName(String name);
 
}

and

package com.binomed.client.requestfactory.shared;
 
@ProxyForName("com.binomed.server.requestfactory.RequestFactoryObjectB")
public interface RequestFactoryObjectBProxy extends ValueProxy {
 
	String getName();
 
	void setName(String name);
 
	int getNum();
 
	void setNum(int num);
 
}

Those interfaces inherits from ValueProxy in order to the frameworks understand that it is a transportable bean. Those beans have some restrictions concerning their types : for example, you could not have the Map type. For more information, please have a look at this page. As for the service, we have to specify the implementation class of our beans.

Normally you should have this structure :

 

 

 

Now let’s have a look to the implementations of thoses interfaces.

First we will look at the beans :

package com.binomed.server.requestfactory;
 
public class RequestFactoryObjectA {
 
	public RequestFactoryObjectA() {
		super();
	}
 
	private RequestFactoryObjectB objectB;
 
	private List listObjectB;
 
	private String name;
 
	public RequestFactoryObjectB getObjectB() {
		return objectB;
	}
 
	public void setObjectB(RequestFactoryObjectB objectB) {
		this.objectB = objectB;
	}
 
	public List getListObjectB() {
		return listObjectB;
	}
 
	public void setListObjectB(List listObjectB) {
		this.listObjectB = listObjectB;
	}
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		this.name = name;
	}
 
}

and

package com.binomed.server.requestfactory;
 
public class RequestFactoryObjectB {
 
	public RequestFactoryObjectB() {
		super();
	}
 
	private String name;
	private int num;
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		this.name = name;
	}
 
	public int getNum() {
		return num;
	}
 
	public void setNum(int num) {
		this.num = num;
	}
 
	/*
	 * 
	 * Service Part
	 */
 
	public static RequestFactoryObjectA getMessage() {
 
		RequestFactoryObjectB objB = new RequestFactoryObjectB();
		objB.setName("ObjectB");
 
		RequestFactoryObjectA result = new RequestFactoryObjectA();
		result.setName("ObjectA");
		result.setListObjectB(new ArrayList<RequestFactoryObjectB>());
		result.getListObjectB().add(objB);
		result.setObjectB(objB);
 
		return result;
	}
 
	public RequestFactoryObjectA getMessageWithParameter() {
 
		RequestFactoryObjectA result = new RequestFactoryObjectA();
		result.setName("WithParameter");
 
		RequestFactoryObjectB objB = new RequestFactoryObjectB();
		objB.setName(name);
		result.setObjectB(objB);
 
		if (num > 0) {
			result.setListObjectB(new ArrayList<RequestFactoryObjectB>());
			for (int i = 0; i < num; i++) {
				result.getListObjectB().add(objB);
			}
		}
 
		return result;
	}
 
}

You have noticed that the implementation of service is included in the bean.

The implementation of interface is not mandatory because you have specified on interfaces what are the implementation classes. Nothing else is needed. But you have not

 

 

Finally we have to declare the servlet in web.xml :

<!-- Request Factory-->
	<servlet>
    <servlet-name>requestFactoryServlet</servlet-name>
	    <servlet-class>com.google.web.bindery.requestfactory.server.RequestFactoryServlet</servlet-class>
	    <init-param>
	      <param-name>symbolMapsDirectory</param-name>
	      <param-value>WEB-INF/classes/symbolMaps/</param-value>
	    </init-param>
	  </servlet>
 
	  <servlet-mapping>
	    <servlet-name>requestFactoryServlet</servlet-name>
	    <url-pattern>/gwtRequest</url-pattern>
	  </servlet-mapping>

If you’re code is not compiling, you maybe have to clean all your projects in order to refresh and be sure that the validation tools is well executed.

Client Side

The client side is pretty complex because you have to define utility classes for the communication.

  • Utils :  Communication configuration and preparing generic service.
  • AndroidRequestTransport : Class used for communication in requestFactory process
package com.binomed.rpc.requestFactory;
 
/**
 * Utility methods for getting the base URL for client-server communication and retrieving shared preferences.
 */
public class Util {
 
	/**
	 * Tag for logging.
	 */
	private static final String TAG = "Util";
 
	// Shared constants
 
	/**
	 * Value for {@link #CONNECTION_STATUS} key.
	 */
	public static final String CONNECTING = "connecting";
 
	/*
	 * URL suffix for the RequestFactory servlet.
	 */
	public static final String RF_METHOD = "/gwtRequest";
 
	public static final String LOCALHOST = "http://10.0.2.2:8888"; //$NON-NLS-1$
 
	/**
	 * Creates and returns an initialized {@link RequestFactory} of the given type.
	 */
	public static  T getRequestFactory(Context context, Class factoryClass) {
		T requestFactory = RequestFactorySource.create(factoryClass);
 
		String authCookie = null;
 
		String uriString = LOCALHOST + RF_METHOD;
		URI uri;
		try {
			uri = new URI(uriString);
		} catch (URISyntaxException e) {
			Log.w(TAG, "Bad URI: " + uriString, e);
			return null;
		}
		requestFactory.initialize(new SimpleEventBus(), new AndroidRequestTransport(uri, authCookie));
 
		return requestFactory;
	}
 
}

and

package com.binomed.rpc.requestFactory;
 
/**
 * An implementation of RequestTransport for use between an Android client and a
 * Google AppEngine server.
 */
public class AndroidRequestTransport implements RequestTransport {
 
    private final URI uri;
 
    private final String cookie;
 
    /**
     * Constructs an AndroidRequestTransport instance.
     *
     * @param uri the URI for the RequestFactory service
     * @param cookie the ACSID or SACSID cookie used for authentication
     */
    public AndroidRequestTransport(URI uri, String cookie) {
        this.uri = uri;
        this.cookie = cookie;
    }
 
    public void send(String payload, TransportReceiver receiver) {
        HttpClient client = new DefaultHttpClient();
        HttpPost post = new HttpPost();
        post.setHeader("Content-Type", "application/json;charset=UTF-8");
        post.setHeader("Cookie", cookie);
 
        post.setURI(uri);
        Throwable ex;
        try {
            post.setEntity(new StringEntity(payload, "UTF-8"));
            HttpResponse response = client.execute(post);
            if (200 == response.getStatusLine().getStatusCode()) {
                String contents = readStreamAsString(response.getEntity().getContent());
                receiver.onTransportSuccess(contents);
            } else {
                receiver.onTransportFailure(new ServerFailure(response.getStatusLine()
                        .getReasonPhrase()));
            }
            return;
        } catch (UnsupportedEncodingException e) {
            ex = e;
        } catch (ClientProtocolException e) {
            ex = e;
        } catch (IOException e) {
            ex = e;
        }
        receiver.onTransportFailure(new ServerFailure(ex.getMessage()));
    }
 
    /**
     * Reads an entire input stream as a String. Closes the input stream.
     */
    private String readStreamAsString(InputStream in) {
        try {
            ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
            byte[] buffer = new byte[1024];
            int count;
            do {
                count = in.read(buffer);
                if (count &gt; 0) {
                    out.write(buffer, 0, count);
                }
            } while (count &gt;= 0);
            return out.toString("UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("The JVM does not support the compiler's default encoding.",
                    e);
        } catch (IOException e) {
            return null;
        } finally {
            try {
                in.close();
            } catch (IOException ignored) {
            }
        }
    }
}

Now let’s have a look at the call of this service :

case REQUEST_FACTORY: {
					Thread.currentThread().setContextClassLoader(mContext.getClassLoader());
					MyRequestFactory requestFactory = Util.getRequestFactory(mContext, MyRequestFactory.class);
					final HelloWorldRequest request = requestFactory.helloWorldRequest();
					if (nbParams == -1) {
						request.getMessage().fire(new Receiver() {
							@Override
							public void onFailure(ServerFailure error) {
								Log.e(TAG, "Failure with request factory request : " + error.getMessage());
								message = null;
							}
 
							@Override
							public void onSuccess(RequestFactoryObjectAProxy result) {
								JavaJsonRpcObjectA objA = new JavaJsonRpcObjectA();
								objA.setName(result.getName());
								if (result.getObjectB() != null) {
									JavaJsonRpcObjectB objB = new JavaJsonRpcObjectB();
									objB.setName(result.getObjectB().getName());
								}
								if ((result.getListObjectB() != null) &amp;&amp; (result.getListObjectB().size() &gt; 0)) {
									JavaJsonRpcObjectB objB = null;
									objA.setListObjectB(new ArrayList());
									for (RequestFactoryObjectBProxy objBProxy : result.getListObjectB()) {
										objB = new JavaJsonRpcObjectB();
										objB.setName(objBProxy.getName());
										objA.getListObjectB().add(objB);
									}
 
								}
 
								// message = result;
								message = objA;
							}
						});
					} else {
						RequestFactoryObjectBProxy parameterB = request.create(RequestFactoryObjectBProxy.class);
						parameterB.setName("Name from request factory");
						parameterB.setNum(nbParams);
						request.getMessageWithParameter().using(parameterB).fire(new Receiver() {
							@Override
							public void onFailure(ServerFailure error) {
								Log.e(TAG, "Failure with request factory request : " + error.getMessage());
								message = null;
							}
 
							@Override
							public void onSuccess(RequestFactoryObjectAProxy result) {
								JavaJsonRpcObjectA objA = new JavaJsonRpcObjectA();
								objA.setName(result.getName());
								if (result.getObjectB() != null) {
									JavaJsonRpcObjectB objB = new JavaJsonRpcObjectB();
									objB.setName(result.getObjectB().getName());
								}
								if ((result.getListObjectB() != null) &amp;&amp; (result.getListObjectB().size() &gt; 0)) {
									JavaJsonRpcObjectB objB = null;
									objA.setListObjectB(new ArrayList());
									for (RequestFactoryObjectBProxy objBProxy : result.getListObjectB()) {
										objB = new JavaJsonRpcObjectB();
										objB.setName(objBProxy.getName());
										objA.getListObjectB().add(objB);
									}
 
								}
 
								// message = result;
								message = objA;
							}
						});
					}
					result = message;
					break;
				}

We have to use Reciever as for RPC mecanism in order to know when the server gives it’s response. The call of service is done by getting a proxy of service and by invocking the fire() method on it. Warning : The call of a fire method is not asynchrone.

You have to notice that normaly Request Factory is very usefull for CRUD and is oriented to be a JPA framework for server. This could explain why there are so many configuration classes.

Restlet

Required libraries and configurations

For Rest we need to download restlet-appEngine and restlet-android.

For the android side, we need the libraries available in previous zip file :

  • org.restlet.jar
  • org.restlet.ext.jackson.jar
  • org.restlet.ext.httpclient.jar
  • org.apache.commons.codec_1.5
  • org.apache.httpclient_4.1
  • org.apache.httpcore_4.1
  • org.apache.httpmime_4.1
  • org.apache.commons.logging_1.1
  • org.apache.james.mime4j_0.6
  • org.codehaus.jackson.core_1.9
  • org.codehaus.jackson.mapper_1.9

For the appEngine , we need the libraries available in previous zip file :

  • org.restlet.jar
  • org.restlet.ext.jackson
  • org.codehaus.jackson.core_1.9
  • org.codehaus.jackson.mapper_1.9
  • org.restlet.ext.json
  • org.json_2.0
  • org.restlet.ext.servlet

 

We need all those libraries on android side due to a bug with the http client, so we have to add the httpclient extension.

Server Side

On the server side we need to configure the Restlet resources and beans.

The resources will be interfaces in order to be shared by client side. The beans will be simple serializable POJO. Those classes are on the shared folder

package com.binomed.client.rest;
 
public interface IRestletService {
 
	@Get
	RestletObjectA getMessage() throws Exception;
 
}

and

package com.binomed.client.rest;
 
public interface IRestletServiceParam {
 
	@Post
	RestletObjectA getMessageWithParameter(RestletObjectB parameter) throws Exception;
 
}

And will have 2 beans

 

package com.binomed.client.rest.dto;
 
public class RestletObjectA implements IObjectA, Serializable {
 
	/**
	 *
	 */
	private static final long serialVersionUID = 1L;
 
	public RestletObjectA() {
		super();
	}
 
	private RestletObjectB objectB;
 
	private List listObjectB;
 
	private String name;
 
	public RestletObjectB getObjectB() {
		return objectB;
	}
 
	public void setObjectB(RestletObjectB objectB) {
		this.objectB = objectB;
	}
 
	public List getListObjectB() {
		return listObjectB;
	}
 
	public void setListObjectB(List listObjectB) {
		this.listObjectB = listObjectB;
	}
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		this.name = name;
	}
 
}
package com.binomed.client.rest.dto;
 
public class RestletObjectB implements IObjectBMap, Serializable {
 
	/**
	 *
	 */
	private static final long serialVersionUID = 1L;
 
	public RestletObjectB() {
		super();
	}
 
	private String name;
 
	private int num;
	private HashMap map;
 
	@Override
	public String getName() {
		return name;
	}
 
	@Override
	public void setName(String name) {
		this.name = name;
	}
 
	@Override
	public HashMap getMap() {
		return map;
	}
 
	@Override
	public void setMap(HashMap map) {
		this.map = map;
	}
 
	@Override
	public int getNum() {
		return num;
	}
 
	@Override
	public void setNum(int num) {
		this.num = num;
 
	}
 
}

 

 

 

Now let’s have a look at the implementation. With Restlet we need to declare a Rest Application. This application will list all resources in order to expose rest service on server. The application has to extends org.restlet.Application

 

package com.binomed.server.rest;
 
public class RestletApplication extends Application {
 
	/**
	 * Creates a root Restlet that will receive all incoming calls.
	 */
	@Override
	public Restlet createInboundRoot() {
		// Create a router Restlet that routes each call to a
		Router router = new Router(getContext());
 
		router.attach("/test", RestResource.class);
		router.attach("/testParam", RestResourceParam.class);
 
		return router;
	}
 
}

 

And we have to declare the resources. A resource always herits from org.restlet.resource.ServerResource

package com.binomed.server.rest;
 
public class RestResource extends ServerResource {
 
	@Get
	public RestletObjectA getMessage() throws Exception {
		RestletObjectB objB = new RestletObjectB();
		objB.setName("ObjectB");
		objB.setMap(new HashMap());
		objB.getMap().put("key", "value");
 
		RestletObjectA result = new RestletObjectA();
		result.setName("ObjectA");
		result.setListObjectB(new ArrayList());
		result.getListObjectB().add(objB);
		result.setObjectB(objB);
 
		return result;
	}
 
}

and

package com.binomed.server.rest;
 
public class RestResourceParam extends ServerResource {
 
	@Post
	public RestletObjectA getMessageWithParameter(RestletObjectB parameter) throws Exception {
		RestletObjectA result = new RestletObjectA();
		result.setName("WithParameter");
		result.setObjectB(parameter);
 
		if ((parameter != null) &amp;&amp; (parameter.getNum() &gt; 0)) {
			result.setListObjectB(new ArrayList());
			for (int i = 0; i &lt; parameter.getNum(); i++) {
				result.getListObjectB().add(parameter);
			}
		}
 
		return result;
	}
 
}

We now have to declare the application in the web.xml

<!-- RESTLET -->
 
	 <servlet>
            <servlet-name>RestletServlet</servlet-name>
            <servlet-class>org.restlet.ext.servlet.ServerServlet</servlet-class>
            <init-param>
                    <param-name>org.restlet.application</param-name>
                    <param-value>com.binomed.server.rest.RestletApplication</param-value>
            </init-param>
    </servlet>
 
	<servlet-mapping>
		<servlet-name>RestletServlet</servlet-name>
		<url-pattern>/rest/*</url-pattern>
	</servlet-mapping>

 

 

Thats all for the server side.

Client Side

The client side is very easy to implement. We only have to instantiate some org.restlet.resource.ClientResource and get a proxy generated by the framework corresponding to our service. In order to deport the code complexity, we will create a class for managing all those configurations.

package com.binomed.rpc.rest;
 
public class RestletAccesClass {
 
	private static ClientResource resource;
	private static ClientResource resourceWithParam;
	private static IRestletService service;
	private static IRestletServiceParam serviceWithParam;
 
	private static synchronized void init() {
		if (resource == null) {
			Engine.getInstance().getRegisteredConverters().add(new JacksonConverter());
			Engine.getInstance().getRegisteredClients().clear();
			Engine.getInstance().getRegisteredClients().add(new HttpClientHelper(null));
			resource = new ClientResource(AndroidRpcProjectActivity.LOCALHOST + "/rest/test");
			resourceWithParam = new ClientResource(AndroidRpcProjectActivity.LOCALHOST + "/rest/testParam");
			resourceWithParam.setRequestEntityBuffering(true);
			service = resource.wrap(IRestletService.class);
			serviceWithParam = resourceWithParam.wrap(IRestletServiceParam.class);
		}
	}
 
	public static RestletObjectA callService() throws Exception {
 
		init();
 
		RestletObjectA result = service.getMessage();
 
		return result;
	}
 
	public static RestletObjectA callServiceWithParam(int nbParams) throws Exception {
		init();
 
		RestletObjectB objB = new RestletObjectB();
		objB.setName("Name with Rest");
		objB.setNum(nbParams);
		objB.setMap(new HashMap());
		objB.getMap().put("key", "value");
 
		RestletObjectA result = serviceWithParam.getMessageWithParameter(objB);
 
		return result;
	}
 
}

 

those lines :

Engine.getInstance().getRegisteredConverters().add(new JacksonConverter());
Engine.getInstance().getRegisteredClients().clear();
Engine.getInstance().getRegisteredClients().add(new HttpClientHelper(null));

Are mandatory dues to a bug in rest android client library. We have to force those parameters. As you can see the call of a restlet service is done by calling the method of interface on the generated proxy.

Finally, the integration code in our main Activity :

case REST: {
					if (nbParams == -1) {
						result = RestletAccesClass.callService();
					} else {
						result = RestletAccesClass.callServiceWithParam(nbParams);
 
					}
					break;
				}

 

Tests and results

 

All the results shown after were done with this configuration :

  • Windows XP SP3
  • Intel Core Duo 2.2Ghz
  • 2Go ram
  • Eclipse Indigo with appEngine in local
  • Android Emulator under Froyo

Apk Size

Now let’s look at the impact of each solution on the final size of the APK

Without Any Framework Json Request Factory Rest
Size 36 Ko 1.82 Mo 420 Ko 3.38 Mo

 

Performant

Here is a comparative table for the average with different number of expected results.

Number of expected Results Json Request Factory Rest
1 177 ms 822 ms 632
10 169 ms and 17 ms / Obj 1167 ms and 117 ms / Obj 678 ms and 68 ms / Obj
100 377 ms and 3 ms / Obj 3300 ms and 33 ms / Obj 750 ms and 7 ms / Obj
1000 2644 ms and 3 ms / Obj 24831 ms and 24 ms / Obj 1266 ms and 1 ms / Obj
10000 Out of memory Exception Out of memory Exception 1167 ms and 117 ms / Obj

 

 

Conclusion

 

1:Bad -> 5:Perfect Json RPC Request Factory Rest
Simplicity 5 3 5
Use complex objects 4 3 4
Effective 3 1 5
Impact on APK size 3 4 1
Total 3.75 2.73 3.75

To conclude, you have to study the best way with your own use cases, because all those solutions could be chosen.
 
The request factory could be very useful if you want a solution with crud operations.
 
The rest solution could be useful if you already have a rest server and if you want to manipulate a hudge quantity of objects. As you can notice, the response time is very low for a high number of POJOs. The Restlet team also says that they are working on an android solution with weightless integration.
 
The Java Json Rpc be use for others cases.
 
All code of this tutorial is available on my github

Thank you to Benjamin who helps me for the redaction.

CineShowTime V3.0.0 for Android is available

28/11/2011

After months of work, CineShowTime V3.0.0 is now available !

  • A complete new UI
  • A tablet compatible UI !
  • Improve the UI on sevrals points
  • Add of chinese language

You can download it at : http://bit.ly/sUJ1ZZ

Binomed on Git

25/03/2011

Just a litle article in order to inform you that Binomed leaves SVN world for Git, all of our project will still be open source but now you will find our project on GitHub at this address : https://github.com/organizations/binomed.

CineShowTime for webOS fixed

09/08/2010

Some Palm users have contacted us about a loading issue on the CineShowTime for webOS app since the last update.

We have been able to spot the issue, and fix it. No update is necessary. However, a new update is in validation in order to consolidate the application.

Thanks for your feedback !

CineShowTime for webOS 1.2.1 out !

28/05/2010

CineShowTime for webOS is now available in version 1.2.1 !

CineShowTime 1.2.1

The newest version of CineShowTime is out, and includes the following changes :

  • Added a “What’s new” feature, displaying the changes within an update
  • Added a “display theme” feature
  • Corrected some little bugs

This version is available here.