我在包A(SignerClient)中有一个活动,在包B(MyService)中有一个服务
活动的结果收件人:
private ResultReceiver resultreceiver = new ResultReceiver(null) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
...
}
};
启动服务:
Intent intent = new Intent("com.example.STARTSERVICE");
intent.putExtra("resultreceiver", resultreceiver);
startService(intent);
接收结束:
ResultReceiver rr = (ResultReceiver) intent.getParcelableExtra("resultreceiver");
当客户端和服务器在同一个包中时,这样做可以正常工作。但在这种情况下,我得到:
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)
我错过了什么?我的想法甚至可能吗?
答案 0 :(得分:24)
我想跨包使用ResultReceiver
并且必须加载其他包的上下文,这对我来说似乎不对...毕竟,接收包不需要知道特定的子类使用ResultReceiver
,它只需要能够调用send()
并让IPC绑定魔法发生。需要使用特定的子类似乎是一个设计缺陷。
有一种解决方法:
public static ResultReceiver receiverForSending(ResultReceiver actualReceiver) {
Parcel parcel = Parcel.obtain();
actualReceiver.writeToParcel(parcel,0);
parcel.setDataPosition(0);
ResultReceiver receiverForSending = ResultReceiver.CREATOR.createFromParcel(parcel);
parcel.recycle();
return receiverForSending;
}
此代码将ResultReceiver
的某个子类的实例转换为ResultReceiver
本身的实例,但仍会将结果发送到原始actualReceiver
。 receiverForSending
可以Intent
额外发送到另一个包中,并且可以免费解组。
答案 1 :(得分:16)
是的,你的想法是可能的。抛出ClassNotFoundException
异常是因为您尝试取消由不同ClassLoader
在不同进程中创建的类。
ResultReceiver
类实现了Parcelable
接口,它适用于进程间调用(IPC),但要在服务中读取您需要使用相同ClassLoader的对象,该对象用于创建对象在客户端应用程序中(即在活动中)。要在服务端获取该ClassLoader,请调用createPackageContext
方法传递客户端软件包名称和CONTEXT_INCLUDE_CODE
| CONTEXT_IGNORE_SECURITY
标志组合。这将返回客户端Context对象,从中可以获取正确的ClassLoader对象。
示例:
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;
}
我刚刚在我的代码中使用它 - 就像魅力一样。
<强>更新强>
不过,我建议您考虑使用Messenger
代替ResultReceiver
。它实现了Parcelable
接口,不需要扩展,因此无法实现ClassLoader问题。它也是recommended in the official documentation。
更新2
如果您仍然希望使用ResultReceiver
,请查看Robert's answer in this thread。它看起来比hacky上下文操作更简洁,更简单。
答案 2 :(得分:0)
出于某种原因,我仅在android.support.v4.os.ResultReceiver
时出现此错误。将导入更改为android.os.ResultReceiver
会使ipc结果立即生效。无需解决方法!