How to properly upgrade AIDL interfaces in Android

2019-06-11 14:55发布

问题:

I have two apps: One is called my-app.apk, the other my-service.apk. The service app just defines a single Android Service, which can be bound by the primary app to execute some methods. This is done using Androids AIDL interface, and it works great - so far.

Now I want to change the interface of the service, and I am wondering what I have to watch out for. I changed the file IRemote.aidl of my-service.apk to the following:

package com.example.myservice;
interface IRemote {
  void printHello();
  void print(int i);
}

And just out of curiosity I changed the IRemote.aidl of my-app.apk to the following (note the differences!):

package com.example.myservice;
interface IRemote {
  void printHello();
  void printYeahThisHasADifferentNameAndParam(String s);
}

Now I got a completely unexpected result: The call to

printYeahThisHasADifferentNameAndParam("hello world");

from my application resulted in the log output "11". Why??

  1. I would not have expected a SecurityException at the bindService() call, although this would be adequate in a situation with completely different interfaces.
  2. What I would have expected would have been a RemoteException when executing the call, telling me that the method is not available.
  3. What I didn't expect entirely was that it will just call a different method with different data as parameters. Although I can understand it from a low-level point of view. Maybe they did it to ensure this interface is performant...

So here are my questions:

  1. What is the best upgrade-strategy? Simply do not delete/alter old methods and order?
  2. I noticed when my-service.apk is upgraded (re-installed) the service gets lost for my-app.apk. Normally the service gets re-scheduled by the system, which it typically does on crashes. How do I ensure my-app.apk aquires the service again? Watch out for newly installed packages?

Thank you in advance for your help! :-)

Cheers, Marc

回答1:

If you take a look at the code generated by the AIDL compiler, you will see that RPC via Binder calls methods by sequential number. Every method in the interface gets number assigned, like:

       SIZE = ::android::IBinder::FIRST_CALL_TRANSACTION + 0,
       SETSIZE = ::android::IBinder::FIRST_CALL_TRANSACTION + 1,
       READ = ::android::IBinder::FIRST_CALL_TRANSACTION + 2,
       WRITE = ::android::IBinder::FIRST_CALL_TRANSACTION + 3,
       SYNC = ::android::IBinder::FIRST_CALL_TRANSACTION + 4,

This number is then used by the calling side to map called method to flat Parcel buffer and by receiving side of IPC to select the generated method to unmarshall the serialized parameters data and finally call the real implementation.

Thus if you replace the method number 1 definition on the calling side, but still have old implementation on the receiving side, you will call the old implementation with completely bogus data. There is no type information in the serialized Parcel data of method arguments (besides the method number itself), so it will happily deserialize new method call parameters buffer as old ones and try to call implementation.