You are on page 1of 44

Розробка мобільних

застосувань під Android

Лекція 10

1
Виконання задач у
фоновому потоці

2
Створення фонового потоку
При запуску програми Android створюється один потік,
який називається основним (main thread). Якщо потрібно
створити новий потік, використовується клас Thread з
пакету java.lang.
Клас Thread інкапсулює всю функціональність створення
та виконання фонового потоку. Цей клас використовує
інтерфейс Runnable. В інтерфейсі Runnable визначено
абстрактний метод run(), який є точкою входу нового
потоку: public void run();
Для створення потоку треба перевизначити метод run(), у
якому помістити код, що виконує фонову задачу. Після
цього створюється екземпляр класу Thread, конструктор
якого передається об'єкт Runnable:
public Thread(Runnable runnable);
Для запуску потоку необхідно викликати метод start() класу
Thread. При виконанні потік викликатиме метод run() 3

об'єкта Runnable.
Створення фонового потоку
Як завдання у нас буде цикл на 100 кроків. На кожному кроці
циклу потік засипатиме на 100 мілісекунд і виводитиме в LogCat
повідомлення:
Thread.sleep(100);
Log.i("Thread", String.format("End task #%d", count));
Таким чином, повний час виконання завдання становитиме 10
секунд. Потік запускатиметься при натисканні кнопки Start.

<?xml version="1.0" encoding="utf-8"?>


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:orientation="vertical">
<Button
android:id="@+id/button_start"
android:layout_height="wrap_content"
android:text="Start"
android:onClick="onClick"
android:layout_width="fill_parent"/>
</LinearLayout>
4
Створення фонового потоку
public class MainActivity extends Activity implements View.OnClickListener {
// Кількість циклів для завдання
private static final int MAX_COUNT = 100;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public void onClick(View v) {
// Створюємо об'єкт Thread
Thread background = new Thread(new Runnable() {
public void run() {
int count = 0;
// Виконуємо цикл
while (count < MAX_COUNT) {
try {
// Зупитяэмо потік на 100 ms
Thread.sleep(100);
// Виводимо в LogCat повідомлення
Log.i("Thread", String.format("End task #%d", count));
count++;
}
catch (InterruptedException e) {
Log.e("ERROR", "Thread Interrupted");
}
}
}
});
// Запускаємо завдання
background.start(); 5
}
}
Надсилання повідомлень в основний потік
Потоки, створені за допомогою об'єкта Thread, не мають доступу
до інтерфейсу користувача, і ми не можемо безпосередньо
відобразити в основному потоці хід виконання завдання. Для
безпечної взаємодії фонових потоків з інтерфейсом користувача
система Android надає клас Handler. Створений фоновий потік
може взаємодіяти з об'єктом Handler, який у свою чергу
оновлюватиме графічний інтерфейс в основному потоці,
наприклад виводити повідомлення або показувати ступінь
виконання завдання.
Для обміну інформацією між потоками використовується
механізм повідомлень, представлений класом Message. Цей
клас має кілька полів та методів для присвоєння та читання
повідомлень. Якщо надсилаємо об'єкти довільного типу,
використовується метод setData(Bundle data). Якщо посилати
об'єкти довільного типу не потрібно, метод setData()
застосовувати не потрібно: наприклад, для одного цілого
значення достатньо використовувати поле what, для пари
значень - поля arg1 і arg2
6
Надсилання повідомлень в основний потік
Перш ніж надіслати повідомлення в об'єкт Handler, необхідно
викликати метод obtainMessage(), щоб витягти об'єкт Message з
глобального пулу повідомлень:
Handler handler;
...
// Виймаємо повідомлення
Message msg = handler.obtainMessage();
Далі треба вставити повідомлення у чергу. Для вставлення
повідомлення в чергу повідомлень об'єкта Handler існує кілька способів:
 sendMessage(Message msg) — поміщає повідомлення до кінця черги
повідомлень;
 sendMessageAtFrontOfQueue(Message msg) — поміщає
повідомлення на початок черги;
 sendMessageAtTime(Message msg, long uptimeMillis) — поміщає
повідомлення в чергу після всіх повідомлень, що очікують, до
встановленого часу uptimeMillisу мілісекундах;
 sendMessageDelayed(Message msg, long uptimeMillis) — поміщає 7
повідомлення в чергу після затримки uptimeMillis у мілісекундах.
Надсилання повідомлень в основний потік
Для надсилання повідомлення без затримки з простим цілочисельним
параметром (це може бути, наприклад, крок виконання завдання) в
чергу повідомлень об'єкту Handler можна використати наступний код:
// Записуємо параметр, який потрібно передати
msg.what = 99;
handler.sendMessage(msg);
В принципі, для передачі лише одного параметра у класі Handler є набір
методів
 sendEmptyMessage():

 sendEmptyMessage(int what)

 sendEmptyMessageAtTime(int what, long uptimeMillis)

 sendEmptyMessageDelayed(int what, long delayMillis)

