6

Does Android AIDL support generics?

For example, assume that I have a class Result<T>, where T can be any type including primitives (via autoboxing) or other custom classes such as Car. Any custom classes implement Parcelable as required by Binder.

Then possible AIDL method signatures would be

  • Result<Car> m1();
  • Result<Void> m2();
  • Result<Boolean> m3();
Daniel
  • 2,380
  • 29
  • 44
  • "Does Android AIDL support generics?" -- not that I am aware of, insofar as AIDL is cross-language, not specific to Java, and I have no idea how C/C++ would consume your desired methods. If `Result` is `Parcelable`, you can probably have `Result m();`, though. – CommonsWare Jan 20 '15 at 18:25

2 Answers2

6

From what I could gather, the AIDL compiler doesn't like things like Result<Animal> getResult();. However, Result getResult(); does work. So this is what I did:

  1. Created a class with the signature public class Result<T extends Parcelable> implements Parcelable.
  2. Created a new class to throw into the first one, which is called Animal. The signature is public class Animal implements Parcelable.
  3. Had to implement methods required by interface Parcelable and a CREATOR in both Result and Animal, and also created one AIDL for each as is required and imported both classes in the main AIDL. This stuff is regular AIDL work and is describe in the AIDL site.
  4. Inside Result, we store not only an object of type T but also a Class object. When writing the parcel we need to write first the class type and only then the generic object. When reading, we do it in the same order. We need to write the class type because when we read we have to do t = (T) in.readValue(classType.getClassLoader()); and without a class type we do not know which class loader to fetch. There are probably other ways to do this but this is how I've done it for this example.
  5. When receiving on the client node, I can successfully do Result<Animal> r = MainActivity.this.service.getResult(); and then call methods on both Result and Animal.

Some code that will hopefully makes things more clearer can be found below.

public class Result<T extends Parcelable> implements Parcelable {

    private String msg;
    private Class classType;
    private T value;

    public Result(String msg, T value, Class classType) {
        this.msg = msg;
        this.value = value;
        this.classType = classType;
    }

    // to reconstruct object
    public Result(Parcel in) {
        readFromParcel(in);
    }

    public String getMsg() {
        return msg;
    }

    public T getValue() {
        return value;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(msg);
        dest.writeValue(classType);
        dest.writeValue(value);
    }

    private void readFromParcel(Parcel in) {
        this.msg = in.readString();
        this.classType = (Class) in.readValue(Class.class.getClassLoader());
        this.value = (T) in.readValue(classType.getClassLoader());
    }

    public static final Creator<Result> CREATOR = new Creator<Result>() {
        @Override
        public Result createFromParcel(Parcel source) {
            return new Result(source);
        }

        @Override
        public Result[] newArray(int size) {
            return new Result[size];
        }
    };
}


public class Animal implements Parcelable {

    private int n;

    public Animal(int n) {
        this.n = n;
    }

    public Animal(Parcel in) {
        readFromParcel(in);
    }

    public int getN() {
        return n;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(n);
    }

    private void readFromParcel(Parcel in) {
        n = in.readInt();
    }

    public static final Creator<Animal> CREATOR = new Creator<Animal>() {
        @Override
        public Animal createFromParcel(Parcel source) {
            return new Animal(source);
        }

        @Override
        public Animal[] newArray(int size) {
            return new Animal[size];
        }
    };
}

Excerpt from the Service:

@Override
public Result getResult() throws RemoteException {
    Result<Animal> r = new Result<Animal>("this is an animal", new Animal(42), Animal.class);
    return r;
}

Excerpt from the Client:

Result<Animal> r = MainActivity.this.service.getResult();

Log.d(TAG, "Received the following (Outer): " + r.getMsg());
Log.d(TAG, "Received the following (Inner): " + r.getValue().getN());

Another way to do it is changing the signature of Result into public class Result<T extends Serializable> implements Parcelable, making Animal implement Serializable, and then use dest.writeSerializable(value); and this.value = (T) in.readSerializable(); inside Result.

With this approach there is no need to send the class type to the other side or even use it at all. You will, nonetheless, pay the price.

Daniel
  • 2,380
  • 29
  • 44
  • Hi Daniel, would it be possible to provide link for the above implementation or share the code. Sorry, I could not implement your approach completely. Thanks in advance!! – Atul Kaushik Mar 16 '15 at 18:54
  • Do you actually need to pass the classType around? It looks like you're only using it to get the classloader. – Hounshell Dec 15 '16 at 16:52
4

Daniels solution almost worked for me except the thing with marshalling and unmarshaling classtype. Instead of "dest.writeValue(classType);" and "this.classType = (Class) in.readValue(Class.class.getClassLoader());" I had to use "dest.writeSerializable(classType);" and "classType = (Class) in.readSerializable();" and it worked like a charm

Thank you Daniel

Teodor Hirs
  • 449
  • 5
  • 8