pyeleven pyeleven - 3 months ago 16
Android Question

Android ResultReceiver across packages

I have an activity in package A (SignerClient), and a service in package B (MyService)

The activity's resultreceiver:

private ResultReceiver resultreceiver = new ResultReceiver(null) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
...
}
};


Starting the service:

Intent intent = new Intent("com.example.STARTSERVICE");
intent.putExtra("resultreceiver", resultreceiver);
startService(intent);


Receiving end:

ResultReceiver rr = (ResultReceiver) intent.getParcelableExtra("resultreceiver");


Doing this when client and server are in the same package works fine. But in this case i get:

FATAL EXCEPTION: IntentService[MyService]
android.os.BadParcelableException: ClassNotFoundException when unmarshalling: com.example.cryptoclient.SignerClient$1
at android.os.Parcel.readParcelable(Parcel.java:1883)
at android.os.Parcel.readValue(Parcel.java:1771)
at android.os.Parcel.readMapInternal(Parcel.java:2008)
at android.os.Bundle.unparcel(Bundle.java:208)
at android.os.Bundle.getParcelable(Bundle.java:1100)
at android.content.Intent.getParcelableExtra(Intent.java:3396)
at org.axades.service.MyService.onHandleIntent(MyService.java:28)
at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:59)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:123)
at android.os.HandlerThread.run(HandlerThread.java:60)


What am I missing? Is my idea even possible?

Answer

Yes, your idea is possible. The ClassNotFoundException exception is thrown because you are trying to unparcel a class that was created in a different process by a different ClassLoader.

ResultReceiver class implements Parcelable interface which is suitable for inter-process calls (IPC), however to read your object in the service you need to use the same ClassLoader, that was used for object creation in the client application (i.e. in the activity). To get that ClassLoader on the service side, call createPackageContext method passing client package name and CONTEXT_INCLUDE_CODE|CONTEXT_IGNORE_SECURITY combination of flags. This will return client Context object from which the correct ClassLoader object can be obtained.

Example:

public int onStartCommand(Intent intent, int flags, int startId) {
  try {

// assuming SignerClient activity is located in the package "com.example.client.A"
    Context context = createPackageContext("com.example.client.A", Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
    ClassLoader cl = context.getClassLoader();

    Bundle bundle = intent.getExtras();
    bundle.setClassLoader(cl);
    ResultReceiver rr = bundle.getParcelable("resultreceiver");

//... your interaction with ResultReceiver ...
    rr.send(1, null);   // will result in a onReceiveResult call in the client activity

  } catch (NameNotFoundException e) {
    Log.e("MyService", "SignerClient package context was not found", e);
    throw new RuntimeException(e);
  }
  return START_STICKY;
}

I've just used it in my code - works like a charm.

UPDATE
However I suggest to consider using Messenger instead of ResultReceiver. It implements Parcelable interface and does not need to be extended thus the ClassLoader issue isn't possible. And it's also recommended in the official documentation.

UPDATE 2
In case you still prefer to use ResultReceiver take a look at Robert's answer in this thread. It looks cleaner and simpler than hacky context manipulations.