LINUX.ORG.RU

Android, MVC, Async и Callback.

 , , , ,


0

2

Доброй ночи ЛОР, сразу извиняюсь если вопрос ранее поднимался, поиском не нашел, суть в следующем:
Я делаю приложение соблюдая паттерн MVC (Model-View-Controller), т.е. по сути я отделяю все данные и логику от контроллера (В случае с ведром это ActivityName.class).
Проблема возникает, когда сюда приходит Async, приведу пример:
В модели есть класс NetworkWorker, который отвечает за всю работу с сетью (все делается через AsyncTask), допустим работа с сетью закончилась, внутри класса NetworkWorker стригеррился Callback, по нему я обратабываю полученные данные и делаю что-то (Парсинг, если речь о JSON'e), но, когда я закончил с обработкой полученных данных, как мне передать все это дело в контроллер, чтобы тот в свою очередь обновил все View? В голову идет только написание своего Callback'а и последующий его триггеринг, когда ВСЯ работа закончена (а.к.а. получение и обработка закончены и данные готовы к употреблению). Но, насколько такой подход является правильным?

Если для низкоуровневых операций (например, получить сериализованные данные из сети) выполняется асинхронная обработка с вызовом callback по готовности результата, то с теоретической точки зрения для вышестоящего API (например, получить данные, готовые к употреблению) есть примерно 3 варианта:

  1. API может синхронно вызывать callback сразу после того как данные будут готовы
  2. API может асинхронно инициировать вызов callback через какой-то диспетчер вызовов
  3. API может предоставлять блокирующий вызов, который будет блокироваться пока внутренние асинхронные вызовы не обработаются.

Первый вариант хорош, если контроллер и UI переживёт вызов метода из того потока, в котором выполняется callback низкоуровневой операции, второй - если не переживёт.

Третий вариант - [см. комментарии ЛОР-экспертов ниже].

GPFault ★★
()

Вполне нормально. И ваш Callback также может быть AsyncTask. Правда, существует очень ненулевая вероятность того, что ваш Callback сработает когда View уже убит, но это уже детали реализации, поэтому вам придётся городить прокси-контейнер, в котором лежит ссылка на реальный обработчик, вроде вот такого:

interface Callback {
    void perform(Object data);
}

// Во избежание утечки ресурсов, реальный callback оборачиваем в прокси
class CallbackProxy implements Callback {
    Callback callback;
    public synchronized void perform(Object data) {
       if (callback!=null) callback.perform(data);
    }
}

// Контейнер параметров асинхронного задания (заодно хранит и callback)
class AsyncTaskArgs {
    int a1;
    float a2;
    String url;
    Callback callback;
}

class ActivityName extends Activity implements Callback {

    // Activity может быть уничтожен в любой момент и пересоздан,
    // и новый экземпляр должен принять данные, поэтому текущий
    // экземпляр Activity записан в синглетон.
    static CallbackProxy proxy = new CallbackProxy();
    protected void onCreate(Bundle b) { synchronized(proxy) { proxy.callback = this; } }
    protected void onDestroy() { synchronized(proxy) { proxy.callback = null; } }

    private void startTask() {
        AsyncTaskArgs arg = new AsyncTaskArgs();
        arg.callback = proxy; arg.a1 = 1; arg.a2 = 1.1; arg.url = "http://ya.ru/";
        new AsyncTask<AsyncTaskArgs,Integer,Callback>() {
            protected Callback doInBackground(AsyncTaskArgument arg) {
                ...
                return arg.callback;
            }
            protected void onPostExecute(Callback callback) {
                callback.perform(somedata);
            }
        }.start(args);
    }
}

Собственно, всё - теперь AsyncTask может быть запущен в любом количестве копий, каждая из них вызывает в конце выполнения переданный в параметрах callback. Метод startTask стартует задание завязав его на работающую ActivityName. Если в процессе работы задания пользователь сменит ориентацию, или выйдет из ActivityName не дожидаясь завершения задания, то уведомление о завершении обработки придёт либо в новый экземпляр ActivityName, либо уйдёт в никуда (если ActivityName пришёл конец жизненного цикла). Минус такой схемы - не поддерживается несколько копий ActivityName.

ActivityName.proxy - диспетчер. Его можно как угодно усложнять.

Nastishka ★★★★★
()
Ответ на: комментарий от Nastishka

Спасибо за пример, уже разобрался, добавил Callback в Worker и обработал его в контроллере, все работает.

demch0g
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.