13

I have a Parcelable object which I use to pass it from Activity to remote service. When I pass it using AIDL interface, everything sounds fine.

Recently, I try to pass it through Messenger from Activity.

// TEST TEST TEST!
StockInfo stockInfo0 = new StockInfo(Code.newInstance("code0"), Symbol.newInstance("symbol0"));
StockInfo stockInfo1 = new StockInfo(Code.newInstance("code1"), Symbol.newInstance("symbol1"));
StockInfo stockInfo2 = new StockInfo(Code.newInstance("code2"), Symbol.newInstance("symbol2"));
List<StockInfo> stockInfos = new ArrayList<StockInfo>();
stockInfos.add(stockInfo0);
stockInfos.add(stockInfo1);
stockInfos.add(stockInfo2);
StockInfosEx stockInfosEx = new StockInfosEx(stockInfos, "abc");
msg.obj = stockInfosEx;

try {
    mService.send(msg);
} catch (RemoteException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}

I'm getting the following exception in remote service.

02-21 22:55:16.546: E/Parcel(8365): Class not found when unmarshalling: com.example.testonmessenger.StockInfosEx, e: java.lang.ClassNotFoundException: com.example.testonmessenger.StockInfosEx

I was wondering, what can get wrong in between? Here is my Parcelable object.

public class StockInfosEx implements Parcelable {
    public final List<StockInfo> stockInfos;
    public final String searchedString;

    public StockInfosEx(List<StockInfo> stockInfos, String searchedString) {
        this.stockInfos = stockInfos;
        this.searchedString = searchedString;
    }

    ////////////////////////////////////////////////////////////////////////////
    // Handling Parcelable nicely.

    public static final Parcelable.Creator<StockInfosEx> CREATOR = new Parcelable.Creator<StockInfosEx>() {
        public StockInfosEx createFromParcel(Parcel in) {
            return new StockInfosEx(in);
        }

        public StockInfosEx[] newArray(int size) {
            return new StockInfosEx[size];
        }
    };

    private StockInfosEx(Parcel in) {
        stockInfos = new ArrayList<StockInfo>();
        in.readTypedList(stockInfos, StockInfo.CREATOR);
        searchedString = in.readString();
    }

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

    @Override
    public void writeToParcel(Parcel parcel, int flags) {
        parcel.writeTypedList(stockInfos);
        parcel.writeString(searchedString);
    }

    // Handling Parcelable nicely.    
    ////////////////////////////////////////////////////////////////////////////        
}

To get complete source code, kindly download from https://www.dropbox.com/s/n69yuhddpb8vedz/testonmessenger.zip

Cheok Yan Cheng
  • 47,586
  • 132
  • 466
  • 875

3 Answers3

23

Not Workable Approach (Because our Parcelable is custom, not part of Framework like Rect)

Activity

msg.obj = stockInfosEx;

Remote Service

StockInfosEx stockInfosEx = (StockInfosEx)msg.obj;

Workable Approach

Activity

msg.getData().putParcelable("data", stockInfosEx);

Remote Service

msg.getData().setClassLoader(StockInfosEx.class.getClassLoader());
StockInfosEx stockInfosEx = (StockInfosEx)msg.getData().getParcelable("data");

Now, after I read back the documentation of msg.obj (http://developer.android.com/reference/android/os/Message.html#obj) again, only I understand what it really mean by Parcelable of a framework class

An arbitrary object to send to the recipient. When using Messenger to send the message across processes this can only be non-null if it contains a Parcelable of a framework class (not one implemented by the application). For other data transfer use setData(Bundle).

Note that Parcelable objects here are not supported prior to the FROYO release.

Cheok Yan Cheng
  • 47,586
  • 132
  • 466
  • 875
6

You're probably not using the right ClassLoader. You need to keep track of the ClassLoader that is marshalling the class in the first place, and use THAT ClassLoader to unmarshall it.

When unmarshalling, you're using current thread's ClassLoader, which is not your UIThread but Android system thread, and as such, has no info about your custom classes.

I used a static class that contained my ClassLoader to solve this (similar approaches can be used without having it to be static).

Something like:

ClassLoaderHelper.setClassLoader(Thread.currentThread().getContextClassLoader());

Then when unmarshalling:

public final void readFromParcel(final Parcel in) {
    id = in.readString();
    appInfo = in.readParcelable(ClassLoaderHelper.getClassLoader());
    ...
}

See this other question for more detailed information (probably a duplicate btw).

Community
  • 1
  • 1
m0skit0
  • 25,268
  • 11
  • 79
  • 127
  • Yup. I'm have double checked. Pretty sure correct classloader is being use during marshalling and unmarshalling. If not, the data structures is not usable for AIDL case. – Cheok Yan Cheng Feb 21 '13 at 15:45
  • I'm telling you because I've had this problem before and I too was sure the right ClassLoader was being used, and it was not. When unmarshalling, you're using Android's default ClassLoader which has no information about your custom classes. I used a static class that contained my ClassLoader to solve this. Did you read CommonsWare's answer btw? – m0skit0 Feb 21 '13 at 15:47
  • But, my remote service is in different process than activity. How can they refer to a same class loader? I perform further testing. My remote service can explicitly construct `StockInfosEx`. So, it should know the definition of it. Just that I'm not sure why Parcel fail on that. – Cheok Yan Cheng Feb 21 '13 at 16:07
  • Perhaps you would like to help to test run on the complete source code, to see whether you can provide any valuable input? thanks ya! – Cheok Yan Cheng Feb 21 '13 at 16:08
  • OK. After reading the documentation and referring back your link, I can understand why my code doesn't work. Thanks. – Cheok Yan Cheng Feb 21 '13 at 16:38
  • In my case I'm also passing objects between service and app. This doesn't matter as long as the class of the object is found on both sides. – m0skit0 Feb 21 '13 at 19:28
3

I'm getting the following exception in remote service.

If you are truly getting this from the remote service, it is because the remote service app does not contain that class. If you are going to use custom Parcelable classes, both the client and the server must have the same class definition.

If, however, your stack trace feels like your Parcelable is being accessed from a core OS process, then you cannot pass Parcelable objects via obj reliably. I have only ever used obj on Message for in-process object passing, never for cross-process messages.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491