» » » Руководство по фоновой работе в Android. Часть 1

 

Руководство по фоновой работе в Android. Часть 1

Автор: admin от 13-02-2018, 12:40, посмотрело: 34

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



Руководство по фоновой работе в Android. Часть 1NetworkOnMainThreadException.



Семь лет назад, когда я разрабатывал свои первые приложения на Android, подход от Google был ограничен использованием AsyncTasks. Давайте посмотрим, как мы писали код для общения с сервером (псевдокод преимущественно):



public class LoadWeatherForecastTask extends AsyncTask<String, Integer, Forecast> {
   public Forecast doInBackground(String... params) {
       HttpClient client = new HttpClient();
       HttpGet request = new HttpRequest(params[0]);
       HttpResponse response = client.execute(request);
       return parse(response);
   }
}


Метод doInBackground() гарантированно будет вызван не на основном потоке. Но на каком? Зависит от реализации. Вот как Android выбирает поток (это часть исходного кода класса AsyncTask):



@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) {
    ...
    mStatus = Status.RUNNING;

    onPreExecute();

    mWorker.mParams = params;
    exec.execute(mFuture); // <-- mFuture contains a Runnable with our doInBackgroundMethod

    return this;
}


Здесь можно увидеть, что выполнение зависит от параметра Executor. Посмотрим, откуда он берется:



public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
...
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
...
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
    return executeOnExecutor(sDefaultExecutor, params);
}


Как здесь указано, по умолчанию executor ссылается на пул потоков размера 1. Это означает, что все AsyncTasks в вашем приложении запускаются последовательно. Это не всегда было верно, так как для версий ОС от DONUT до HONEYCOMB использовался пул размером от 2 до 4(в зависимости от количества ядер процессора). После HONEYCOMB AsyncTasks снова выполняются последовательно по умолчанию.



Итак, работа выполнена, байты закончили свое длинное путешествие с другого полушария. Нужно превратить их во что-то понятное и разместить на экране. К счастью, наша Activity тут как тут. Давайте поместим результат в одно из наших View.



public class LoadWeatherForecastTask extends AsyncTask<String, Integer, Forecast> {
    public Forecast doInBackground(String... params) {
       HttpClient client = new HttpClient();
       ...
       Forecast forecast = parse(response);
       mTemperatureView.setText(forecast.getTemp());
    }
}


О, черт! Опять исключение!



android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

Но мы не делали никаких сетевых обращений на основном потоке! Правильно, но мы попытались нарушить другой закон UI. Пользовательский интерфейс можно менять только из UI-потока. Это верно не только для Android, но и практически для любой системы, с которой вы столкнетесь. Причину этого хорошо объяснили в Java Concurrency In Practice. Вкратце – архитекторы хотели избежать сложной блокировки при изменениях из нескольких источников (пользовательский ввод, биндинг и другие изменения). Использование единственного потока решает эту проблему.



Да, но UI все равно нужно обновлять. У AsyncTask есть еще метод onPostExecute, который вызывается на UI-потоке:



public void onPostExecutre(Forecast forecast) {

       mTemperatureView.setText(forecast.getTemp());
}


Как эта магия работает? Посмотрим в исходном коде AsyncTask:



private void finish(Result result) {
    if (isCancelled()) {
        onCancelled(result);
    } else {
        onPostExecute(result);
    }
    mStatus = Status.FINISHED;
}
private static class InternalHandler extends Handler {
    public InternalHandler(Looper looper) {
        super(looper);
    }

    @Override
    public void handleMessage(Message msg) {
        AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
        switch (msg.what) {
            case MESSAGE_POST_RESULT:
                // There is only one result
                result.mTask.finish(result.mData[0]);
                break;
        }
    }
}


AsyncTask использует Handler для вызова onPostExecute в UI, ровно как и метод postOnUiThread в компонентах Android.



Handler прячет всю внутреннюю кухню. Какую именно? Идея состоит в том, чтобы иметь бесконечный цикл проверки сообщений, приходящих на UI-поток, и обрабатывать их соответствующе. Велосипедов тут никто не изобретает, хотя без кручения педалей не обошлось.



Руководство по фоновой работе в Android. Часть 1



Для Android это реализовано классом Looper, который передается в InternalHandler в приведенном выше коде. Суть класса Looper находится в методе loop:



public static void loop() {
...
    for (;;) {
       Message msg = queue.next(); 
       ....
       msg.target.dispatchMessage(msg);
    }
...
}


Он просто опрашивает очередь входящих сообщений в бесконечном цикле и обрабатывает эти сообщения. Это означает, что на UI-потоке должен быть инициализированный экземпляр Looper. Можно получить доступ к нему с помощью статического метода:



public static Looper getMainLooper()


Кстати, вы только что узнали, как проверить, вызывается ли ваш код в UI-потоке:



if (Looper.myLooper() === Looper.getMainLooper()) {
// we are on the main thread
}


Если вы попытаетесь создать экземпляр Handler в методе doInBackground, то получите другое исключение. Оно сообщит о необходимости наличия Looper для потока. Теперь вы знаете, что это значит.



Надо заметить, что AsyncTask может быть создан только в UI-потоке по указанным выше причинам.

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




  • Каждый раз нужно писать достаточно много кода для решения относительно простой задачи

  • AsyncTasks ничего не знают о жизненном цикле. При неправильном обращении лучшее, что вы получите — утечка памяти, в худшем – сбой

  • AsyncTask не поддерживает сохранение состояния прогресса и повторное использование результатов загрузки.



В следующей части я подробно разберу эти проблемы и покажу, как Loaders могут помочь их решить.



Источник: Хабрахабр

Категория: Операционные системы » Android

Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь.
Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

Добавление комментария

Имя:*
E-Mail:
Комментарий:
Полужирный Наклонный текст Подчеркнутый текст Зачеркнутый текст | Выравнивание по левому краю По центру Выравнивание по правому краю | Вставка смайликов Выбор цвета | Скрытый текст Вставка цитаты Преобразовать выбранный текст из транслитерации в кириллицу Вставка спойлера
Введите два слова, показанных на изображении: *