Статьи

Android Full App, часть 4: асинхронное выполнение запроса API из основного действия

Это четвертая часть серии «Полное руководство по Android» . Полное приложение предназначено, чтобы обеспечить легкий способ выполнять поиск фильмов / актеров через Интернет. В первой части серии ( «Основной интерфейс действий» ) мы создали проект Eclipse и настроили базовый интерфейс для основной деятельности приложения. Во второй части ( «Использование HTTP API» ) мы использовали клиентскую библиотеку Apache HTTP для использования внешнего HTTP API и интеграции возможностей поиска API в наше приложение. В третьей части ( «Анализ XML-ответа» ) мы увидели, как анализировать XML-ответ, используя встроенные в Android возможности синтаксического анализа XML. В этой части мы свяжем сервисы HTTP retriever и XML parser для выполнения запроса поиска API из основной деятельности нашего приложения. Запрос будет выполняться асинхронно в фоновом потоке, чтобы избежать блокировки основного потока пользовательского интерфейса.

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

Несмотря на то, что скорость подключения к Интернету на мобильных устройствах значительно улучшилась за последние годы, все еще остается фактом, что загрузка данных из Интернета может занять много времени. Таким образом, мы не хотим приостанавливать основной поток, пока HTTP-клиент ожидает загрузки данных. Также обратите внимание, что остановка пользовательского интерфейса основного потока более чем на пять секунд вызовет появление диалогового окна «Приложение не отвечает» (ANR), и пользователю будет предоставлена ​​возможность убить ваше приложение. Это определенно не особенность, чтобы иметь.

Для этой цели мы собираемся использовать Android API и использовать встроенный класс AsyncTask . Я объяснил его использование в моем предыдущем посте ( «Обратное геокодирование Android с Yahoo API — PlaceFinder» ), но вкратце этот класс позволяет нам правильно и легко использовать поток пользовательского интерфейса. Со страницы официальной документации : « AsyncTask позволяет правильно и легко использовать поток пользовательского интерфейса. Этот класс позволяет выполнять фоновые операции и публиковать результаты в потоке пользовательского интерфейса без необходимости манипулировать потоками и / или обработчиками ».

Прежде чем мы начнем писать асинхронный код, мы сначала представим некоторые классы обслуживания, которые будут отвечать за выполнение HTTP-запросов, анализ XML-ответов, создание соответствующих объектов модели и возврат их в вызывающую активность. Эти классы расширят абстрактный базовый класс с именем GenericSeeker следующим исходным кодом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.javacodegeeks.android.apps.moviesearchapp.services;
 
import java.net.URLEncoder;
import java.util.ArrayList;
 
public abstract class GenericSeeker<E> {
     
    protected static final String BASE_URL = "http://api.themoviedb.org/2.1/";   
    protected static final String LANGUAGE_PATH = "en/";
    protected static final String XML_FORMAT = "xml/";
    protected static final String API_KEY = "<YOUR_API_KEY_HERE>";
    protected static final String SLASH = "/";
     
    protected HttpRetriever httpRetriever = new HttpRetriever();
    protected XmlParser xmlParser = new XmlParser();
     
    public abstract ArrayList<E> find(String query);
    public abstract ArrayList<E> find(String query, int maxResults);
 
    public abstract String retrieveSearchMethodPath();
     
    protected String constructSearchUrl(String query) {
        StringBuffer sb = new StringBuffer();
        sb.append(BASE_URL);
        sb.append(retrieveSearchMethodPath());
        sb.append(LANGUAGE_PATH);
        sb.append(XML_FORMAT);
        sb.append(API_KEY);
        sb.append(SLASH);
        sb.append(URLEncoder.encode(query));
        return sb.toString();
    }
     
    public ArrayList<E> retrieveFirstResults(ArrayList<E> list, int maxResults) {
        ArrayList<E> newList = new ArrayList<E>();
        int count = Math.min(list.size(), maxResults);
        for (int i=0; i<count; i++) {
            newList.add(list.get(i));
        }
        return newList;
    }
 
}

Класс GenericSeeker означает, что он может найти результаты определенного класса, и расширяющиеся классы должны будут предоставить конкретные реализации для соответствующих классов. Будут использованы объекты HttpRetriever и XmlParser из наших предыдущих руководств. Помните, что API TMDb использует похожие URL-адреса для фильмов и поиска людей:

Таким образом, мы используем общий базовый URL, и расширяющиеся классы должны предоставить дополнительный путь путем реализации метода «retrieveSearchMethodPath». Необходимо реализовать еще два метода, find (String) и find (String, int), которые оба возвращают ArrayList объектов с соответствующим классом. Второй может быть использован для того, чтобы сузить общее количество результатов. Это может быть полезно, потому что API обычно возвращает результаты, которые не очень релевантны поисковому запросу и могут быть отброшены, чтобы получить некоторое повышение производительности. Наконец, не забудьте заменить значение переменной API_KEY действительным ключом с сайта TMDb .

