Search This Blog

18 February 2012

AIDL Step 7: Using Callbacks

What if we would like the service to callback into the client?

Rather than repeating all the previous tutorials, let's just alter the v2 API. Note: In the real world, you should never do that as you need to remain backward compatible. Since I know no one else is using the api, we'll cheat.

First, let's create API/src/main/java/org/eoti/android/test/apkextensions/api/v2/IServerCallback.aidl:
package org.eoti.android.test.apkextensions.api.v2;

oneway interface IServerCallback {
    void message(String text);
}


And update our IServer.aidl:
package org.eoti.android.test.apkextensions.api.v2;

import org.eoti.android.test.apkextensions.api.v2.Registration;
import org.eoti.android.test.apkextensions.api.v2.IServerCallback;

interface IServer
{
    void register(inout Registration registration, IServerCallback cb);
    void unregister(in String registrationName, IServerCallback cb);
    String getServerIdentifier();
}


Recompile our API
malachi@onyx:~/work/apkextensions/API$ mvn clean install


Next, we update our server.  Replace createV2Binder() with:
    final RemoteCallbackList<IServerCallback> callbacks = new RemoteCallbackList<IServerCallback>();

    protected void broadcast(String message)
    {
        final int N = callbacks.beginBroadcast();
        for(int i=0; i<N; i++)
        {
            try {
                callbacks.getBroadcastItem(i).message(message);
            } catch (RemoteException e) {
                // RemoteCallbackList will take care of removing dead objects
            }
        }
        callbacks.finishBroadcast();
    }
   
    private IBinder createV2Binder()
    {
        return new IServer.Stub(){
            @Override
            public void register(Registration registration, IServerCallback cb) throws RemoteException {
                if(registration == null) throw new RemoteException(); // API15 required for 'new RemoteException(string)'
                registrations.put(registration.getName(), registration);
                if(cb != null)
                {
                    callbacks.register(cb);
                    cb.message("Registration successful");
                }
                Log.d(TAG, "Registration received:  " + registration.getName() + " v" + registration.getVersion());
                broadcast(registration.getName() + " has entered the room");
            }

            @Override
            public void unregister(String registrationName, IServerCallback cb) throws RemoteException {
                if(registrationName == null)  throw new RemoteException(); // API15 required for 'new RemoteException(string)'
                registrations.remove(registrationName);
                if(cb != null)
                {
                    cb.message("Unregistration successful");
                    callbacks.unregister(cb);
                }
                Log.d(TAG, "Registration removed: " + registrationName);
                broadcast(registrationName + " has left the room");
            }

            @Override
            public String getServerIdentifier() throws RemoteException {
                return TheServer.class.getName() + "#" + TheServer.this.hashCode();
            }
        };
    }

And compile it
malachi@onyx:~/work/apkextensions/TheServer$ mvn clean install


Next, we'll update our v2 client...
In our Client2/src/main/java/org/eoti/android/test/apkextensions/client2/Client2Activity.java, add:
    private IServerCallback callback = new IServerCallback.Stub() {
        @Override
        public void message(String text) throws RemoteException {
            Log.d(TAG, "Message received: " + text);
        }
    };

change:
server.register(registration);
to:
server.register(registration, callback);

and change:
server.unregister(REG_NAME);
to:
server.unregister(REG_NAME, callback);

Rebuild it
malachi@onyx:~/work/apkextensions/Client2$ mvn clean install

Run Client1:
I/ActivityManager(   61): Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=org.eoti.android.test.apkextensions.client1/.Client1Activity } from pid 127
I/ActivityManager(   61): Start proc org.eoti.android.test.apkextensions.server for service org.eoti.android.test.apkextensions.server/.TheServer: pid=846 uid=10045 gids={1015}
E/Client1 (  546): Server bound
I/ActivityManager(   61): Displayed org.eoti.android.test.apkextensions.client1/.Client1Activity: +726ms
D/Client1 (  546): Server connected
D/TheServer(  846): Registration received from deprecated client:  org.eoti.android.test.apkextensions.client1.Client1Activity
D/Client1 (  546): Registered org.eoti.android.test.apkextensions.client1.Client1Activity
D/Client1 (  546): Running tests...
D/Client1 (  546): Tests done...
D/TheServer(  846): Registration removed from deprecated client: org.eoti.android.test.apkextensions.client1.Client1Activity
E/Client1 (  546): Server unbound

Run Client2:
W/KeyCharacterMap(  546): No keyboard for id 0
W/KeyCharacterMap(  546): Using default keymap: /system/usr/keychars/qwerty.kcm.bin
I/ActivityManager(   61): Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=org.eoti.android.test.apkextensions.client2/.Client2Activity } from pid 127
I/ActivityManager(   61): Start proc org.eoti.android.test.apkextensions.client2 for activity org.eoti.android.test.apkextensions.client2/.Client2Activity: pid=856 uid=10047 gids={1015}
E/Client2 (  856): Server bound
D/Client2 (  856): Server connected: org.eoti.android.test.apkextensions.server.TheServer#1079096160
D/Client2 (  856): Message received: Registration successful
D/TheServer(  846): Registration received:  org.eoti.android.test.apkextensions.client2.Client2Activity v1.0alpha
D/Client2 (  856): Message received: org.eoti.android.test.apkextensions.client2.Client2Activity has entered the room
D/Client2 (  856): Registered org.eoti.android.test.apkextensions.client2.Client2Activity
D/Client2 (  856): Running tests...
D/Client2 (  856): Tests done...
D/TheServer(  846): Registration removed: org.eoti.android.test.apkextensions.client2.Client2Activity
D/Client2 (  856): Message received: Unregistration successful
E/Client2 (  856): Server unbound
I/ActivityManager(   61): Displayed org.eoti.android.test.apkextensions.client2/.Client2Activity: +931ms


This all looks good -- but why didn't Client1 announce when Client2 entered the room? 
It's because Client1 is still using the old deprecated API.

1 comment: