Помимо самых основных приложений Android, все, что вы создаете, потребует, по крайней мере, некоторого использования фонового потока для выполнения операции. Это связано с тем, что в Android есть время, известное как тайм-аут ANR (приложение не используется), которое возникает, когда операция занимает пять или более секунд в потоке пользовательского интерфейса, предотвращая ввод пользователя и вызывая зависание пользователя. приложение.
Чтобы избежать этого, вы должны перенести более длительные операции, такие как сетевые запросы или медленные запросы к базе данных, в другой поток, чтобы не мешать пользователю продолжать использовать ваше приложение. Несмотря на то, что всесторонний охват многопоточности является крупным и сложным предметом в области компьютерных наук, этот учебник познакомит вас с основными понятиями многопоточности в Android и с некоторыми из доступных инструментов, которые помогут вам создавать приложения, которые работают лучше с использованием фоновых процессов.
Тебе легче учиться с видео? Почему бы не проверить наш курс:
Понимание потоков
Когда приложение запускается, запускается новый процесс Linux с одним основным потоком выполнения. Это поток, который имеет доступ к инструментарию пользовательского интерфейса Android, прослушивает вводимые пользователем данные и обрабатывает рисование на экране устройства Android. Из-за этого это также обычно называют потоком пользовательского интерфейса .
Все компоненты приложения выполняются в одном потоке и обрабатываются по умолчанию, хотя могут быть созданы дополнительные потоки для перемещения задач из потока пользовательского интерфейса и предотвращения ANR. Когда дело доходит до многопоточности в Android, нужно помнить о двух простых правилах, чтобы приложение работало должным образом:
- Не блокируйте поток пользовательского интерфейса.
- Не пытайтесь получить доступ к компонентам пользовательского интерфейса Android вне потока пользовательского интерфейса.
Хотя вы можете соблюдать первое правило, просто создав новый Thread и Runnable , обработка второго правила становится немного сложнее. Рассмотрим следующий фрагмент кода:
|
01
02
03
04
05
06
07
08
09
10
11
|
new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(6000);
} catch( InterruptedException e ) {
}
mTextView.setText(«test»);
}
}).start();
|
Хотя этот код не останавливает поток пользовательского интерфейса, пока поток спит после истечения времени ожидания ANR, попытка установить текст TextView приведет к тому, что приложение выдаст следующую ошибку:
|
1
|
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
|
К счастью, есть несколько простых способов обойти это. Вы можете использовать метод Android runOnUiThread(Runnable) чтобы выполнить код обратно в главном потоке приложения.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
mTextView = (TextView) findViewById(R.id.text);
new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(6000);
} catch( InterruptedException e ) {
}
runOnUiThread(new Runnable() {
@Override
public void run() {
mTextView.setText(«test»);
}
});
}
}).start();
|
Или вы можете взять стандартный объект View и post на нем Runnable .
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(6000);
} catch( InterruptedException e ) {
}
mTextView.post(new Runnable() {
@Override
public void run() {
mTextView.setText(«test»);
}
});
}
}).start();
|
В то время как оба эти трюка помогут сделать ваши операции безопасными, но по мере усложнения вашего приложения его обслуживание станет громоздким.
AsyncTask
AsyncTask — AsyncTask один из инструментов, предоставляемых Android для управления сложностью фоновых потоков. AsyncTask предоставляет рабочий поток для блокирования операций, а затем AsyncTask результат обратно в поток пользовательского интерфейса с помощью предварительно созданного метода обратного вызова, что позволяет легко выполнять задачи без необходимости использовать потоки и обработчики.
AsyncTask Lifecycle
Прежде чем начать использовать класс AsyncTask , вам необходимо понять жизненный цикл по сравнению с выполнением операции в главном потоке.

