Search code examples
androidperformanceandroid-serviceipcandroid-binder

Rapid IPC with Messengers or AIDL


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?


Solution

  • 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;
        }
    };