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:
- Created a class with the signature
public class Result<T extends Parcelable> implements Parcelable
.
- Created a new class to throw into the first one, which is called Animal. The signature is
public class Animal implements Parcelable
.
- 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.
- 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.
- 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.