Android ResultReceiver через пакеты - PullRequest
12 голосов
/ 21 апреля 2011

У меня есть активность в пакете 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)

Что мне не хватает? Возможна ли моя идея?

Ответы [ 3 ]

24 голосов
/ 29 августа 2012

Я хотел использовать 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 в другой пакет и может быть просто распакован.

16 голосов
/ 15 августа 2011

Да, ваша идея возможна. Исключение 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, взгляните на Ответ Роберта в этой теме . Это выглядит чище и проще, чем хакерские манипуляции с контекстом.

0 голосов
/ 07 сентября 2016

По какой-то причине у меня была эта ошибка только с android.support.v4.os.ResultReceiver. Изменение импорта на android.os.ResultReceiver сделало результаты ipc работающими сразу. Обходной путь не требуется!

...