яким передається лише один параметр what і при необхідності


встановлюється затримка, аналогічно до групи методів sendMessage().
При використанні цих методів для надсилання повідомлень нам немає
необхідності використовувати метод для вилучення повідомлень
obtainMessage(), ми просто викликаємо sendEmptyMessage() з параметром:
int what = 99; 8
sendEmptyMessage(int what);
Надсилання повідомлень в основний потік
Щоб обробляти отримані повідомлення, для об'єкта
Handler необхідно реалізувати інтерфейс
Handler.Callback, у якому перевизначити метод
зворотного виклику handleMessage(). Цей метод буде
викликатися кожним повідомленням з черги:
private Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
int param = msg.what;
// Код обробки повідомлення
...
}

});

9
Надсилання повідомлень в основний потік
public class MainActivity extends Activity implements View.OnClickListener {
private static final int MAX_COUNT = 100;
private TextView text;
private Handler handler = new Handler(new Handler.Callback() { <?xml version="1.0" encoding="utf-8"?>
@Override <LinearLayout xmlns:android=«
public boolean handleMessage(Message msg) { http://schemas.android.com/apk/res/android"
// Виводимо повідомлення у текстове поле android:layout_width="fill_parent"
text.setText(String.format("End task #%d", msg.what)); android:layout_height="fill_parent"
return false;
android:gravity="center"
}
}); android:orientation="vertical">
@Override <TextView
public void onCreate(Bundle savedInstanceState) { android:id="@+id/text"
super.onCreate(savedInstanceState); android:layout_height="wrap_content"
setContentView(R.layout.activity_main); android:layout_width="wrap_content"
text = (TextView)findViewById(R.id.text);
android:layout_margin="5px"
}
@Override android:textStyle="bold"/>
public void onClick(View v) { <Button
Thread background = new Thread(new Runnable() { android:id="@+id/button_start"
public void run() { android:layout_height="wrap_content"
int count = 0; android:text="Start"
while (count <= MAX_COUNT) {
android:onClick="onClick"
try {
Thread.sleep(100); android:layout_width="fill_parent"/>
// Надсилаємо повідомлення в основний потік </LinearLayout>
handler.sendEmptyMessage(count);
count++;
}
catch (InterruptedException e) {
Log.e("ERROR", "Thread Interrupted");
} } } }); 10
background.start();
} }
Припинення виконання потоку
У програмах часто з’являється необхідність
примусово припинити виконання фонового потоку.
Але із зупинкою потоку є деякі проблеми. У класі
Thread є метод stop(), але це метод оголошений
застарілим. Він не рекомендується до використання,
тому що є небезпечним.
Тому для припинення виконання потоку зазвичай
використовують інший спосіб. Можна створити в
програмі змінну типу Boolean (прапор), в якій ми
можемо змінювати значення, наприклад true - при
виконанні потоку і false - при зупинці, і контролювати
цей прапор у методі run() при виконанні завдання

11
Припинення виконання потоку
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/text"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_margin="5px"
android:textStyle="bold"/>
<LinearLayout
android:id="@+id/LinearLayout01"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_margin="5px">
<Button
android:id="@+id/button_start"
android:text="Start"
android:layout_weight="1"
android:onClick="onClick"
android:layout_height="wrap_content"
android:layout_width="fill_parent"/>
<Button
android:id="@+id/button_stop"
android:text="Stop"
android:layout_weight="1"
android:onClick="onClick"
android:layout_height="wrap_content"
android:layout_width="fill_parent"/> 12
</LinearLayout>
</LinearLayout>
Припинення виконання потоку
public class MainActivity extends Activity implements View.OnClickListener {
private TextView text;
private Thread thread;
// Прапор виконання потоку
private boolean isRunning; public void startThread() {
private static final int MAX_COUNT = 100; thread = new Thread(new Runnable() {
private Handler handler = new Handler(new Handler.Callback() { public void run() {
@Override int count = 0;
public boolean handleMessage(Message msg) { // Додатково перевіряємо значення isRunning
text.setText(String.format("End task #%d", msg.what)); while (count <= MAX_COUNT && isRunning) {
return false; try {
} Thread.sleep(100);
}); handler.sendEmptyMessage(count);
@Override count++;
public void onCreate(Bundle savedInstanceState) { }
super.onCreate(savedInstanceState); catch (InterruptedException e) {
setContentView(R.layout.activity_main); Log.e("ERROR", "Thread Interrupted");
text = (TextView)findViewById(R.id.text); }
} }
@Override }
public void onClick(View v) { });
switch (v.getId()) { // Запускаємо потік
case R.id.button_start: isRunning = true;
startThread(); thread.start();
break; }
case R.id.button_stop: public void stopThread() {
stopThread(); // зупиняємо потік
break; isRunning = false;
} 13
}
} }
Виконання дій в основному потоці
У класі Activity є метод runOnUiThread(), який дозволяє з
фонового потоку, створеного в цьому Activity, безпосередньо
звертатися до основного потоку інтерфейсу користувача. Цей
метод викликається у фоновому потоці. Йому передається як
параметр Runnable, у методі run() якого ми можемо
безпосередньо звертатися до основного потоку:
runOnUiThread(new Runnable() {
@Override
public void run() {
// Отримуємо доступ до UI, можемо його оновлювати
...
}
});
Таким чином, при використанні цього методу відпадає необхідність
створення екземпляра Thread та надсилання повідомлень. По суті
цей метод інкапсулює всі дії, які ми виконували раніше для
взаємодії між фоновим і основним потоками.

