Poor man’s RMI for Android
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
Category: Development
About the Author (Author Profile)
Comments (8)
Trackback URL | Comments RSS Feed
Sites That Link to this Post
- Android Remote Procedure Call | Developers Questions - Msn4Free.com | December 18, 2013
- Android Remote Procedure Call [closed] | Zipf Answers | December 7, 2014
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?
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.
try this: https://github.com/jorgenpt/lipermi
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
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
Hi,
could you show me how you use it. Just copy some code here so I can understand the problem.