7

I'm attempting to create a program in Android which communicates rapidly with a remote service (~40,000/sec), however all Android IPC seems to fall short of being able to accomplish this task. My first attempt involved a standard Messenger system which was unable to do more then ~2,000/second and equally bad was that it seemed punctuated with intermittent lag.

MainActivity (Test with Messengers)

public class MainActivity extends Activity implements ServiceConnection{

    Messenger mServiceMessenger;
    Messenger mClientMessenger = new Messenger(new ClientHandler());

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent(this,TestService.class);
        bindService(intent,this, Context.BIND_AUTO_CREATE);
    }


    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mServiceMessenger = new Messenger(service);
        Message m = Message.obtain();
        m.replyTo = mClientMessenger;
        try {
            mServiceMessenger.send(m);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {}

    public class ClientHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            Log.d("Spam","Message Received");
        }
    }
}

RemoteService (Test with Messengers)

public class TestService extends Service {

    private Messenger mServiceMessenger = new Messenger(new ServiceHandler());
    private Messenger mClientMessenger;
    private Random r = new Random();

    public TestService() {
        super();
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }


    @Override
    public IBinder onBind(Intent intent) {
        return mServiceMessenger.getBinder();
    }

    public void initSpam(){
        for(int i=0;i<10;i++) {
            TimerTask task = new TimerTask() {
                @Override
                public void run() {
                    Bundle b = new Bundle();
                    b.putInt("INT",r.nextInt());
                    b.putLong("LONG",r.nextLong());
                    b.putBoolean("BOOL",r.nextBoolean());
                    b.putFloat("FLOAT",r.nextFloat());
                    b.putDouble("DOUBLE",r.nextDouble());
                    b.putString("STRING",String.valueOf(r.nextInt()));
                    Message msg = Message.obtain();
                    msg.setData(b);

                    try {
                        mClientMessenger.send(msg);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            };
            Timer timer = new Timer();
            timer.scheduleAtFixedRate(task,1,1);
        }
    }

    public class ServiceHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            mClientMessenger = msg.replyTo;
            initBarrage();

        }
    }
}

The second attempt was done with AIDL. Although this also implements Binders for IPC, I assumed had significantly less overhead. However, AIDL proved to not be significantly more efficient then Messengers and it also did not solved the issue with stuttering.

MainActivity (Test with AIDL)

public class MainActivity extends Activity implements ServiceConnection{