14
Виконання дій в основному потоці
public class MainActivity extends Activity implements View.OnClickListener {
private TextView text;
private Thread background;
private boolean isRunning; public void startThread() {
private static int count; background = new Thread(new Runnable() {
private static final int MAX_COUNT = 100; public void run() {
@Override count = 0;
public void onCreate(Bundle savedInstanceState) { while (count < MAX_COUNT && isRunning) {
super.onCreate(savedInstanceState); try {
setContentView(R.layout.activity_main); Thread.sleep(100);
text = (TextView)findViewById(R.id.text); runOnUiThread(new Runnable() {
} @Override
@Override public void run() {
public void onClick(View v) { text.setText
switch (v.getId()) { (String.format("End task #%d", count));
case R.id.button_start: }
startThread(); });
break; count++;
case R.id.button_stop: }
stopThread(); catch (InterruptedException e) {
break; Log.e("ERROR", "Thread Interrupted");
} }
} }
}
});
isRunning = true;
background.start();
}
public void stopThread() {
isRunning = false; 15
}
}
ProgressBar
Для відображення ступеня завершеності тривалих
завдань у програмі застосовується індикатор прогресу.
Всі, напевно, з ним знайомі, тому що індикатор прогресу
використовується практично на всіх платформах і мають
схожий зовнішній вигляд.
В Android індикатор прогресу представлений класом
ProgressBar. Основні методи, що використовуються для
роботи з об'єктом ProgressBar:
 setProgress() - встановлює задане значення прогресу;

 getProgress() – повертає поточне значення прогресу;

 incrementProgressBy() - встановлює величину


дискретизації збільшення значення прогресу;
 setMax() – встановлює максимальне значення величини
прогресу.
16
public class MainActivity extends Activity implements View.OnClickListener {
ProgressBar private TextView text;
private Thread thread;
<?xml version="1.0" encoding="utf-8"?> private boolean isRunning;
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" private static final int MAX_COUNT = 100;
android:layout_width="fill_parent" private ProgressBar progress;
android:layout_height="fill_parent"
android:gravity="center" private Handler handler = new Handler(new Handler.Callback() {
android:orientation="vertical"> @Override
<TextView public boolean handleMessage(Message msg) {
android:id="@+id/text" // Поновлюємо індикатор
android:layout_height="wrap_content" progress.setProgress(msg.what);
android:layout_width="wrap_content" text.setText(String.format("End task #%d", msg.what));
android:layout_margin="5dp" return false;
android:textStyle="bold"/> }
<ProgressBar });
@Override
android:id="@+id/progress"
public void onCreate(Bundle savedInstanceState) {
style="?android:attr/progressBarStyleHorizontal"
super.onCreate(savedInstanceState);
android:layout_width="fill_parent"
setContentView(R.layout.activity_main);
android:layout_height="wrap_content" /> text = (TextView)findViewById(R.id.text);
<LinearLayout
progress = (ProgressBar)findViewById(R.id.progress);
android:layout_width="fill_parent"
}
android:layout_height="wrap_content" @Override
public void onClick(View v) {
android:orientation="horizontal" switch (v.getId()) {0
android:layout_margin="5dp"> case R.id.button_start:
startThread();
<Button break;
case R.id.button_stop:
android:id="@+id/button_start" stopThread();
break;
android:text="Start" }
android:onClick="onClick" }
public void startThread() {
android:layout_weight="1" thread = new Thread(new Runnable() {
public void run() {
android:layout_height="wrap_content" int count = 0;
while (count <= MAX_COUNT && isRunning) {
android:layout_width="fill_parent"/> try {
<Button Thread.sleep(100);
handler.sendEmptyMessage(count);
android:id="@+id/button_stop" count++;
}
android:text="Stop" catch (InterruptedException e) {
Log.e("ERROR", "Thread Interrupted");
android:onClick="onClick" } } } });
android:layout_weight="1" isRunning = true;
thread.start();
android:layout_height="wrap_content" }
public void stopThread() {
android:layout_width="fill_parent"/> isRunning = false;
}
</LinearLayout> }
17
</LinearLayout>
Відображення індикатора прогресу на Action Bar
При розробці дизайну програми іноді не потрібно відображати
на основному екрані індикатор, який займає багато місця. Якщо
потрібно, щоб індикатор не впадав у вічі та водночас
інформував користувача про хід виконання завдання, його краще
розмістити на Action Bar та звільнити місце на екрані для інших
елементів.
Для відображення індикатора на Action Bar в Activity викликається
метод requestWindowFeature() з прапором
FEATURE_INDETERMINATE_PROGRESS:

requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);

Цей спосіб дозволяє поставити індикатор на Action Bar, але не


відображає його. Щоб показати індикатор, треба запустити
завдання викликавши метод setProgressBarIndeterminateVisibility()
з параметром true. Після закінчення завдання, щоб закрити
індикатор, потрібно знову викликати цей метод, але з параметром
false. 18
Відображення індикатора прогресу на Action Bar
public class MainActivity extends Activity implements View.OnClickListener {
private TextView text;
private Thread background;
private boolean isRunning;
private static final int MAX_COUNT = 100;
private Handler handler = new Handler(new Handler.Callback() {
@Override public void startThread() {
public boolean handleMessage(Message msg) { // Показуємо індикатор
text.setText(String.format("End task #%d", msg.what)); setProgressBarIndeterminateVisibility(true);
if (msg.what == MAX_COUNT) { background = new Thread(new Runnable() {
// Завдання виконано, прибираємо індикатор public void run() {
setProgressBarIndeterminateVisibility(false); int count = 1;
} while (count <= MAX_COUNT && isRunning) {
return false; try {
} Thread.sleep(100);
}); handler.sendEmptyMessage(count);
@Override count++;
public void onCreate(Bundle savedInstanceState) { }
super.onCreate(savedInstanceState); catch (InterruptedException e) {
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); Log.e("ERROR", "Thread Interrupted");
setContentView(R.layout.activity_main); }
text = (TextView)findViewById(R.id.text); }
} }
@Override });
public void onClick(View v) { isRunning = true;
switch (v.getId()) { background.start();
case R.id.button_start: }
startThread(); public void stopThread() {
break; // Завдання зупинено, прибираємо індикатор
case R.id.button_stop: setProgressBarIndeterminateVisibility(false);
stopThread(); isRunning = false;
break; } 19
} }
}
Використання ProgressDialog
Індикатор прогресу можна також виводити у вигляді діалогу,
використовуючи клас ProgressDialog - це підклас AlertDialog,
який є діалоговим вікном з вбудованим індикатором прогресу. У
діалог також можна додати керуючі кнопки, наприклад для скасування
виконуваного завдання.
Для створення діалогу з індикатором прогресу необхідно ініціалізувати
об'єкт ProgressDialog викликом конструктора класу ProgressDialog(Context),
передавши як параметр контекст поточного Activity:
ProgressDialog progressDialog = New ProgressDialog(Activity_Name.this);
Далі треба встановити необхідні властивості діалогового вікна,
наприклад:
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setMessage("Loading...");
progressDialog.setCancelable(false);
Метод setProgressStyle() встановлює стиль діалогу. Стилі задаються
константами, визначеними у класі ProgressDialog:
 STYLE_HORIZONTAL – індикатор з горизонтальною шкалою;

 STYLE_SPINNER – індикатор із круговою шкалою. 20


