Клас AsyncTask (застарілий)
У попередніх статтях був описаний загальний підхід, який застосовується зараз для запуску нового потоку в додатку та оновлення в ньому користувацького інтерфейсу. Розглянемо інший підхід, який представляє клас AsyncTask. Хоча застосування AsyncTask в сучасних додатках Android застаріло, тим не менш, оскільки він досі широко застосовується, також розглянемо його.
Щоб використовувати AsyncTask, нам потрібно:
- Створити клас, який успадковується від
AsyncTask(як правило, для цього створюється внутрішній клас уactivityабо у фрагменті) - Перевизначити один або кілька методів
AsyncTaskдля виконання деякої роботи у фоновому режимі - При необхідності створити об'єкт
AsyncTaskі викликати його методexecute(), щоб почати роботу
Отже, створимо найпростіший додаток із використанням AsyncTask. Визначимо наступну розмітку інтерфейсу у файлі activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="16dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="22sp"
android:id="@+id/clicksView"
android:text="Clicks: 0"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/clicksBtn"
android:text="Click" />
</LinearLayout>
<Button
android:id="@+id/progressBtn"
android:text="Запуск"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/statusView"
android:text="Статус"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ProgressBar
android:id="@+id/indicator"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="100"
android:progress="0" />
</LinearLayout>
Тут визначено кнопку для запуску фонового потоку, а також текстове поле і прогрессбар для індикації виконання завдання. Крім того, тут визначено додаткову кнопку, яка збільшує кількість кліків, і текстове поле, яке виводить число кліків.
Далі визначимо в класі MainActivity такий код:
import android.os.AsyncTask;
import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
int[] integers=null;
int clicks = 0;
ProgressBar indicatorBar;
TextView statusView;
TextView clicksView;
Button progressBtn;
Button clicksBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
integers = new int[100];
for(int i=0;i<100;i++) {
integers[i] = i + 1;
}
indicatorBar = (ProgressBar) findViewById(R.id.indicator);
statusView = findViewById(R.id.statusView);
progressBtn = findViewById(R.id.progressBtn);
progressBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new ProgressTask().execute();
}
});
clicksView = findViewById(R.id.clicksView);
clicksBtn = findViewById(R.id.clicksBtn);
clicksBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
clicks++;
clicksView.setText("Clicks: " + clicks);
}
});
}
class ProgressTask extends AsyncTask<Void, Integer, Void> {
@Override
protected Void doInBackground(Void... unused) {
for (int i = 0; i<integers.length;i++) {
publishProgress(i);
SystemClock.sleep(400);
}
return(null);
}
@Override
protected void onProgressUpdate(Integer... items) {
indicatorBar.setProgress(items[0]+1);
statusView.setText("Статус: " + String.valueOf(items[0]+1));
}
@Override
protected void onPostExecute(Void unused) {
Toast.makeText(getApplicationContext(), "Задача завершена", Toast.LENGTH_SHORT)
.show();
}
}
}
Клас задачі ProgressTask визначений як внутрішній клас. Він успадковується не просто від AsyncTask, а від його типізованої версії AsyncTask<Void, Integer, Void>. Вона типізується трьома типами:
- Клас для зберігання інформації, яка потрібна для обробки задачі
- Тип об'єктів, які використовуються для індикації процесу виконання задачі
- Тип результату задачі
Ці типи можуть бути представлені різними класами. У даному випадку сутність задачі буде полягати в переборі масиву integers, що представляє набір елементів Integer. І тут нам не треба передавати в задачу жоден об'єкт, тому перший тип йде як Void.
Для індикації перебору використовуються цілі числа, які показують, який об'єкт з масиву ми зараз перебираємо. Тому в якості другого типу використовується Integer.
В якості третього типу знову використовується Void, оскільки в даному випадку не потрібно нічого повертати з задачі.
AsyncTask містить чотири методи, які можна перевизначити:
- Метод
doInBackground(): виконується у фоновому потоці, повинен повертати певний результат - Метод
onPreExecute(): викликається з головного потоку перед запуском методуdoInBackground() - Метод
onPostExecute(): виконується з головного потоку після завершення роботи методуdoInBackground() - Метод
onProgressUpdate(): дозволяє сигналізувати користувачеві про виконання фонового потоку
Оскільки метод doInBackground() не приймає нічого і не повертає нічого, то в якості його параметра використовується Void... - масив Void, і в якості повернутого типу - також Void. Ці типи відповідають першому та третьому типам в AsyncTask<Void, Integer, Void>.
Метод doInBackground() перебирає масив і при кожній ітерації сповіщає систему за допомогою методу publishProgress(item). Оскільки в нашому випадку для індикації використовуються цілі числа, то параметр item має представляти ціле число.
Метод onProgressUpdate(Integer... items) отримує передане вище число і застосовує його для налаштування текстового поля та прогресбару.
Метод onPostExecute() виконується після завершення задачі і в якості параметра приймає об'єкт, що повертається з методу doInBackground() - тобто в даному випадку об'єкт типу Void. Щоб сигналізувати про закінчення роботи, тут виводиться на екран спливаюче повідомлення.
Запустимо додаток. Запустимо завдання, натиснувши на кнопку:
При цьому поки виконується завдання, ми можемо паралельно натискати на другу кнопку і збільшувати кількість кліків, або виконувати якусь іншу роботу в застосунку.
AsyncTask і фрагменти
Під час використання AsyncTask слід враховувати такий момент. Оптимальнішим способом є робота AsyncTask із фрагментом, ніж безпосередньо з activity. Наприклад, якщо ми візьмемо попередній проєкт, запустимо застосунок і змінимо орієнтацію мобільного пристрою, то відбудеться перестворення activity. У разі зміни орієнтації пристрою потік AsyncTask продовжуватиме звертатися до старої activity, замість нової. Тому в цьому випадку краще використовувати фрагменти.
Отже, візьмемо проєкт з минулої теми і додамо в нього новий фрагмент, який назвемо ProgressFragment.
Визначимо для нього новий файл розмітки інтерфейсу fragment_progress.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_progress"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="16dp"
android:orientation="vertical">
<Button
android:id="@+id/progressBtn"
android:text="Запуск"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/statusView"
android:text="Статус"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ProgressBar
android:id="@+id/indicator"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="100"
android:progress="0" />
</LinearLayout>
Сам клас фрагмента ProgressFragment змінимо таким чином:
package com.example.eugene.asyncapp;
import android.widget.Button;
import android.os.AsyncTask;
import android.os.Bundle;
import android.app.Fragment;
import android.os.SystemClock;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import android.view.View.OnClickListener;
public class ProgressFragment extends Fragment {
int[] integers=null;
ProgressBar indicatorBar;
TextView statusView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_progress, container, false);
integers = new int[100];
for(int i=0;i<100;i++) {
integers[i] = i + 1;
}
indicatorBar = (ProgressBar) view.findViewById(R.id.indicator);
statusView = (TextView) view.findViewById(R.id.statusView);
Button btnFetch = (Button)view.findViewById(R.id.progressBtn);
btnFetch.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
new ProgressTask().execute();
}
});
return view;
}
class ProgressTask extends AsyncTask<Void, Integer, Void> {
@Override
protected Void doInBackground(Void... unused) {
for (int i = 0; i<integers.length;i++) {
publishProgress(i);
SystemClock.sleep(400);
}
return null;
}
@Override
protected void onProgressUpdate(Integer... items) {
indicatorBar.setProgress(items[0]+1);
statusView.setText("Статус: " + String.valueOf(items[0]+1));
}
@Override
protected void onPostExecute(Void unused) {
Toast.makeText(getActivity(), "Задача завершена", Toast.LENGTH_SHORT)
.show();
}
}
}
Тут визначено всі ті дії, які були розглянуті в минулій темі і які раніше перебували в класі MainActivity. Особливо варто відзначити виклик setRetainInstance(true) у методі onCreate(), який дає змогу зберігати стан фрагмента незалежно від зміни орієнтації.
Тепер зв'яжемо фрагмент з activity. Для цього визначимо у файлі activity_main.xml такий код:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/progressFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.example.eugene.asyncapp.ProgressFragment"/>
</LinearLayout>
А сам клас MainActivity скоротимо:
package com.example.eugene.asyncapp;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
Тепер якщо ми запустимо додаток, то незалежно від зміни орієнтації мого мобільного пристрою фонове завдання продовжуватиме свою роботу:
