Использование AsyncTask в сочетании с n-уровневой моделью - PullRequest
0 голосов
/ 13 августа 2011

Я думаю, что могу ошибаться, поэтому выкладываю это, чтобы узнать.

Я занимаюсь разработкой приложения для Android, которое использует несколько запросов на отдых для загрузки своих данных.

Я расположил свой код по n-уровневому шаблону: уровень домена (домен) / уровень доступа к веб-сервису (dal) / уровень бизнес-логики (сервис) / уровень пользовательского интерфейса (ui)

Из моегодеятельность Я запускаю AsyncTask для загрузки элементов из сервисного уровня.Пока элементы загружаются, я показываю диалог прогресса со значком непрерывного зацикливания.Это не очень удобно для пользователя, поэтому я хотел бы отображать прогресс вместо индикатора прогресса.

Я могу легко изменить свой dal так, чтобы он сначала получал список идентификаторов элементов для выборки, а затем выбирал их по одному, вместо большого количества.Таким образом, я мог бы сообщать пользователю о прогрессе, обновляя пользовательский интерфейс каждый раз, когда загружается новый элемент (или 2 или более).

Проблема в том, что я не знаю, как это сделать.Все примеры AsyncTask работают по принципу, согласно которому все происходит внутри класса, который наследует AsyncTask, и это должно выполняться из потока пользовательского интерфейса.

Есть ли кто-нибудь, кто может предоставить краткий пример кода о том, как я должен определитьмой класс AsyncTask и как я мог бы сообщать о прогрессе из уровня доступа к данным в пользовательский интерфейс?

Если бы я поместил весь код в пользовательский интерфейс, проблем не было бы, но это не очень хорошопотренироваться в imo.

Пример кода того, что я хочу сделать:

Где-то в MainApplication Я храню список заказов:

public List<Order> orders = null;

Моя деятельность вызывает AsyncTask и запускаетсяэто:

OrderActivity.java

public void loadOrdersAsync() {
    // Create async task instance
    OrderLoaderAsyncTask loader = new OrderLoaderAsyncTask();

    // Start loading
    loader.doInBackground();
}

OrderLoadAsyncTask.java

public class OrderLoaderAsyncTask  extends AsyncTask {
    @Override
    public Integer doInBackground(Context.... params) {
        OrderService service = new OrderService();
        MainApplication.orders = service.getAll();
    }

    @Override
    public void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);

        // Update progress bar
        updateProgressBar(values);
    }
}

OrderService.java

public class OrderService {
    public List<Order> getAll() {
        // Check to see if we have a cached version
        // If we don't, we fetch the orders from REST webservice
        OrderRepository repository = new OrderRepository();

        // Get list of ID's to fetch
        List<Integer> ids = new ArrayList<Integer>;
        List<Order> orders = new ArrayList<Order>;
        ids = repository.getIDs();

        // For each id, fetch order from webservice
        for (Integer id : ids) {
            Order order = repository.getOneById(id);

            orders.add(order);

            // Report progress, but how?
        }

        return orders;
    }
}

Теперь в моем сервисе я могуследить за прогрессом, но как мне сообщить об этом прогрессе в пользовательский интерфейс, не перемещая логику для построения списка заказов в мой класс AsyncTask?Как мне сообщить о прогрессе из уровня сервиса?Или даже из моего слоя хранилища?

Ответы [ 4 ]

2 голосов
/ 13 августа 2011

Первое, что я хотел бы сказать, это то, что, по моему мнению, n-уровневая архитектура, которую вы описываете, немного излишня для большинства мобильных приложений.Это не значит, что вам не нужно разделять свое приложение и разделять обязанности.Просто уровни, которые вы описываете, действительно являются хорошими архитектурными уровнями для крупных корпоративных приложений, но я считаю, что применение этих уровней строго на основе кода Android приводит к борьбе с системой, когда на самом деле есть (лучший) способ сделать это для Android.

Например, одна из ваших проблем заключается в том, что вы хотели бы полностью отделить «службу» от своей деятельности, но все же хотите обновить что-то в пользовательском интерфейсе.Это невозможно.

Android широко использует внедрение зависимостей.Вы заметите, что Context часто является первым аргументом для различных методов.Просто чтобы показать, что если вы хотите полностью отделить что-то от пользовательского интерфейса, вам нужно создать новый «контекст» для работы.Android предоставляет это с услугами.Таким образом, вы можете работать полностью отдельно от пользовательского интерфейса и использовать намерения для обмена сообщениями между фоновой службой и пользовательским интерфейсом.

Но я понимаю, что для некоторых приложений или начинающих разработчиков Android службы кажутся немного сложными (они не являютсядействительно).Просто хотел отметить, что сервисы - это единственный реальный способ создания потока, который не связан с деятельностью.Некоторые люди используют трюки, переопределяя класс Application и отображая мягкие ссылки на действия и потоки.Таким образом, когда происходят изменения конфигурации (например, изменение ориентации), потоки не уничтожаются и могут получить ссылку на вновь созданный экземпляр действия.Эти трюки, на мой взгляд, являются анти-шаблонами.