Використання ProgressDialog
public class MainActivity extends Activity implements View.OnClickListener { isRunning = true;
private Thread thread; thread.start();
private boolean isRunning; openDialog();
private static final int MAX_COUNT = 100; }
private ProgressDialog dialog; private void openDialog() {
private Handler handler = new Handler(new Handler.Callback() { dialog = new ProgressDialog(this);
@Override dialog.setTitle("Please wait...");
public boolean handleMessage(Message msg) { dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setProgress(msg.what); //dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
dialog.setMessage(String.format("Run task #%d", msg.what)); dialog.setCancelable(true);
if (msg.what == MAX_COUNT) dialog.setOnCancelListener(new
dialog.dismiss(); DialogInterface.OnCancelListener(){
return false; public void onCancel(DialogInterface dialog) {
} isRunning = false;
}); Toast.makeText(MainActivity.this, "User close dialog",
@Override Toast.LENGTH_LONG).show();
public void onCreate(Bundle savedInstanceState) { }
super.onCreate(savedInstanceState); });
setContentView(R.layout.activity_main);
dialog.setOnDismissListener(new
}
@Override DialogInterface.OnDismissListener() {
public void onClick(View v) { public void onDismiss(DialogInterface dialog) {
startThread(); Toast.makeText(MainActivity.this,
} "Finished all task. Dialog dismissed",
public void startThread() { Toast.LENGTH_LONG).show();
thread = new Thread(new Runnable() { }
public void run() {
});
int count = 0;
while (count <= MAX_COUNT && isRunning) { dialog.show();
try { }
Thread.sleep(100); }
handler.sendEmptyMessage(count);
count++;
}
catch (InterruptedException e) { 21
Log.e("ERROR", "Thread Interrupted");
} } } });
Клас AsyncTask
У бібліотеці Android у пакеті android.os для виконання завдань у
фоновому режимі є клас AsyncTask. З його допомогою можна
значно спростити створення багатопоточного коду. На відміну від
використаних раніше Thread та Handler, цей клас є потокобезпечним і
дозволяє спростити створення потоків, інкапсулюючи в собі більшу
частину коду.
Клас AsyncTask є абстрактним, тому для роботи з ним у програмі треба
створити власний клас, похідний від AsyncTask, і в ньому перевизначити
потрібні нам методи. Крім того, при створенні свого класу ми повинні
визначити три параметри, вказавши їх типи у кутових дужках. Ці три
параметри будуть використовуватися під час виконання завдання:
 тип вхідного параметра;

 тип параметрів, які передаватимуться у головний потік під час


виконання завдання;
 тип результату, який буде передано до головного потоку після
закінчення виконання завдання.
Якщо один із параметрів (або всі) не буде використовуватися, тип цього
параметра вказується як Void:
22
public class CustomAsyncTask extends AsyncTask<Void, Void, Void>
Клас AsyncTask
З цих методів, оголошених у класі AsyncTask, обов'язково треба
реалізувати лише метод doInBackground(). У ньому має бути
реалізоване завдання: @Override
protected Void doInBackground(Void... params) {
// TODO Auto-generated method stub
return null;
}
Крім обов'язкового методу doInBackground(), у класі, похідному від
AsyncTask, також у більшості випадків бажано перевизначити ще два
методи:
 onPreExecute() - викликається перед запуском завдання, тобто перед
doInBackground(), із цього методу можливий доступ до головного потоку;
 onPostExecute() - викликається після закінчення завдання, тобто після
відпрацювання методу doInBackground(), з цього методу також
можливий доступ до головного потоку програми.
Щоб запустити фоновий потік, в основному потоці треба створити
екземпляр класу CustomAsyncTask і викликати його метод execute(). В
нього ми передаємо набір вхідних параметрів типу, який вказаний першим
у кутових дужках при описі AsyncTask (або нічого не передаємо, якщо вхідні
параметри не використовуються). 23
Клас AsyncTask
public class MainActivity extends Activity implements View.OnClickListener {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public void onClick(View arg0) {
CustomAsyncTask task = new CustomAsyncTask(this);
task.execute();
}
}

<?xml version="1.0" encoding="utf-8"?>


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:orientation="vertical">
<Button
android:id="@+id/button_start"
android:layout_height="wrap_content"
android:text="Start"
android:onClick="onClick"
android:layout_width="fill_parent"/> 24
</LinearLayout>
Клас AsyncTask
public class CustomAsyncTask extends AsyncTask<Void, Void, Void> {
private Context context;
private static final int MAX_COUNT = 100;
public CustomAsyncTask(Context con) {
context = con;
}
@Override
protected Void doInBackground(Void... params) {
int count = 0;
while (count < MAX_COUNT) {
try {
Thread.sleep(100);
count++;
}
catch (InterruptedException e) {
Log.e("ERROR", "Thread Interrupted");
}
}
return null;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
Toast.makeText(context, "onPreExecute", Toast.LENGTH_LONG).show();
}
@Override
protected void onPostExecute(Void result) {
25
super.onPostExecute(result);
Toast.makeText(context, "onPostExecute", Toast.LENGTH_LONG).show(); } }
Відображення діалогу
public class CustomAsyncTask extends AsyncTask<Void, Void, Void> {
private static final int MAX_COUNT = 100;
private Context context;
private ProgressDialog progressDialog;
public CustomAsyncTask(Context con) {
context = con;
}
@Override
protected Void doInBackground(Void... params) {
int count = 0;
while (count < MAX_COUNT) {
try {
Thread.sleep(100);
count++;
}
catch (InterruptedException e) {
Log.e("ERROR", "Thread Interrupted");
}
}
return null;
}
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
this.progressDialog.dismiss();
Toast.makeText(context, "onPostExecute", Toast.LENGTH_LONG).show();
}
@Override
protected void onPreExecute() {
super.onPreExecute();
progressDialog = new ProgressDialog(context);
progressDialog.setMessage("Downloading content, please wait...");
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
progressDialog.setIndeterminate(true);
progressDialog.setCancelable(false);
progressDialog.show(); 26
Toast.makeText(context, "onPreExecute", Toast.LENGTH_LONG).show();
} }
Вхідні параметри
Щоб розпочати виконання завдання, треба так само, як і в
попередній програмі, створити екземпляр класу
CustomAsyncTask та викликати його метод execute(). Але
тепер у нього ми передамо набір даних типу, який був
вказаний першим у реалізації класу CustomAsyncTask:
public class CustomAsyncTask extends AsyncTask<Integer, Void, Void>

