Освой программирование играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
Класс 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 = " + seconds);
}
mHandler.sendEmptyMessageDelayed(0, 1000);
}
};
gameOn = true;
mHandler.sendEmptyMessage(0);
}
Запустите пример на эмуляторе и через некоторое время закройте его через кнопку Назад или Домой. При этом смотрите на логи на вкладе Android Monitor. Вы увидите, что приложение по-прежнему печатает текст типа после секундной задержки после запуска.
.../I/info: seconds = 45
.../I/info: seconds = 46
.../I/info: seconds = 47
Разберёмся, что происходит и как следует читать код.
Мы объявили новый объект Handler и булевую переменную, которая по умолчанию имеет значение false - по ней будем отслеживать состояние приложения. В третьей переменной мы сохраним текущее время в момент запуска.
Приложение запустилось, сразу же запоминаем время через currentTimeMillis() - это опорная точка отсчёта.
Основная логика заключена в условии if - тут пишется то, что мы хотим поместить в новый поток. Не страшно, если не понятно, как это всё работает. Просто уясните принцип. Мы иницилизируем объект mHandler и сразу начинаем его настраивать. Переопредляем его главный метод handleMessage() и вызываем метод суперкласса. Это ничем не отличается от вызова onCreate() активности, мы не раз такое делали.
Блок if позволяет управлять кодом для потока. Сам запуск мы сделаем позже. А в самом блоке мы снова получаем текущее время и сравниваем с самой первым временем, полученным во время запуска. Для удобства вычисления идут в секундах. Результат выводится в лог. Метод sendEmptyMessageDelayed() сообщает системе, что мы хотим повторять код в handleMessage() раз в секунду.
После инициализации и настройки mHandler мы присваиваем значение true переменной gameOn, показывая готовность к запуску кода из блока if.
Последняя строка sendEmptyMessage() запускает поток.
Можно использовать более сложные приёмы запуска потока, но пока этого достаточно для понимания.
При сложных вычислениях может понадобиться очередь 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 существует несколько методов:
Чтобы обрабатывать эти сообщения, для объекта Handler необходимо реализовать метод обратного вызова handleMessage(), который будет вызываться каждым сообщением из очереди сообщения.
Для примера создадим приложение с ProgressBar, который будет отображать ход выполнения длительной задачи (это будет простой цикл с приостановкой потока на 1 секунду в каждой итерации цикла) и обновлять степень завершения этой задачи через объект Handler в классе активности.
<?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/progressBar1"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ProgressBar
android:id="@+id/progressBar2"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ProgressBar
android:id="@+id/progressBarHorizontal"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:max="100" />
</LinearLayout>
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);
}
};
};
}
Очень часто программисты используют 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);
}
}
Автоматическое скрытие панели (Handler, Runnable)