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);
            }
        }
    }

Что здесь не так?

Решение

Решение напоминает удалённый вызов метода средствами Spring: просто вызываешь метод интерфейса, фреймворк займётся всем остальным.

Так, код в предыдущем примере должен иметь вид:


    public void onClick(View v) {
        controller.getPosts(true, time);
    }

    @OnResult("getPosts")
    public void onPostsReceived(List posts) {
        // здесь мог бы быть мой код
    }

Эта задача легко решается с помощью Proxy — благо, Java предоставляет такую возможность.

Вот код на GitHub.

Чтобы им пользоваться, нужно объявить интерфейс со всеми методами, которые вызываются удалённо, и его реализацию. Вызов метода из Activity или Fragment спровоцирует выполнение метода в сервисе, его результат будет возвращён в другой метод. Сам по себе метод, ведущий к прослойке (controller.getPosts() в примере), вернёт 0 или null.

Библиотека умеет из коробки работать с Retrofit'ом: для него уже в комплекте есть адаптер.


← клик, если это интересно   |   ↓ место для вопросов и идей