Poor man’s RMI for Android

| October 11, 2012 | 8 Replies

I had to create a bridge between an Android application and a Java application which uses RMI as a service interface. Its well known that Android does not support the RMI part of Java and a web service is NOT an option in this particular case.
I did some research and it turns out there are several ways to cope with this situation. I need something very simple and I couldn’t find it online so I decided to do it myself. Five small classes later I had my poor man’s RMI for Android. The client is ~100 lines and server ~180 and it does the job alright.

You should take into account that this approach only works if you are able to make changes to the server application which in my case is possible. If a better approach is feasible for you (REST, Web service etc.) please remember that you should avoid self written solutions (like this one) and use other, preferably standardized, solutions.

I wont go into code details. I’ll just show an example usage and comment on key implementation parts. You can download it try it out for yourself.

1. Create your service class and interface. This is my example service class. It implements the service interface ServiceExample (see below).

// very straight forward
public class ServiceExampleImpl implements ServiceExample {
	@Override
	public String concat(String... args) {
		if (args == null || args.length == 0) {
			return "";
		}
		String concat = "";
		for (String arg : args) {
			concat += arg;
		}
		return concat;
	}
}
public interface ServiceExample {
	// concatenate the arguments
	public String concat(String... args);
}

2. Create the server and register your service.

	// create the RMI server
	RpcServer rpcServer = new RpcServer();
	// register a service under the name example
	// the service has to implement an interface for the magic to work
	rpcServer.registerService("example", new ServiceExampleImpl());
	// start the server at port 6789
	rpcServer.start(6789);

3. Create a client and use the service

	// lookup the service with name example and interface ServiceExample located at host localhost and port 6789
	ServiceExample example = RpcClient.lookupService("localhost", 6789, "example", ServiceExample.class);
	// call the method concat and display the result
	System.out.println(example.concat("foo", " ", "bar", " ", "baz"));

So that wraps up the usage.
The implementation can be summarized with the two code snippets below. I rely heavily on ObjectInput/OutputStream and Java Serialization. I use Java Proxies (thank god Android supports this Java feature) to provide a more convenient way to use it on the client. Here is a part of my InvocationHandler interface:

	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		try {
			RpcRequest rpcRequest = new RpcRequest();

			rpcRequest.setServiceName(serviceName);
			String interfaceCName = interfaceClass.getCanonicalName();
			rpcRequest.setInterfaceCName(interfaceCName);

			String methodName = method.getName();
			rpcRequest.setMethodName(methodName);
			Class<?>[] argTypes = method.getParameterTypes();
			rpcRequest.setArgTypes(argTypes);
			rpcRequest.setArgs(args);

			Socket clientSocket = new Socket(host, port);
			writeRequestObject(rpcRequest, clientSocket);
			RpcResponse rpcResponse = readResponseObject(clientSocket);

			if (!rpcResponse.isSuccessfull()) {
				throw new RpcException(rpcResponse.getException());
			}

			Object returnValue = rpcResponse.getReturnValue();
			return returnValue;
		} catch (Exception e) {
			throw new RpcException(e);
		}
	}

I use two small DTO classes to transfer the call details over an ObjectOutputStream and read the return value of the call from an ObjectInputStream. RpcRequest has all the details needed for the finding of the service method on the service. RpcResponse has the result of the method and/or exception it had failed.
Once on the server the RpcRequest is handled by this method:

	private RpcResponse handleMethodCall(RpcRequest remoteRequest) {

		String serviceName = checkServiceName(remoteRequest);

		Object serviceImpl = serviceNameToImpl.get(serviceName);
		Class<?> serviceImplClass = serviceImpl.getClass();

		checkServiceInterface(remoteRequest, serviceImplClass);

		String methodName = remoteRequest.getMethodName();
		Class<?>[] argTypes = remoteRequest.getArgTypes();

		Method method = null;
		try {
			method = serviceImplClass.getMethod(methodName, argTypes);
		} catch (Exception e) {
			throw new RpcException(e);
		}

		Object[] args = remoteRequest.getArgs();
		Object returnValue = null;
		Exception exception = null;
		try {
			if (!method.isAccessible()) {
				method.setAccessible(true);
			}
			returnValue = method.invoke(serviceImpl, args);
		} catch (Exception e) {
			exception = e;
		}

		RpcResponse rpcResponse = new RpcResponse();

		if (exception != null) {
			rpcResponse.setException(exception);
		} else {
			rpcResponse.setReturnValue(returnValue);
		}
		return rpcResponse;
	}

