Android ResultReceiver через пакеты
У меня есть активность в пакете 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)
Что мне не хватает? Возможна ли моя идея?
Ответы
Ответ 1
Да, твоя идея возможна. Исключение ClassNotFoundException
выбрано потому, что вы пытаетесь раскрыть класс, который был создан в другом процессе, с помощью другого ClassLoader
.
ResultReceiver
класс реализует интерфейс Parcelable
, который подходит для межпроцессных вызовов (IPC), однако для чтения вашего объекта в службе вам нужно использовать тот же ClassLoader, который использовался для создания объекта в клиентском приложении (т.е. в деятельности). Чтобы получить этот класс ClassLoader со стороны службы, вызовите createPackageContext
метод, передающий имя клиентского пакета и CONTEXT_INCLUDE_CODE
| CONTEXT_IGNORE_SECURITY
комбинация флагов. Это вернет объект Контекста клиента, из которого может быть получен правильный объект 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;
}
Я только что использовал его в своем коде - работает как шарм.
UPDATE
Однако я предлагаю использовать Messenger
вместо ResultReceiver
. Он реализует интерфейс Parcelable
и не нуждается в расширении, поэтому проблема с ClassLoader невозможна. И это также рекомендуется в официальной документации.
ОБНОВЛЕНИЕ 2
Если вы все еще предпочитаете использовать ResultReceiver
, посмотрите ответ Robert в этой теме. Он выглядит чище и проще, чем хакерские манипуляции контекстом.
Ответ 2
Я хотел использовать 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
в дополнение к другому пакету и может быть отменен без штрафа.
Ответ 3
По какой-то причине у меня была эта ошибка только с android.support.v4.os.ResultReceiver
. Изменение импорта на android.os.ResultReceiver
заставило результаты ipc работать сразу. Обходной путь не требуется!