Android. Простой типобезопасный способ выполнения кода в сервисе
Продолжение статьи «1001 способ потерять загруженную картинку».
Обычно развязка между фрагментом (Presenter) и сервисом (Controller) выглядит со стороны фрагмента так:
public void onClick(View v) {
// запустить действие
Bundle bun = new Bundle();
bun.putBoolean("add", true);
bun.putLong("from", time);
startMainService(MainService.ACTION_GET_POSTS, bun);
}
// обработать результат
@Override
protected void onStart() {
super.onStart();
mMainReceiver = new Receiver();
IntentFilter filter = new IntentFilter();
filter.addAction(MainService.ACTION_IMAGE_DOWNLOADED);
filter.addAction(MainService.ACTION_CONNECTED);
filter.addAction(MainService.ACTION_POSTS_RECEIVED);
filter.addAction(MainService.ACTION_POST_CHANGED);
filter.addAction(MainService.ACTION_CHATS_RECEIVED);
LocalBroadcastManager
.getInstance(this)
.registerReceiver(mMainReceiver, filter);
}
@Override
protected void onStop() {
if (mMainReceiver != null) {
LocalBroadcastManager
.getInstance(this)
.unregisterReceiver(mMainReceiver);
}
super.onStop();
}
private class Receiver extends BroadcastReceiver {
private static final String TAG = "MainActivity$Receiver";
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
case MainService.ACTION_CONNECTED:
onConnected(intent.getIntExtra(Intent.EXTRA_TEXT, 0));
break;
case MainService.ACTION_IMAGE_DOWNLOADED:
onImageDownloaded(intent.getStringExtra(Intent.EXTRA_TEXT));
break;
case MainService.ACTION_POSTS_RECEIVED:
onPostsReceived();
break;
case MainService.ACTION_POST_CHANGED:
onPostChanged(intent.getLongExtra("id", -1));
break;
case MainService.ACTION_CHATS_RECEIVED:
onChatsReceived();
break;
default:
Log.e(TAG, "misplaced intent: " + intent);
}
}
}
Что здесь не так?
всё это чересчур пахнет рутиной. Много кода для отправки запроса, для установки Intent-фильтров, для обработки результата;
очень легко что-нибудь забыть: не добавить фильтр или case, не положить аргумент в Bundle;
никакой типобезопасности: любой nullable-аргумент неподхдящего типа будет проигнорирован.
Решение
Решение напоминает удалённый вызов метода средствами Spring: просто вызываешь метод интерфейса, фреймворк займётся всем остальным.
Так, код в предыдущем примере должен иметь вид:
public void onClick(View v) {
controller.getPosts(true, time);
}
@OnResult("getPosts")
public void onPostsReceived(List posts) {
// здесь мог бы быть мой код
}
Эта задача легко решается с помощью Proxy — благо, Java предоставляет такую возможность.
Чтобы им пользоваться, нужно объявить интерфейс со всеми методами, которые вызываются удалённо, и его реализацию. Вызов метода из Activity или Fragment спровоцирует выполнение метода в сервисе, его результат будет возвращён в другой метод. Сам по себе метод, ведущий к прослойке (controller.getPosts() в примере), вернёт 0 или null.
Библиотека умеет из коробки работать с Retrofit'ом: для него уже в комплекте есть адаптер.