Handler. Відкладені повідомлення, видалення з черги, Handler.Callback
Ми відправляли повідомлення в чергу, а система відразу ж діставала їх і перенаправляла в Handler на обробку. Але ми можемо налаштувати повідомлення так, щоб система відправила його на обробку не відразу, а із затримкою. Для цього використовуються методи sendEmptyMessageDelayed (якщо використовуєте тільки what) і sendMessageDelayed (повне повідомлення). У них ми можемо вказати паузу в мілісекундах. Система вичекає цю паузу і тільки потім відправить повідомлення в Handler.
Якщо раптом помістили таке відкладене повідомлення в чергу, а потім вирішили, що воно не повинно піти на обробку, то його можна з черги видалити. Для цього використовується метод removeMessages.
Ми створювали свій Handler, і в його методі handleMessage кодили свій алгоритм обробки повідомлень. Крім цього способу Handler також може використовувати для обробки повідомлень об'єкт, що реалізує інтерфейс Handler.Callback. В інтерфейсу всього один метод handleMessage - у ньому і прописуємо всю логіку обробки повідомлень.
public class MainActivity extends Activity {
final String LOG_TAG = "myLogs";
Handler h;
Handler.Callback hc = new Handler.Callback() {
public boolean handleMessage(Message msg) {
Log.d(LOG_TAG, "what = " + msg.what);
return false;
}
};
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
h = new Handler(hc);
sendMessages();
}
void sendMessages() {
Log.d(LOG_TAG, "send messages");
h.sendEmptyMessageDelayed(1, 1000);
h.sendEmptyMessageDelayed(2, 2000);
h.sendEmptyMessageDelayed(3, 3000);
}
}
Ми створюємо об'єкт hc типу Handler.Callback. У нього є метод handleMessage, в якому ми будемо обробляти повідомлення. У нашому випадку просто читаємо атрибут what і виводимо значення в лог.
В onCreate створюємо handler, використовуючи конструктор Handler(Handler.Callback callback). На вхід передаємо створений раніше hc. І тепер Handler оброблятиме повідомлення не сам, а передоручить це об'єкту hc. Далі ми виконуємо метод sendMessages , який кладе три повідомлення в чергу повідомлень. Для цього використовується метод sendEmptyMessageDelayed. Це аналог знайомого нам методу sendEmptyMessage з минулого уроку. Він теж заповнює в повідомленні тільки атрибут what, але при цьому він дає змогу вказати затримку в обробці повідомлення. Тобто повідомлення буде витягнуто з черги і відправлено на обробку через зазначену кількість мілісекунд.
Отже, ми поміщаємо три повідомлення:
what = 1, обробка через 1000 мс.what = 2, обробка через 2000 мс.what = 3, обробка через 3000 мс.
Зауважу, що відлік затримки починається після поміщення в чергу, а не після обробки попереднього повідомлення. Тобто ці повідомлення по відношенню одне до одного спрацюють з інтервалом в одну секунду.
Усе збережемо і запустимо застосунок. У логах один за одним з'являтимуться записи:
10:21:07.759: D/myLogs(332): send messages
10:21:08.786: D/myLogs(332): what = 1
10:21:09.765: D/myLogs(332): what = 2
10:21:10.776: D/myLogs(332): what = 3
Зверніть увагу на час цих записів. Перший спрацьовує через 1000 мс після поміщення в чергу (send messages), другий - через дві секунди, третій - через три.
Тепер спробуємо видалити повідомлення з черги. Перепишемо метод sendMessages:
void sendMessages() {
h.sendEmptyMessageDelayed(1, 1000);
h.sendEmptyMessageDelayed(2, 2000);
h.sendEmptyMessageDelayed(3, 3000);
h.removeMessages(2);
}
Використовуємо метод removeMessages, у якому вказуємо значення атрибута what. Цей метод знаходить у черзі повідомлення з атрибутом what, що дорівнює 2, і видаляє його з черги.
Усе зберігаємо, запускаємо додаток. Дивимося лог:
10:24:49.916: D/myLogs(434): send messages
10:24:50.927: D/myLogs(434): what = 1
10:24:52.948: D/myLogs(434): what = 3
Як бачимо, повідомлення з what = 2 не спрацювало.
А якщо буде кілька повідомлень з однаковим значенням what? Система видалить перше-ліпше чи всі?
Перевіримо. Перепишемо sendMessages:
void sendMessages() {
Log.d(LOG_TAG, "send messages");
h.sendEmptyMessageDelayed(1, 1000);
h.sendEmptyMessageDelayed(2, 2000);
h.sendEmptyMessageDelayed(3, 3000);
h.sendEmptyMessageDelayed(2, 4000);
h.sendEmptyMessageDelayed(5, 5000);
h.sendEmptyMessageDelayed(2, 6000);
h.sendEmptyMessageDelayed(7, 7000);
h.removeMessages(2);
}
Будемо поміщати в чергу купу повідомлень. З них кілька з what = 2. Перевіримо, які видалить система.
Запускаємо додаток і дивимося лог:
10:29:23.297: D/myLogs(467): send messages
10:29:24.372: D/myLogs(467): what = 1
10:29:26.307: D/myLogs(467): what = 3
10:29:28.364: D/myLogs(467): what = 5
10:29:30.332: D/myLogs(467): what = 7
Усі повідомлення з what = 2 було видалено. Не забувайте це. А то захочете видалити одне останнє повідомлення, а система знайде всі відповідні, які очікують на обробку, і знесе їх.
У методу removeMessages є ще реалізація з використанням obj. Тут усе так само, тільки система шукає для видалення з черги повідомлення із зазначеними атрибутами what і obj.
Якщо хочете запланувати повноцінне повідомлення, а не просто what, то використовуйте метод sendMessageDelayed - на вхід даєте повідомлення і вказуєте затримку обробки.
Є ще методи sendEmptyMessageAtTime і sendMessageAtTime. Вони теж дають змогу вказати затримку обробки. Але ця затримка буде відрахована від часу останнього старту системи, а не від часу поміщення в чергу. Якщо повідомлення виявиться простроченим на момент поміщення в чергу, воно виконується відразу.
Обробка Runnable
Крім обробки повідомлень, ми можемо попросити Handler виконати шматок коду - Runnable. Ми працювали з повідомленнями, які містили атрибути. Ми їх обробляли в Handler і залежно від значень атрибутів виконували ті чи інші дії. Runnable же - це шматок коду, який ми надішлемо замість атрибутів повідомлення, і він буде виконаний у потоці, з яким працює Handler. Нам уже нічого не треба обробляти.
Для відправлення коду в роботу використовується метод post. Як і повідомлення, Runnable може бути виконаний із затримкою (postDelayed), і може бути вилучений з черги (removeCallbacks). Напишемо додаток, який продемонструє всі ці можливості.
strings.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">HandlerRunnable</string>
<string name="info">Подробно</string>
</resources>
main.xml:
<?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:orientation="vertical">
<ProgressBar
android:id="@+id/pbCount"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp">
</ProgressBar>
<CheckBox
android:id="@+id/chbInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/info">
</CheckBox>
<TextView
android:id="@+id/tvInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:visibility="gone">
</TextView>
</LinearLayout>
ProgressBar, що відображає поточний прогрес. CheckBox, який вмикатиме відображення додаткової інформації в TextView.
MainActivity.java:
public class MainActivity extends Activity {
ProgressBar pbCount;
TextView tvInfo;
CheckBox chbInfo;
int cnt;
final String LOG_TAG = "myLogs";
final int max = 100;
Handler h;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
h = new Handler();
pbCount = (ProgressBar) findViewById(R.id.pbCount);
pbCount.setMax(max);
pbCount.setProgress(0);
tvInfo = (TextView) findViewById(R.id.tvInfo);
chbInfo = (CheckBox) findViewById(R.id.chbInfo);
chbInfo.setOnCheckedChangeListener(new OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
if (isChecked) {
tvInfo.setVisibility(View.VISIBLE);
// показуємо інформацію
h.post(showInfo);
} else {
tvInfo.setVisibility(View.GONE);
// скасовуємо показ інформації
h.removeCallbacks(showInfo);
}
}
});
Thread t = new Thread(new Runnable() {
public void run() {
try {
for (cnt = 1; cnt < max; cnt++) {
TimeUnit.MILLISECONDS.sleep(100);
// оновлюємо ProgressBar
h.post(updateProgress);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
// оновлення ProgressBar
Runnable updateProgress = new Runnable() {
public void run() {
pbCount.setProgress(cnt);
}
};
// показ інформації
Runnable showInfo = new Runnable() {
public void run() {
Log.d(LOG_TAG, "showInfo");
tvInfo.setText("Count = " + cnt);
// планує сам себе через 1000 мсек
h.postDelayed(showInfo, 1000);
}
};
}
В onCreate ми прописуємо обробник для CheckBox. У разі ввімкнення прапорця відображається TextView і в роботу відправляється завдання showInfo. У разі вимкнення прапорця - завдання showInfo видаляється з черги.
Далі в новому потоці емулюємо будь-яку дію - запускаємо лічильник із паузами. У кожній ітерації циклу відправляємо в роботу завдання updateProgress, яке оновлює ProgressBar.
updateProgress- код, який оновлює значенняProgressBar.showInfo- код, який оновлюєTextViewі сам себе планує на виконання через1000мсек. Тобто ми вмикаємоCheckBox,showInfoспрацьовує перший раз і саме себе планує на наступний раз. Тобто цей код лежить у черзі повідомлень, обробляється і знову кладе себе туди. Так триває, поки ми явно його не видалимо з черги (removeCallbacks), вимкнувшиCheckBox.
Будемо виводити що-небудь у лог із showInfo, щоб побачити, коли він працює, а коли ні.
Ще кілька способів виконання коду в UI-потоці
Ми детально розглянули Handler і побачили, що він вміє. Головна його перевага - це вміння виконувати код в UI-потоці. Існує ще пара способів виконувати Runnable в UI-потоці. Це методи:
Activity.runOnUiThread(Runnable)View.post(Runnable)View.postDelayed(Runnable, long)
Перші два схожі і відправляють Runnable на негайну обробку. А третій метод дозволяє вказати затримку виконання Runnable.
main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/llMain"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tvInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="">
</TextView>
</LinearLayout>
TextView, яке будемо оновлювати з нового потоку.
public class MainActivity extends Activity {
final String LOG_TAG = "myLogs";
TextView tvInfo;
/** Called when the activity is first created. */
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
tvInfo = (TextView) findViewById(R.id.tvInfo);
Thread t = new Thread(new Runnable() {
public void run() {
try {
TimeUnit.SECONDS.sleep(2);
runOnUiThread(runn1);
TimeUnit.SECONDS.sleep(1);
tvInfo.postDelayed(runn3, 2000);
tvInfo.post(runn2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
Runnable runn1 = new Runnable() {
public void run() {
tvInfo.setText("runn1");
}
};
Runnable runn2 = new Runnable() {
public void run() {
tvInfo.setText("runn2");
}
};
Runnable runn3 = new Runnable() {
public void run() {
tvInfo.setText("runn3");
}
};
}
В onCreate створюємо новий потік. У ньому ми через паузи виконуємо runn1 і runn2, і плануємо runn3 із затримкою у 2000 мсек, використовуючи вищезгадані методи.
runn1, runn2 і runn3 - це просто Runnable, які оновлюють текст у TextView. Вони повинні бути виконані в UI-потоці.
Тим самим, якщо ваші алгоритми не особливо складні, можна використовувати ці методи для виконання коду в UI-потоці. Якщо ж потрібні навороти й алгоритм досить складний, то використовуємо Handler.
Доповнення до AsyncTask
Ми вводили їх в окремий потік і використовували Handler для зворотного зв'язку та оновлення екрана. Творці Android вирішили, що ці механізми варто виділити в окремий клас - AsyncTask. Тобто його мета - це виконання важких завдань і передача в UI-потік результатів роботи. Але при цьому нам не треба замислюватися про створення Handler і нового потоку.`