As you see its a very simple idea. Hope it helps.
Download source

Tags: , , ,

Category: Development

About the Author ()

Comments (8)

Trackback URL | Comments RSS Feed

  1. elad says:

    good article – thanks!
    when trying to use the client on a android project i’m having this error in the server:
    xception in thread “Thread-15″ rpc.RpcException: rpc.RpcException: java.lang.ClassNotFoundException: com.marakana.ServiceExample
    at rpc.RpcServer.tryReadWriteObjects(RpcServer.java:95)
    at rpc.RpcServer.access$0(RpcServer.java:89)
    at rpc.RpcServer$RpcHandler.run(RpcServer.java:85)
    at java.lang.Thread.run(Thread.java:722)
    do you have any idea why?

    • h.indzhov says:

      Hey, could you post more details on how exactly are you using it ?
      Did you copy the source directly into the project structure ?
      Please check if the RpcException class is in the package rpc or if u have unresolved imports.

  2. Franklin says:

    Hi… first I want to say you Thanks for your tool
    But I have tried and I have some problems

    I have my service on a eclipse project and my client on Android Studio project

    I have this lines of codes for invoke to servicer (eclipse project) over Android Studio Project

    CODE:
    // lookup the service with name “example”, interface ServiceExample located at host localhost and port 6789
    ItfMovimiento movimiento = RpcClient.lookupService(“localhost”, 6789, “movimiento”, ItfMovimiento.class);

    where ItfMovimiento is my interface of service implemented on eclipse project over my same laptop (for this reason i use localhost)

    and when i call this method i get this error
    method call: movimiento.abajo(); (one of my methods implemented on eclipse project)

    03-04 11:54:05.113 2276-2276/com.example.franklin.pasapapel E/AndroidRuntime﹕ FATAL EXCEPTION: main
    Process: com.example.franklin.pasapapel, PID: 2276
    rpc.RpcException: android.os.NetworkOnMainThreadException
    at rpc.RpcClient.invoke(RpcClient.java:53)
    at java.lang.reflect.Proxy.invoke(Proxy.java:397)
    at $Proxy0.arriba(Unknown Source)
    at com.example.franklin.pasapapel.MainActivity$1.onClick(MainActivity.java:82)
    at android.view.View.performClick(View.java:4756)
    at android.view.View$PerformClick.run(View.java:19749)
    at android.os.Handler.handleCallback(Handler.java:739)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:135)
    at android.app.ActivityThread.main(ActivityThread.java:5221)
    at java.lang.reflect.Method.invoke(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:372)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)
    Caused by: android.os.NetworkOnMainThreadException
    at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1147)
    at java.net.InetAddress.lookupHostByName(InetAddress.java:418)
    at java.net.InetAddress.getAllByNameImpl(InetAddress.java:252)
    at java.net.InetAddress.getAllByName(InetAddress.java:215)
    at java.net.Socket.tryAllAddresses(Socket.java:109)
    at java.net.Socket.(Socket.java:178)
    at java.net.Socket.(Socket.java:150)
    at rpc.RpcClient.invoke(RpcClient.java:42)
                at java.lang.reflect.Proxy.invoke(Proxy.java:397)
                at $Proxy0.arriba(Unknown Source)
                at com.example.franklin.pasapapel.MainActivity$1.onClick(MainActivity.java:82)
                at android.view.View.performClick(View.java:4756)
                at android.view.View$PerformClick.run(View.java:19749)
                at android.os.Handler.handleCallback(Handler.java:739)
                at android.os.Handler.dispatchMessage(Handler.java:95)
                at android.os.Looper.loop(Looper.java:135)
                at android.app.ActivityThread.main(ActivityThread.java:5221)
                at java.lang.reflect.Method.invoke(Native Method)
                at java.lang.reflect.Method.invoke(Method.java:372)
                at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
                at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)

    please help me or send me your email for sending my projects

  3. Franklin says:

    I have corrected using AsyncTask (http://android-developers.blogspot.in/2009/05/painless-threading.html)

    But now the problem and the mistake is:

    03-04 18:09:27.955 2416-2438/com.example.franklin.pasapapel E/AndroidRuntime﹕ FATAL EXCEPTION: AsyncTask #1
    Process: com.example.franklin.pasapapel, PID: 2416
    java.lang.RuntimeException: An error occured while executing doInBackground()
    at android.os.AsyncTask$3.done(AsyncTask.java:300)
    at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:355)
    at java.util.concurrent.FutureTask.setException(FutureTask.java:222)
    at java.util.concurrent.FutureTask.run(FutureTask.java:242)
    at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
    at java.lang.Thread.run(Thread.java:818)
    Caused by: rpc.RpcException: java.net.ConnectException: failed to connect to localhost/127.0.0.1 (port 6789): connect failed: ECONNREFUSED (Connection refused)
    at rpc.RpcClient.invoke(RpcClient.java:53)
    at java.lang.reflect.Proxy.invoke(Proxy.java:397)
    at $Proxy0.arriba(Unknown Source)
    at com.example.franklin.pasapapel.MainActivity$arriba.doInBackground(MainActivity.java:80)
    at android.os.AsyncTask$2.call(AsyncTask.java:288)
    at java.util.concurrent.FutureTask.run(FutureTask.java:237)
                at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
                at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
                at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
                at java.lang.Thread.run(Thread.java:818)
    Caused by: java.net.ConnectException: failed to connect to localhost/127.0.0.1 (port 6789): connect failed: ECONNREFUSED (Connection refused)
    at libcore.io.IoBridge.connect(IoBridge.java:124)
    at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:183)
    at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:163)
    at java.net.Socket.startupSocket(Socket.java:590)
    at java.net.Socket.tryAllAddresses(Socket.java:128)
    at java.net.Socket.(Socket.java:178)
    at java.net.Socket.(Socket.java:150)
    at rpc.RpcClient.invoke(RpcClient.java:42)
                at java.lang.reflect.Proxy.invoke(Proxy.java:397)
                at $Proxy0.arriba(Unknown Source)
                at com.example.franklin.pasapapel.MainActivity$arriba.doInBackground(MainActivity.java:80)
                at android.os.AsyncTask$2.call(AsyncTask.java:288)
                at java.util.concurrent.FutureTask.run(FutureTask.java:237)
                at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
                at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
                at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
                at java.lang.Thread.run(Thread.java:818)
    Caused by: android.system.ErrnoException: connect failed: ECONNREFUSED (Connection refused)
    at libcore.io.Posix.connect(Native Method)
    at libcore.io.BlockGuardOs.connect(BlockGuardOs.java:111)
    at libcore.io.IoBridge.connectErrno(IoBridge.java:137)
    at libcore.io.IoBridge.connect(IoBridge.java:122)
                at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:183)
                at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:163)
                at java.net.Socket.startupSocket(Socket.java:590)
                at java.net.Socket.tryAllAddresses(Socket.java:128)
                at java.net.Socket.(Socket.java:178)
                at java.net.Socket.(Socket.java:150)
                at rpc.RpcClient.invoke(RpcClient.java:42)
                at java.lang.reflect.Proxy.invoke(Proxy.java:397)
                at $Proxy0.arriba(Unknown Source)
                at com.example.franklin.pasapapel.MainActivity$arriba.doInBackground(MainActivity.java:80)
                at android.os.AsyncTask$2.call(AsyncTask.java:288)
                at java.util.concurrent.FutureTask.run(FutureTask.java:237)
                at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
                at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
                at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
                at java.lang.Thread.run(Thread.java:818)

    Help me please

Leave a Reply


five + = 13