Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android AIDL with Java generics

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();
like image 550
Daniel Avatar asked Jan 20 '15 18:01

Daniel


1 Answers

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.

like image 113
Daniel Avatar answered Sep 29 '22 21:09

Daniel