Первый метод, который вызывается AsyncTask это onPreExecute() . Этот метод выполняется в потоке пользовательского интерфейса и предназначен для настройки любых компонентов интерфейса, которые должны дать пользователю знать, что что-то происходит.
После onPreExecute() вызывается doInBackground(T) . Общий параметр здесь — это любая информация, которую вам нужно передать методу, чтобы он мог выполнить свою задачу. Например, если вы пишете задачу, которая извлекает JSON из URL-адреса, вы передадите URL-адрес этому методу в виде String . Когда операция выполняется в doInBackground() , вы можете вызвать onProgressUpdate(T) чтобы обновить ваш пользовательский интерфейс (например, индикатор выполнения на экране). Здесь универсальный является значением, представляющим прогресс, такой как Integer .
После завершения doInBackground() он может вернуть объект, переданный в onPostExecute(T) , такой как JSONObject который был загружен с нашего исходного URL-адреса. onPostExecute(T) работает в потоке пользовательского интерфейса.
Когда вы создаете класс AsyncTask , вы должны переопределить эти обобщения как в объявлении класса, так и в описанных выше методах. Пример AsyncTask который обновляет ProgressBar каждую секунду, можно увидеть здесь:
|
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
|
protected class DemoAsyncTask extends AsyncTask<Integer, Void, String> {
@Override
protected void onPreExecute() {
super.onPreExecute();
mProgress.setProgress(0);
mProgress.setVisibility(View.Visible);
}
@Override
protected void onProgressUpdate(Integer… values) {
super.onProgressUpdate(values);
mProgress.setProgress(values[0]);
}
@Override
protected Void doInBackground(Void… params) {
for( int i = 0; i < 100; i++ ) {
try {
Thread.sleep(1000);
} catch( InterruptedException e ) {}
publishProgress(i);
}
return «All done!»;
}
@Override
protected void onPostExecute(String result) {
super.onPostExecute(aVoid);
if( isCancelled() ) {
return;
}
mProgress.setVisibility(View.GONE);
Toast.makeText(context, result, Toast.LENGTH_SHORT).show();
}
}
|
Возможно, вы заметили, что onPostExecute(T) проверяет isCancelled() . Это потому, что есть одна большая проблема с AsyncTasks : они поддерживают ссылку на Context даже после того, как этот Context был уничтожен.
Это легче всего увидеть при запуске AsyncTask а затем при вращении экрана. Если вы попытаетесь сослаться на элемент Context (например, View или действие) после уничтожения исходного Context , будет Exception . Самый простой способ обойти это — вызвать метод cancel(true) для AsyncTask в методе onDestroy() вашего Activity или Fragment , а затем проверить, что задача не была отменена в onPostExecute(T) .
Как и в любом AsyncTask программировании, ответ на вопрос, когда следует использовать AsyncTask : это зависит. Хотя AsyncTasks просты в использовании, они не являются AsyncTasks и конечным решением для многопоточности, и их лучше всего использовать для коротких операций, длящихся не более нескольких секунд. Если у вас есть операция, которая может длиться дольше, я рекомендую вам изучить использование ThreadPoolExecutor , Service или GcmNetworkManager (обратно совместимая версия JobScheduler ).
Сервисы
Когда вам нужно выполнить длительную операцию в фоновом режиме, например, воспроизведение музыки, выполнение сетевых транзакций или взаимодействие с поставщиком контента, вы можете рассмотреть возможность использования Service . Базовая Service может существовать в двух состояниях: запущена и ограничена.
Запущенная Service запускается компонентом в вашем приложении и остается активной в фоновом режиме устройства, даже если исходный компонент уничтожен. Когда задача, которую выполняет запущенная Service , завершена, Service остановится сама. Стандартная запущенная Service обычно используется для длительных фоновых задач, которым не требуется связь с остальной частью приложения.
Связанная Service похожа на запущенную Service и предоставляет обратные вызовы для различных компонентов приложения, которые могут с ней связываться. Когда все связанные компоненты отсоединятся от Service , он остановится сам. Важно отметить, что эти два способа запуска Service не являются взаимоисключающими: вы можете запустить Service которая будет работать неограниченное время и иметь привязанные к ней компоненты.
IntentService
Одна из самых больших проблем со стандартной Service заключается в том, что она не может обрабатывать несколько запросов одновременно, поскольку это было бы многопоточным кошмаром. Одним из способов решения этой IntentService является расширение IntentService , которое расширяет стандартную Service . IntentService создает рабочий поток по умолчанию для выполнения всех намерений, полученных в onStartCommand() , поэтому все операции могут выполняться вне основного потока. Затем он создает рабочую очередь для отправки каждого намерения в onHandleIntent() одному, чтобы вам не onHandleIntent() беспокоиться о проблемах с многопоточностью.
Помимо обработки потоков, IntentService также автоматически останавливается после обработки всех запросов на запуск. Поскольку все детали реализации обрабатываются в IntentService , работа для вас, как для разработчика, довольно проста.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
public class ExampleIntentService extends IntentService {
//required constructor with a name for the service
public ExampleIntentService() {
super(«ExampleIntentService»);
}
@Override
protected void onHandleIntent(Intent intent) {
//Perform your tasks here
try {
Thread.sleep(5000);
} catch (InterruptedException e) {}
}
}
|
Вывод
В этом уроке вы узнали много нового о многопоточности и многопоточности в Android. Целые книги были написаны о многопоточности в Android, но теперь у вас должно быть достаточно основы для кодирования общих задач и понимания более подробной документации для ваших более сложных приложений Android.