    IRemoteService mService;
    TextView countTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent(this,TestService.class);
        bindService(intent,this, Context.BIND_AUTO_CREATE);
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {

        mService = IRemoteService.Stub.asInterface(service);
        try {
            mService.registerCallback(mClientBinder);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {}


    public final IServiceAidlCallback.Stub mClientBinder = new IServiceAidlCallback.Stub(){
        public void basicTypes(int anInt, long aLong, boolean aBoolean,
                               float aFloat, double aDouble, String aString){
            Log.d("Spam","Callback Received");
        }
    };
}

RemoteService (Test with AIDL)

public class TestService extends Service {

    private Random r = new Random();

    private IServiceAidlCallback mClientCallback;

    public TestService() {
        super();
    }

    @Override
    public void onRebind(Intent intent) {
        super.onRebind(intent);
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    public final IRemoteService.Stub mBinder = new IRemoteService.Stub(){
        public void registerCallback(IBinder callback){

            mClientCallback = IServiceAidlCallback.Stub.asInterface(callback);
            initSpam();

        }
    };

    public void initSpam(){
        for(int i=0;i<10;i++) {
            TimerTask task = new TimerTask() {
                @Override
                public void run() {
                    try {
                        mClientCallback.basicTypes(
                                r.nextInt(),
                                r.nextLong(),
                                r.nextBoolean(),
                                r.nextFloat(),
                                r.nextDouble(),
                                String.valueOf(r.nextInt()));
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            };
            Timer timer = new Timer();
            timer.scheduleAtFixedRate(task,1,1);
        }
    }
}

Am I doing something wrong in either of these cases which would prevent me from getting above ~5,000/second? or is there another system for Android IPC that I was not aware of?

Onik
  • 19,396
  • 14
  • 68
  • 91
Beryllium
  • 556
  • 1
  • 7
  • 20
  • 3
    You're not going to be able to do anything sustained at ~40,000 times per second without stuttering, let alone across processes, on off-the-shelf hardware with a manufacturer-supplied ROM, from the Android SDK. SDK apps are not in control over thread and process scheduling. – CommonsWare Nov 11 '15 at 18:02
  • 1
    you could consider raw `Binder` protocol (`transact` / `onTransact`) which should be faster than AIDL but not much, also sockets or `MemoryFile` maybe (i never used it actually so i'm not sure how fast it can be)? – pskink Nov 11 '15 at 18:06
  • @CommonsWare I'm able to get ~40,000 across a local network with telnet to my remote service which is inherently IPC, is there nothing on Android which can match that? – Beryllium Nov 11 '15 at 18:14
  • @Beryllium: `telnet` is not an Android SDK app. `telnet` will suffer from the same basic problems, in that the OS thread and process scheduler is not guaranteed to give you adequate time to accomplish your processing aims. You're certainly welcome, as pskink suggests, to use a socket, which will be a direct analogue to using `telnet`. You're also welcome to confirm that `Timer` is going to let you get to ~40,000 events/second, regardless of IPC. I'm just warning you that you're likely to be disappointed in the end. – CommonsWare Nov 11 '15 at 18:21
  • 1
    using raw `Binder`s (and `FLAG_ONEWAY`) i was able to send 100.000 integers in 120- 200 ms, of course not using 100000 transactions but by packing 100 ints in a row per transaction, thus making 1000 transactions, run on the emu – pskink Nov 11 '15 at 20:04
  • @pskink I'm still testing, but at the moment it seems like the issue with `Binder`s is the limited number of transactions/sec. Unfortunately in my case data cannot be bundled it needs to be sent real time. `Sockets` however seem to be faster by a little over an order of magnitude, although I haven't tested marshalling or unmarshalling objects in conjunction with that. – Beryllium Nov 11 '15 at 22:40
  • 200 ms / 1000 == 0.2 ms per transaction, that resolution is not enough? – pskink Nov 11 '15 at 22:44
  • @pskink The goal for me is 40k/sec which is about 0.025ms per transaction. `Socket`s seem to approach that number so I'll likely settle for one of those – Beryllium Nov 11 '15 at 22:50
  • @pskink Doesn't quite hit how fast I wanted to get, but it does seem to be the fastest I've seen so far. If you want to make an answer I'll mark it because I'm fairly certain there's a hardware limitation for most mobile devices and this hits against it. Thank you for your dedication as well :). – Beryllium Nov 13 '15 at 02:41
  • btw how did you pass the returned pipe across processes? – pskink Nov 14 '15 at 10:23
  • For testing I'm simply using `Messenger`s, but any `Binder` class should be fairly good at passing `Parcel`s. – Beryllium Nov 14 '15 at 19:59

1 Answers1

4

do something like that:

MainActivity

// use it for writing: stream.write(byte[]) 
// (make sure to write as biggest data chunks as possible)
// or wrap it around some other streams like DataOutputStream
private OutputStream stream;

// ServiceConnection implementation
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
    try {
        ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
        stream = new ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]);

        Parcel data = Parcel.obtain();
        FileDescriptor readFileDescriptor = pipe[0].getFileDescriptor();
        data.writeFileDescriptor(readFileDescriptor);
        service.transact(IBinder.FIRST_CALL_TRANSACTION, data, null, 0);
    } catch (Exception e) {
        e.printStackTrace();
    }
    Log.d(TAG, "onServiceConnected " + stream);
}

RemoteService

@Override
public IBinder onBind(Intent intent) {
    Log.d(TAG, "onBind ");
    return binder;
}

IBinder binder = new Binder() {
    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        ParcelFileDescriptor pfd = data.readFileDescriptor();
        final InputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
        // do something with a 'stream', start a new Thread for example and read data in a loop
        ...
        ...
        return true;
    }
};
pskink
  • 23,874
  • 6
  • 66
  • 77
  • it is me again talking to you from your link. In terms of your transact call in onServiceConnected and onTransact override, I did pretty much the same thing. Except you used null for reply and 0 for flags. In which I used a Parcel for reply. I will try those params and see. In terms of createpipe, thanks I never knew that. That can be helpful to do interprocess communication. Will let you know if I get through. –  Mar 19 '20 at 07:39
  • @JaveneCPPMcGowan `reply` is optional (only `data` is required - even if you dont use it at all), what `flags` did you use? – pskink Mar 19 '20 at 07:42
  • I use 0 for flags. Also I still got no luck. I posted my sample code as an answer here. Kindly read it and I will delete it later. –  Mar 19 '20 at 09:58
  • I hope my sample code helped you to understand the level of my problem. –  Mar 19 '20 at 20:15
  • 1
    @JaveneCPPMcGowan you are starting an activity from another one, in that case you need `startActivityForResult`, not a `Binder` – pskink Mar 20 '20 at 04:42
  • Thanks. That solves my number one problem I dont know how I forgot that. Maybe because I was too focused on my secondary problem; getting my binder.transact method to work. At this point all that is puzzling me is why cant I get transact to call my onTransact. I am searching all over the internet, but no tutorials on how to do it. Everyone talks about AIDL and no even AIDL is working. And what is AIDL really? Is it code, a driver,..? Also I am unable to send a pipe via intent. –  Mar 20 '20 at 05:30
  • 1
    @JaveneCPPMcGowan you have the working code above - just use "bound" service that returns a `Binder` from `onBind` method – pskink Mar 20 '20 at 06:00
  • I want to thank you extremely extremely extremely much. You really tried your best to help me out and I got transact working. But only in bounded services. It seems android is doing its usual restrictions and binders cannot work between activities. However Messenger api can send messages between activities. Either there is a special binder driver for Messenger or it is secretly using a service. Anyhow, I am now satisfied and Activity for results solves my first problem –  Mar 20 '20 at 11:32
  • @JaveneCPPMcGowan *"Either there is a special binder driver for Messenger or it is secretly using a service."* - no, you have to use `Intent.putExtra(String name, Bundle value)` and `Bundle` has means for storing `Binder`s – pskink Mar 20 '20 at 11:45
  • I mean that you can use Messenger to communicate between two activities in different processes. –  Mar 20 '20 at 12:11
  • @JaveneCPPMcGowan use `Intent.putExtra(String name, Bundle value)` then – pskink Mar 20 '20 at 12:12