Далее у нас есть код для двух дочерних классов, MovieSeeker и PersonSeeker. Классы очень похожи, поэтому я приведу здесь только один из соображений краткости:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.javacodegeeks.android.apps.moviesearchapp.services;
 
import java.util.ArrayList;
 
import android.util.Log;
 
import com.javacodegeeks.android.apps.moviesearchapp.model.Movie;
 
public class MovieSeeker extends GenericSeeker<Movie> {
         
    private static final String MOVIE_SEARCH_PATH = "Movie.search/";
     
    public ArrayList<Movie> find(String query) {
        ArrayList<Movie> moviesList = retrieveMoviesList(query);
        return moviesList;
    }
     
    public ArrayList<Movie> find(String query, int maxResults) {
        ArrayList<Movie> moviesList = retrieveMoviesList(query);
        return retrieveFirstResults(moviesList, maxResults);
    }
     
    private ArrayList<Movie> retrieveMoviesList(String query) {
        String url = constructSearchUrl(query);
        String response = httpRetriever.retrieve(url);
        Log.d(getClass().getSimpleName(), response);
        return xmlParser.parseMoviesResponse(response);
    }
 
    @Override
    public String retrieveSearchMethodPath() {
        return MOVIE_SEARCH_PATH;
    }
 
}

Закрытый метод «retrieveMoviesList» является ядром этого класса. Сначала он создает URL-адрес для вызова API и выполняет HTTP-запрос, используя экземпляр класса HttpRetriever. Если запрос выполнен успешно, ответ XML передается службе XmlParser, которая отвечает за сопоставление ответа с объектом модели Movie. Методы find (String) и find (String, int) фактически являются обертками для закрытого метода.

Теперь мы готовы использовать поисковые сервисы из нашей основной деятельности. Сначала мы создаем экземпляр этих сервисов следующим образом:

1
2
3
4
...
private GenericSeeker<Movie> movieSeeker = new MovieSeeker();
private  GenericSeeker<Person> personSeeker = new PersonSeeker();
...

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
...
private class PerformMovieSearchTask extends AsyncTask<String, Void, List<Movie>> {
 
   @Override
   protected List<Movie> doInBackground(String... params) {
      String query = params[0];
      return movieSeeker.find(query);
   }
    
   @Override
   protected void onPostExecute(final List<Movie> result) {        
      runOnUiThread(new Runnable() {
      @Override
      public void run() {
         if (progressDialog!=null) {
            progressDialog.dismiss();
            progressDialog = null;
         }
         if (result!=null) {
               for (Movie movie : result) {
                  longToast(movie.name + " - " + movie.rating);
               }
            }
      }
       });
   }
       
}
...

Сначала мы объявляем, что наша реализация расширяет класс AsyncTask>. Эта подпись означает, что тип параметров, отправляемых в задачу при выполнении, относится к типу String , что во время фоновых вычислений (обозначаемых как Void ) не будут публиковаться единицы прогресса и что результат фоновых вычислений имеет тип List, который содержит Объекты кино. В методе doInBackground мы выполняем фактический поиск данных HTTP, а затем, в методе onPostExecute, представляем результаты в виде уведомлений Toast . Обратите внимание, что другой класс задач с именем «PerformPersonSearchTask» также создается и используется для поиска людей.

Обратите внимание, что виджет ProgressDialog используется для того, чтобы пользователь знал, что поиск данных происходит и что он должен быть терпеливым. Диалог хода выполнения объявляется отменяемым в заводском методе, поэтому пользователь может отменить задачу по запросу. Соответствующий код следующий:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
...
private void performSearch(String query) {
         
        progressDialog = ProgressDialog.show(MovieSearchAppActivity.this,
                "Please wait...", "Retrieving data...", true, true);
         
        if (moviesSearchRadioButton.isChecked()) {
            PerformMovieSearchTask task = new PerformMovieSearchTask();
            task.execute(query);
            progressDialog.setOnCancelListener(new CancelTaskOnCancelListener(task));
        }
        else if (peopleSearchRadioButton.isChecked()) {
            PerformPersonSearchTask task = new PerformPersonSearchTask();
            task.execute(query);
            progressDialog.setOnCancelListener(new CancelTaskOnCancelListener(task));
        }
         
    }
...

Мы также предоставляем реализацию для OnCancelListener диалогового окна прогресса с единственной целью отмены соответствующей задачи. Код следующий:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
...
private class CancelTaskOnCancelListener implements OnCancelListener {
        private AsyncTask<?, ?, ?> task;
        public CancelTaskOnCancelListener(AsyncTask<?, ?, ?> task) {
            this.task = task;
        }
        @Override
        public void onCancel(DialogInterface dialog) {
            if (task!=null) {
                task.cancel(true);
            }
        }
    }
...

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

Пользователь может отменить задачу в любое время, нажав кнопку «Назад». Если операция прошла успешно, будет вызван метод обратного вызова, и пользователю будет представлен тост за каждый результат, как показано на следующем рисунке:

На этом завершается четвертая часть учебного цикла. Вы можете скачать здесь созданный проект Eclipse.

Статьи по Теме :