Це оголошення означає, що ми тепер можемо передавати в


клас CustomAsyncTask як вхідні параметри набір
цілочисельних значень (одне або кілька). Відповідно, ці
параметри треба передавати у методі execute():
CustomAsyncTask task = New CustomAsyncTask();
task.execute (10, 20, 30);

27
Вхідні параметри
ПРИМІТКА!
Ми можемо задавати різні типи параметрів, але, на жаль, вони
мають бути однакового типу. Не можна, наприклад, використовувати
одночасно для вхідних параметрів Integer та String. Вхідні, проміжні і
вихідні параметри не повинні мати однаковий тип, наприклад, можуть
бути такими: <String, Integer, Boolean>.

Тобто якщо, у класі CustomAsyncTask ми маємо цей набір параметрів,


то їх необхідно правильно обробити. Метод doInBackground() тепер
повинен приймати вхідні параметри і виглядатиме так:
protected Void doInBackground(Integer... params)
У тілі методу набір вхідних параметрів обробляється як звичайний
масив відповідного типу:
int paramA = params[0];
int paramB = params[1];

28
Вхідні параметри
@Override
protected Void doInBackground(Integer... params) {
int start = params[0];
int end = params[1];
// Запускаємо цикл зі стартового значення,
// вказаного у параметрі start
int count = start;
// Відпрацьовуємо цикл до кроку, вказаного у параметрі end
while (count < end) {
try {
Thread.sleep(100);
count++;
}
catch (InterruptedException e) {
Log.e("ERROR", "Thread Interrupted");
}
}
return null;
}

@Override
public void onClick(View arg0) {
CustomAsyncTask task = new CustomAsyncTask(this);
29
task.execute(10,30);
}
Виведення ходу виконання завдання
Для виведення проміжних результатів в основний потік, як ви вже
зрозуміли, треба вказати тип другого параметра в кутових дужках,
наприклад, Integer, якщо ми збираємося відображати кількість
виконаних кроків завдання. Щоб передати хід виконання завдання, в тілі
методу doInBackground() потрібно викликати метод класу
publishProgress(Progress... values), якому передати набір параметрів
(один або кілька) для виведення в інтерфейс користувача.
Далі необхідно отримати ці параметри в інтерфейсі користувача і
відобразити їх. Для цього в нашому класі слід перевизначити метод
зворотного виклику onProgressUpdate(). Цей метод отримує на вхід
проміжні результати як масиву заданого типу і викликається системою
після кожного виклику методу publishProgress().
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
// Отримуємо проміжний результат
int progress = values[0];
}
Метод publishProgress() також можна викликати у методах
onPreExecute() та onPostExecute(). 30
Виведення ходу виконання завдання
public class CustomAsyncTask extends AsyncTask<Void, Integer, Void> {
private static final int MAX_COUNT = 100;
private Context context;
private ProgressDialog progressDialog;
public CustomAsyncTask(Context con) {
context = con;
} @Override
@Override protected void onProgressUpdate(Integer... values) {
protected Void doInBackground(Void... params) { super.onProgressUpdate(values);
int count = 0; progressDialog.setProgress(values[0]);
while (count < MAX_COUNT) { }
try { @Override
Thread.sleep(100); protected void onPostExecute(Void result) {
count++; super.onPostExecute(result);
publishProgress(count); this.progressDialog.dismiss();
} Toast.makeText(context, "onPostExecute",
catch (InterruptedException e) { Toast.LENGTH_LONG).show();
Log.e("ERROR", "Thread Interrupted"); }
} }
}
return null;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
progressDialog = new ProgressDialog(context);
progressDialog.setMessage("Downloading content, please wait...");
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setMax(100);
progressDialog.setProgress(0);
progressDialog.setIndeterminate(false);
progressDialog.setCancelable(false);
progressDialog.show(); 31
Toast.makeText(context, "onPreExecute", Toast.LENGTH_LONG).show();
}
Відображення ходу виконання
завдання в основному потоці
Клас AsyncTask легко пристосувати для завдань, які
передбачають не лише виведення результатів роботи у діалог, а й
безпосередньо у вікно Activity. Тобто ми можемо використовувати
шкалу прогресу та текстове поле, що знаходяться на Activity, та
оновлювати їх із нашого класу, похідного від AsyncTask.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/text"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_margin="5dp"
android:textStyle="bold"/>
<ProgressBar
android:id="@+id/progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/button_start"
android:layout_height="wrap_content"
android:text="Start"
android:onClick="onClick" 32
android:layout_width="fill_parent"/>
</LinearLayout>
Відображення ходу виконання
завдання в основному потоці
public class CustomAsyncTask extends AsyncTask<Void, Integer, Void> {
private static final int MAX_COUNT = 100;
private Context context;
private ProgressBar progress;
private TextView text;
public CustomAsyncTask(Context con, ProgressBar progress, TextView text) {
this.context = con;
this.progress = progress;
this.text = text;
}
@Override
protected Void doInBackground(Void... params) {
@Override
int count = 0;
protected void onProgressUpdate(Integer... values) {
while (count < MAX_COUNT) {
super.onProgressUpdate(values);
try {
progress.setProgress(values[0]);
Thread.sleep(100);
text.setText("Run task #" + values[0]);
count++;
}
publishProgress(count);
@Override
}
protected void onPostExecute(Void result) {
catch (InterruptedException e) {
Toast.makeText(context, "onPostExecute",
Log.e("ERROR", "Thread Interrupted");
Toast.LENGTH_LONG).show();
}
super.onPostExecute(result);
}
}
return null;
}
}
@Override
protected void onPreExecute() {
super.onPreExecute();
Toast.makeText(context, "onPostExecute", Toast.LENGTH_LONG).show(); 33
progress.setProgress(0);
}
Відображення ходу виконання
завдання в основному потоці
public class MainActivity extends Activity implements View.OnClickListener {
private static final int MAX_COUNT = 100;
private TextView text;
private CustomAsyncTask task;
private ProgressBar progress;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (TextView)findViewById(R.id.text);
progress = (ProgressBar)findViewById(R.id.progress);
}
@Override
public void onClick(View v) {
task = new CustomAsyncTask(this, progress, text);
task.execute();
}
}

