We expose an AIDL service to third party developers. We'd like to return parcelable objects from this service, but we have concerns about backwards compatibility. By this, I mean clients compiled against version N of the parcelable, and a service compiled against version N+1 must work together.

From my tests, for simple flat objects (simple type fields only), backwards compatibility is possible, as long as new fields are parceled at the end of stream ... e.g.,

in.writeInt(field1);
in.writeInt(field2); // new field

however, when it comes to complex objects, things blow up. for example,

class D implements Parcelable {
  int field1;
}

class C implements Parcelable {
  List<D> ds;
}

if a second field is added to class D,

class D implements Parcelable {
  int field1;
  int field2; // new field
}

unmarshalling of class Cs fail (i tried using Parcel.writeList() and Parcel.writeParcelableArray()).

This seems almost inconceivable that this case can't be handled. Sure, we can leave the old binder interface as-is, and create a new binder interface that returns objects of the new class with the additional fields, but for a service that returns oft-changing classes, this will result in a convoluted mess. For example, interface 1.0 returns a Person. now we add a field, and we have a new binder interface 2.0 that returns Person2 objects, and so on.

This leaves us to using some more forgiving format, like JSON, to pass IPC data.

Any suggestions?

有帮助吗?

解决方案

Clients compiled against version N of the Parcelable and the AIDL interface will need to be supported by the Service until the heat death of the universe, not just until N+1, unless you want to break a bunch of clients or are in position to force those developers to update their apps.

Sure, we can leave the old binder interface as-is

Any change to the AIDL itself means that you need a new service endpoint for the new protocol version, let alone changes to the Parcelable definitions.

for example, interface 1.0 returns a Person. now we add a field, and we have a new binder interface 2.0 that returns Person2 objects, and so on.

Either:

  • Get it right the first time, so that you do not have an "oft-changing" public API, or

  • Use a Bundle for the "oft-changing" aspects, since Bundle is stable (e.g., a Person has a properties Bundle for stuff you wish to glom onto your public API in between every-few-years major API revisions), or

  • Use a Bundle in the first place, so your API is more passing around property bags, or

  • Switch to Serializable, despite it being perhaps a bit slower, as it has the notion of versioning, or

  • Dump the binding pattern entirely and use the command pattern, with extras serving as the property bag

Your alternative of JSON is roughly analogous to using a Bundle in the first place, except that you don't have to fuss with your own marshaling/unmarshaling code with a Bundle.

Parcelable specifically avoids versioning for speed reasons. That's why Parceable is not designed for durable storage, when classes might change between when the data was saved and when the data was read in.

其他提示

If you are only adding an additional method in your AIDL (not modifying an existing one), you can place the additional method as the last method in your AIDL file and that won't break existing dependent apps (who are still built against your previous AIDL definition). Your new service app (with the added method) will work for old apps and new apps (built against your new AIDL definition). In new apps, they just need to catch IllegalArgumentException in the case they are communicating with an old version of your service which supports your older AIDL definition. In my case, I provide a client class which wraps the IPC calls so the apps don't actually do the calling and I do the exception catch there and just log the situation and return null from that method.

Versioning of AIDL is now supported in Android 10 through Stable AIDL.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top