Освой программирование играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

Класс Handler

Класс android.os.Handler является дальнейшим развитием потоков, упрощающий код. 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);
    }
}

На экране будет отображаться время и одновременно мы можем нажимать на кнопку. Эти действия не мешают друг другу, так как работают в разных потоках.

Handler and Runnable

Кроме метода 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 в классе активности.


<?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);
			}
		};
	};
}

ProgressBar

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);
    }
}

Примеры

Автоматическое скрытие панели (Handler, Runnable)

Реклама