Возвращаясь к вашей проблеме.

Я предлагаю сделать это простым.Просто знайте, что запуск нового потока из пользовательского интерфейса подразумевает, что поток может быть убит при изменении конфигурации, и вы можете потерять данные, уже извлеченные из сети.

  1. Напишите класс утилиты http, который переносит некоторые записи /получить функциональность и, возможно, установить некоторые заголовки http для каждого запроса.
  2. Написать простой слой обслуживания.Эти классы содержат методы, каждый из которых выполняет одну простую задачу или запрос покоя.
  3. В своей деятельности используйте AsyncTask, который объединяет некоторые методы обслуживания и обеспечивает обратную связь с пользовательским интерфейсом

Вы можете улучшитьоб этом позже, если вам нравится перемещать логику из AsyncTask в ее собственную реальную фоновую службу и выполнять промежуточные сообщения с помощью Intents и BroadcastReceivers.

Но для вашего удобства приведен упрощенный пример того, как сделать это простым способом без служб:

public class OrderActivity extends Activity {

    private ProgressDialog progressDialog = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        new OrderLoaderTask().execute();
    }

    class Order {
        // Add some data
    }

    class OrderService {

        public List<Integer> getOrderIds() {
            List<Integer> ids = Arrays.asList(new Integer[] {1, 2, 3, 4, 5, 6, 7});
            return ids;         
        }

        public Order getOrder(int id) {
            // Do real http request through some utility class
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            return new Order();
        }

    }

    class OrderLoaderTask extends AsyncTask<Void, Integer, List<Order>> {

        @Override
        protected void onPreExecute() {
            progressDialog = ProgressDialog.show(TestAndroidActivity.this, "loading", "loading");
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            progressDialog.setMessage(String.format("loading (%s/%s)", values[0], values[1]));
        }

        @Override
        protected List<Order> doInBackground(Void... params) {

            OrderService service = new OrderService();

            List<Order> orders = new ArrayList<Order>();

            List<Integer> ids = service.getOrderIds();
            for (int i=0; i<ids.size(); i++) {
                orders.add(service.getOrder(ids.get(i)));
                publishProgress(i+1, ids.size());
            }
            return orders;
        }

        @Override
        protected void onPostExecute(List<Order> result) {
            if (result != null) {
                // Do something with the results!
            }
            progressDialog.dismiss();
        }

    }
}

Примечание: просто создайте новый проект Android, скопируйте этот код, исправьте импорт и запустите!

Если вы хотите «повторно использовать» код в AsyncTask, перейдите в его собственный файл, сделайте класс общедоступным и переопределите методы ... Опять же, между потоками всегда необходим некоторый связующий код или интерфейс.

new OrderLoaderTask() {

    protected void onProgressUpdate(Integer[] values) {
        // Update progress
    };

    protected void onPostExecute(List<Order> result) {
        // Do something
    };

}.execute();
1 голос
/ 13 августа 2011

Я не вижу способа сделать это с точностью, службы REST являются своего рода запросом-ответом, вы получите данные только после того, как все они будут загружены в слой данных внутри сервера REST.

Я сделаю это с помощью индикатора выполнения, обновляя каждые 500 мсек с помощью таймера. Просто помните, что Timer запускает другой поток, поэтому вам нужно обновить интерфейс внутри метода runOnUiThread () вашей активности.

Пример кода:

public class myActivity extends Activity {

    private Timer myTimer;

    @Override
    public void onCreate(Bundle b) {
        super.onCreate(b);
        setContentView(R.layout.main);

        myTimer = new Timer();
        myTimer.schedule(new TimerTask() {
            @Override
            public void run() {
                TimerMethod();
            }

        }, 0, 500);  // 500mS starting with no delay
    }

    private void TimerMethod()
    {
            // this runs each time Timer fires
        this.runOnUiThread(Timer_Tick);
    }

    private Runnable Timer_Tick = new Runnable() {
        public void run() {

            // do something with my progress bar
        UpdateMyProgressBar();

        }
    };
}
1 голос
/ 13 августа 2011

Я не рекомендую собирать объекты один за другим, потому что это приведет к перегрузке вашей базы данных, если у вас много записей. Для этого рассмотрите возможность использования подкачки (загружайте только в том случае, если пользователь действительно видит элемент на экране).

Что вы можете сделать, чтобы ускорить процесс, это загрузить минимум данных (например, только заголовок и описание) для отображения в списке, а остальные получить позже, если потребуется дополнительная информация.

1 голос
/ 13 августа 2011

AsyncTask имеет функцию, вызываемую при обновлении прогресса, которая позволяет вам обновлять поток пользовательского интерфейса с вашим прогрессом.Это сделано для того, чтобы делать именно то, что вы описываете.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...