Урок 82. Handler. Пример с более содержательными сообщениями
В прошлых уроках мы использовали метод sendEmptyMessage. Этот метод сам создавал сообщение Message, заполнял его атрибут what и отправлял в очередь. Кроме what у сообщения есть еще атрибуты arg1 и arg2 типа int, и obj типа Object. В этом уроке мы сами будем создавать сообщение, заполнять атрибуты и отправлять.
Создадим приложение, которое будет подключаться к серверу, запрашивать кол-во файлов готовых для загрузки, эмулировать загрузку и отображать на экране ход действий, используя горизонтальный ProgressBar и TextView.
Project name: P0821_HandlerAdvMessage
Build Target: Android 2.3.3
Application name: HandlerAdvMessage
Package name: ru.startandroid.develop.p0821handleradvmessage
Create Activity: MainActivity
HandlerAdvMessage Connect
MainActivity.java:
package ru.startandroid.develop.p0821handleradvmessage; import java.util.Random; import java.util.concurrent.TimeUnit; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.view.View; import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; public class MainActivity extends Activity < final String LOG_TAG = "myLogs"; final int STATUS_NONE = 0; // нет подключения final int STATUS_CONNECTING = 1; // подключаемся final int STATUS_CONNECTED = 2; // подключено final int STATUS_DOWNLOAD_START = 3; // загрузка началась final int STATUS_DOWNLOAD_FILE = 4; // файл загружен final int STATUS_DOWNLOAD_END = 5; // загрузка закончена final int STATUS_DOWNLOAD_NONE = 6; // нет файлов для загрузки Handler h; TextView tvStatus; ProgressBar pbDownload; Button btnConnect; /** Called when the activity is first created. */ public void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setContentView(R.layout.main); tvStatus = (TextView) findViewById(R.id.tvStatus); pbDownload = (ProgressBar) findViewById(R.id.pbDownload); btnConnect = (Button) findViewById(R.id.btnConnect); h = new Handler() < public void handleMessage(android.os.Message msg) < switch (msg.what) < case STATUS_NONE: btnConnect.setEnabled(true); tvStatus.setText("Not connected"); pbDownload.setVisibility(View.GONE); break; case STATUS_CONNECTING: btnConnect.setEnabled(false); tvStatus.setText("Connecting"); break; case STATUS_CONNECTED: tvStatus.setText("Connected"); break; case STATUS_DOWNLOAD_START: tvStatus.setText("Start download " + msg.arg1 + " files"); pbDownload.setMax(msg.arg1); pbDownload.setProgress(0); pbDownload.setVisibility(View.VISIBLE); break; case STATUS_DOWNLOAD_FILE: tvStatus.setText("Downloading. Left " + msg.arg2 + " files"); pbDownload.setProgress(msg.arg1); saveFile((byte[]) msg.obj); break; case STATUS_DOWNLOAD_END: tvStatus.setText("Download complete!"); break; case STATUS_DOWNLOAD_NONE: tvStatus.setText("No files for download"); break; >>; >; h.sendEmptyMessage(STATUS_NONE); > public void onclick(View v) < Thread t = new Thread(new Runnable() < Message msg; byte[] file; Random rand = new Random(); public void run() < try < // устанавливаем подключение h.sendEmptyMessage(STATUS_CONNECTING); TimeUnit.SECONDS.sleep(1); // подключение установлено h.sendEmptyMessage(STATUS_CONNECTED); // определяем кол-во файлов TimeUnit.SECONDS.sleep(1); int filesCount = rand.nextInt(5); if (filesCount == 0) < // сообщаем, что файлов для загрузки нет h.sendEmptyMessage(STATUS_DOWNLOAD_NONE); // и отключаемся TimeUnit.MILLISECONDS.sleep(1500); h.sendEmptyMessage(STATUS_NONE); return; >// загрузка начинается // создаем сообщение, с информацией о количестве файлов msg = h.obtainMessage(STATUS_DOWNLOAD_START, filesCount, 0); // отправляем h.sendMessage(msg); for (int i = 1; i // загрузка завершена h.sendEmptyMessage(STATUS_DOWNLOAD_END); // отключаемся TimeUnit.MILLISECONDS.sleep(1500); h.sendEmptyMessage(STATUS_NONE); > catch (InterruptedException e) < e.printStackTrace(); >> >); t.start(); > byte[] downloadFile() throws InterruptedException < TimeUnit.SECONDS.sleep(2); return new byte[1024]; >void saveFile(byte[] file) < >>
В onCreate мы создаем Handler и в его методе обработки (handleMessage) прописываем всю логику изменения экрана в зависимости от приходящих сообщений. Не буду подробно это расписывать, там все просто – меняем текст, включаем/выключаем кнопку, показываем/скрываем ProgressBar, меняем значение ProgressBar. Из интересного здесь стоит отметить, что читаем мы на этот раз не только what, но и остальные атрибуты сообщения – arg1, arg2, obj. А как они заполняются, увидим далее.
В onclick создаем новый поток для загрузки файлов. Устанавливаем подключение, получаем кол-во готовых для загрузки файлов. Если файлов для загрузки нет, посылаем соответствующее сообщение в Handler и отключаемся. Если же файлы есть, мы создаем сообщение Message с помощью метода obtainMessage (int what, int arg1, int arg2). Он принимает на вход атрибуты what, arg1 и arg2. В what мы кладем статус, в arg1 — кол-во файлов, arg2 – не нужен, там просто ноль.
Далее начинаем загрузку. После загрузки каждого файла мы создаем сообщение Message c помощью метода obtainMessage (int what, int arg1, int arg2, Object obj), заполняем его атрибуты: what – статус, arg1 – порядковый номер файла, arg2 – кол-во оставшихся файлов, obj – файл. И отправляем.
По завершению загрузки отправляем соответствующее сообщение и отключаемся.
downloadFile – эмулирует загрузку файла. ждет две секунды и возвращает массив из 1024 байтов.
saveFile – метод сохранения файла на диск. Просто заглушка. Ничего не делает.
Все сохраняем и запускаем. Жмем Connect.
Далее, либо начинается загрузка
либо появляется сообщение, что файлов нет
Используя разные атрибуты кроме what, мы смогли передать в основной поток и использовать там более разнообразные данные.
Мы создаем сообщения с помощью разных реализаций метода obtainMessage. А почему бы не создавать напрямую объект Message с помощью его конструкторов? В принципе можно, но официальный хелп рекомендует пользоваться методами obtainMessage, потому что это эффективней и быстрее. В этом случае сообщение достается из глобального пула сообщений, а не создается с нуля.
Здесь вы можете посмотреть все реализации метода obtainMessage для формирования сообщений и использовать тот, который подходит для ситуации. Они различаются разными комбинациями входных параметров.
На следующем уроке:
— посылаем отложенные сообщения
— удаляем сообщения из очереди
— используем Handler.Callback для обработки сообщений
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
Урок 83. Handler. Отложенные сообщения, удаление из очереди, Handler.Callback
В прошлых уроках мы отправляли сообщения в очередь, а система сразу же доставала их и перенаправляла в Handler на обработку. Но мы можем настроить сообщение так, чтобы система отправило его на обработку не сразу, а с задержкой. Для этого используются методы sendEmptyMessageDelayed (если используете только what) и sendMessageDelayed (полное сообщение). В них мы можем указать паузу в миллисекундах. Система выждет эту паузу и только потом отправит сообщение в Handler.
Если вдруг поместили такое отложенное сообщение в очередь, а потом решили, что оно не должно уйти на обработку, то его можно из очереди удалить. Для этого используется метод removeMessages.
В прошлых уроках мы создавали свой Handler, и в его методе handleMessage кодили свой алгоритм обработки сообщений. Кроме этого способа Handler также может использовать для обработки сообщений объект, реализующий интерфейс Handler.Callback. У интерфейса всего один метод handleMessage – в нем и прописываем всю логику обработки сообщений. Я пока не встречал практической пользы от этой штуки, но все же разберемся, как ее можно использовать. Может когда и пригодится.
Project name: P0831_HandlerMessageManage
Build Target: Android 2.3.3
Application name: HandlerMessageManage
Package name: ru.startandroid.develop.p0831handlermessagemanage
Create Activity: MainActivity
strings.xml и main.xml не трогаем, они нам не нужны. Будем работать с логами.
Кодим MainActivity.java:
package ru.startandroid.develop.p0831handlermessagemanage; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Log; 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; >>; /** Called when the activity is first created. */ 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, но при этом он позволяет указать задержку в обработке сообщения. Т.е. сообщение будет извлечено из очереди и отправлено на обработку через указанное количество миллисекунд.
Итак, мы помещаем три сообщения:
1) what = 1, обработка через 1000 мс.
2) what = 2, обработка через 2000 мс.
3) 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()
Используем метод 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()
Будем помещать в очередь кучу сообщений. Из них несколько с 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. Они тоже позволяют указать задержку обработки. Но эта задержка будет отсчитана от времени последнего старта системы, а не от времени помещения в очередь. Если сообщение окажется просроченным на момент помещения в очередь, оно выполняется сразу.
На следующем уроке:
— работаем с Handler и Runnable
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
Класс Handler
Класс android.os.Handler является дальнейшим развитием потоков, упрощающий код. Handler может использоваться для планирования выполнения кода в некоторый момент в будущем. Также класс может использоваться для передачи кода, который должен выполняться в другом программном потоке.
Рассмотрим максимально простой пример для знакомства
private Handler mHandler; boolean gameOn; long startTime; @Override protected void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); startTime = System.currentTimeMillis(); mHandler = new Handler()< public void handleMessage(Message msg)< super.handleMessage(msg); if(gameOn) < long seconds = ((System.currentTimeMillis() - startTime)) / 1000; Log.i("info", "seconds text-warning">Периодическое выполнение задачи При сложных вычислениях может понадобиться очередь Runnable-объектов. Помещая объект в очередь, вы можете задать время его запуска. Для демонстрации использования обработчика потока напишем программу, запускающую фоновый процесс, который будет каждые 200 миллисекунд получать текущее время и обновлять текст. Нам понадобится кнопка Пуск и две текстовые метки, в которых будет отображаться время и количество нажатий кнопки:
package ru.alexanderklimov.handler; import android.os.Bundle; import android.os.Handler; import android.os.SystemClock; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.TextView; public class MainActivity extends AppCompatActivity < // считаем нажатия кнопки private int mButtonPressed = 0; // счетчик времени private long mTime = 0L; private TextView mCounterTextView; private TextView mTimeTextView; // обработчик потока - обновляет сведения о времени // Создаётся в основном UI-потоке private Handler mHandler = new Handler(); @Override protected void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTimeTextView = (TextView) findViewById(R.id.textViewTime); mCounterTextView = (TextView) findViewById(R.id.textViewCounter); if (mTime == 0L) < mTime = SystemClock.uptimeMillis(); mHandler.removeCallbacks(timeUpdaterRunnable); // Добавляем Runnable-объект timeUpdaterRunnable в очередь // сообщений, объект должен быть запущен после задержки в 100 мс mHandler.postDelayed(timeUpdaterRunnable, 100); >> // Описание Runnable-объекта private Runnable timeUpdaterRunnable = new Runnable() < public void run() < // вычисляем время final long start = mTime; long millis = SystemClock.uptimeMillis() - start; int second = (int) (millis / 1000); int min = second / 60; second = second % 60; // выводим время mTimeTextView.setText("" + min + ":" + String.format("%02d", second)); // повторяем через каждые 200 миллисекунд mHandler.postDelayed(this, 200); >>; @Override protected void onPause() < // Удаляем Runnable-объект для прекращения задачи mHandler.removeCallbacks(timeUpdaterRunnable); super.onPause(); >@Override protected void onResume() < super.onResume(); // Добавляем Runnable-объект mHandler.postDelayed(timeUpdaterRunnable, 100); >public void onClick(View v) < mCounterTextView.setText("" + ++mButtonPressed); >>
На экране будет отображаться время и одновременно мы можем нажимать на кнопку. Эти действия не мешают друг другу, так как работают в разных потоках.

Кроме метода postDelayed() вы можете использовать метод postAtTime():
postAtTime(Runnable r, long uptimeMillis)
В этом случае объект r добавляется в очередь сообщений, запуск объекта производится во время, заданное вторым параметром.
Самый простой способ помещения объекта в очередь - метод post(), когда указывается только помещаемый объект без указания времени выполнения объекта:
post(Runnable r)
Пример с индикатором прогресса
Всё, что вам нужно - создать экземпляр класса Handler в классе активности. Поток будет работать с объектом Handler, который в свою очередь, будет обновлять шкалу индикатора в основном потоке активности.
Чтобы послать сообщение в объект Handler, сначала необходимо вызвать метод obtainMessage(), чтобы извлечь объект Message из глобального пула сообщений.
Для вставки сообщения в очередь сообщений объекта Handler существует несколько методов:
- sendMessage() — помещает сообщение в очередь немедленно (в конец очереди)
- sendMessageAtFrontofQueue() — помещает сообщение в очередь немедленно и, кроме того, помещает это сообщение впереди очереди (по умолчанию оно ставится в конец очереди), таким образом ваше сообщение берет приоритет над всеми другими
- sendMessageAtTime() — помещает сообщение в очередь в установленное время в миллисекундах
- sendMessageDeiayed() — помещает сообщение в очередь после задержки, выраженной в миллисекундах
Чтобы обрабатывать эти сообщения, для объекта Handler необходимо реализовать метод обратного вызова handleMessage(), который будет вызываться каждым сообщением из очереди сообщения.
Для примера создадим приложение с ProgressBar, который будет отображать ход выполнения длительной задачи (это будет простой цикл с приостановкой потока на 1 секунду в каждой итерации цикла) и обновлять степень завершения этой задачи через объект Handler в классе активности.
package ru.alexanderklimov.progressbar; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.widget.ProgressBar; public class ProgressBarDemoActivity extends Activity < ProgressBar mProgressBar; int mProgress = 0; @Override public void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mProgressBar = (ProgressBar) findViewById(R.id.progressBarHorizontal); new Thread(myThread).start(); >private Runnable myThread = new Runnable() < @Override public void run() < while (mProgress < 100) < try < myHandler.sendMessage(myHandler.obtainMessage()); Thread.sleep(1000); >catch (Throwable t) < >> > Handler myHandler = new Handler() < @Override public void handleMessage(Message msg) < mProgress++; mProgressBar.setProgress(mProgress); >>; >; >

Splash-screen
Очень часто программисты используют Handler для реализации окна приветствия, которое автоматически закрывается и следом запускается основная активность игры или приложения.
public classSplashActivity extends Activity < @Override protected voidonCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setContentView(R.layout.activity_splash); newHandler().postDelayed(new Runnable() < @Override public void run() < startActivity(newIntent( SplashActivity.this, MainMenuActivity.class)); finish(); >>, 2000); > >
При подготовке материала использовались источники:
https://startandroid.ru/ru/uroki/vse-uroki-spiskom/145-urok-82-handler-primer-s-bolee-soderzhatelnymi-soobschenijami.html
https://startandroid.ru/ru/uroki/vse-uroki-spiskom/146-urok-83-handler-otlozhennye-soobschenija-udalenie-iz-ocheredi-handlercallback.html
https://developer.alexanderklimov.ru/android/theory/handler.php