Помимо самых основных приложений 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.