34
Закриття завдання
Тепер розглянемо примусове закриття завдання у класі,
похідному від AsyncTask. Для припинення виконання
завдання у класі AsyncTask передбачено метод
cancel(boolean mayInterruptIfRunning). Бульовий параметр вказує,
чи можна переривати потік (true), що виконує цю задачу, або
необхідно зупинити її виконання (false).
Слід зазначити, що метод cancel() лише намагається скасувати
виконання завдання. Ця спроба буде невдалою, якщо завдання
вже завершено, було скасовано або не може бути скасовано з
якоїсь іншої (невідомої причини), тому не можна покладатися на
те, що виклик цього методу відразу припинить виконання
завдання.

35
Закриття завдання
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/text"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_margin="5dp"
android:textStyle="bold"/>
<ProgressBar
android:id="@+id/progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_margin="5dp">
<Button
android:id="@+id/button_start"
android:text="Start"
android:onClick="onClick"
android:layout_weight="1"
android:layout_height="wrap_content"
android:layout_width="fill_parent"/>
<Button
android:id="@+id/button_stop"
android:text="Stop"
android:onClick="onClick"
android:layout_weight="1"
android:layout_height="wrap_content"
android:layout_width="fill_parent"/>
</LinearLayout> 36
</LinearLayout>
Закриття завдання
public class MainActivity extends Activity implements View.OnClickListener {
private TextView text;
private CustomAsyncTask task;
private ProgressBar progress;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (TextView)findViewById(R.id.text);
progress = (ProgressBar)findViewById(R.id.progress);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button_start:
startTask();
break;
case R.id.button_stop:
stopTask();
break;
}
}
private void startTask() {
task = new CustomAsyncTask(this, progress, text);
task.execute();
}
private void stopTask() {
task.cancel(true);
37
}
}
Закриття завдання
public class CustomAsyncTask extends AsyncTask<Void, Integer, Void> {
private static final int MAX_COUNT = 100;
private Context context;
private ProgressBar progress;
private TextView text;
private int count;
public CustomAsyncTask(Context con, ProgressBar progress, TextView text) {
this.context = con;
this.progress = progress;
this.text = text;
}
@Override
protected Void doInBackground(Void... params) {
count = 0;
while (count < MAX_COUNT && !isCancelled()) {
try {
Thread.sleep(100); @Override
count++; protected void onProgressUpdate(Integer... values) {
publishProgress(count); super.onProgressUpdate(values);
} progress.setProgress(values[0]);
catch (InterruptedException e) { text.setText("Run task #" + values[0]);
Log.e("Thread Interrupted", e.getMessage()); }
} @Override
} protected void onCancelled() {
return null; super.onCancelled();
} Toast.makeText(context, "Cancel task, count=" + count,
@Override Toast.LENGTH_LONG).show();
protected void onPreExecute() { }
super.onPreExecute(); @Override
progress.setProgress(0); protected void onPostExecute(Void result) {
} super.onPostExecute(result); 38
}
}
Статус завдання
Часто в програмах потрібно запустити на виконання
фонове завдання, але немає потреби виводити дані про
його виконання в інтерфейс користувача. Однак, якщо є
потреба, можна дізнатися про статус виконання завдання.
Для цього використовують перелік AsynсTask.Status. Воно
визначає три стани завдання:
 PENDING - завдання ще не запущене;

 RUNNING - завдання виконується;

 FINISHED – завдання завершено.

39
Статус завдання <Button
android:id="@+id/button_status"
android:layout_height="wrap_content"
<?xml version="1.0" encoding="utf-8"?> android:text="Status"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:onClick="onClick"
android:layout_width="fill_parent" android:layout_weight="1"
android:layout_height="fill_parent" android:layout_width="fill_parent"/>
android:gravity="center" </LinearLayout>
android:orientation="vertical"> </LinearLayout>
<TextView
android:id="@+id/text"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_margin="5dp"
android:textStyle="bold"/>
<ProgressBar
android:id="@+id/progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_margin="5dp">
<Button
android:id="@+id/button_start"
android:layout_height="wrap_content"
android:text="Start"
android:onClick="onClick"
android:layout_weight="1"
android:layout_width="fill_parent"/>
<Button
android:id="@+id/button_stop"
android:layout_height="wrap_content"
android:text="Stop"
android:onClick="onClick"
android:layout_weight="1" 40
android:layout_width="fill_parent"/>
public class MainActivity extends Activity implements View.OnClickListener {
private TextView text;
private CustomAsyncTask task;
private ProgressBar progress; Статус завдання
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (TextView)findViewById(R.id.text);
progress = (ProgressBar)findViewById(R.id.progress);
task = new CustomAsyncTask(this, progress, text);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button_start:
startTask();
break;
case R.id.button_stop:
stopTask();
break;
case R.id.button_status:
getStatus();
break;
}
}
private void startTask() {
// Повторно створюємо об'єкт CustomAsyncTask
task = new CustomAsyncTask(this, progress, text);
task.execute();
}
private void stopTask() {
task.cancel(true);
}
private void getStatus() {
Toast.makeText(this, task.getStatus().toString(),
Toast.LENGTH_SHORT).show(); 41
}
}
Створення обробника події закінчення завдання
Є ще спосіб перехопити завершення завдання та дізнатися про
успішний чи невдалий результат виконання завдання — створити
власний обробник події. У класі, похідному від AsyncTask,
оголосити інтерфейс з одним методом, наприклад, так:
public interface OnDownloadCompletedListener {
void onDownloadTaskCompleted(boolean success);
}
Далі в класі CustomAsyncTask оголосити змінну типу
OnDownloadCompletedListener:
private OnDownloadCompletedListener listener;
і створити відкритий метод із вхідним параметром типу
OnDownloadCompletedListener,в якому цей параметр
присвоюватиметься змінною listener класу CustomAsyncTask:
public void setOnDownloadCompletedListener(
OnDownloadCompletedListener listener) {
this.listener = listener;
}

42
Створення обробника події закінчення завдання
Після відпрацювання завдання в тілі методу викликати метод
onDownloadTaskCompleted() і передати йому результат
виконання завдання:
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
listener.onDownloadTaskCompleted(result);
}
Щоб обробити цю подію у класі Activity, додамо реалізацію
інтерфейсу OnDownloadCompletedListener:

public class MainActivity extends Activity


implements CustomAsyncTask.OnDownloadCompletedListener
і реалізуємо метод нашого інтерфейсу, наприклад,
виводитимемо результат у текстове поле:
@Override
public void onDownloadTaskCompleted(boolean success) {
if (success)
text.setText("End task successfully");
else
text.setText("Error while running task");
} 43
Створення обробника події закінчення завдання
Останнє, що треба зробити, - це приєднати обробник до
створеного екземпляра, використовуючи метод
setOnDownloadCompletedListener():
CustomAsyncTask tаsk;
...
task = new CustomAsyncTask(this, progress, text);
task.setOnDownloadCompletedListener(this);
task.execute();

Використання таких обробників подій зручно при


виконанні складних завдань, коли при виконанні
завдання можливо багато варіантів успішного, частково
успішного або невдалого виконання завдання. Можна
створити гнучку систему обробки подій, що виникають
при виконанні завдання, та визначати свій набір станів
завдання.
44

You might also like