Освой Java играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
Классы Timer и TimerTask из пакета java.util позволяют планировать запуск задания на определённое время в будущем. Вы можете создать поток, выполняющий в фоновом режиме и ожидающий заданное время. Когда время истечёт, задача, связанная с этим потоком, будет запущена. С помощью параметров можно запланировать задачу на повторяющий запуск либо на запуск по определённой дате. Вам не нужно создавать поток с помощью класса Thread, так как таймер упрощает эту задачу.
Учитывайте обстоятельство, что таймер выполняется в своём потоке и не должен задействовать UI-элементы, которые выполняются в своём потоке. Для решения этой проблемы можете использовать метод runOnUiThread() для обновления данных у компонентов.
Классы Timer и TimerTask работают в связке. Класс Timer используется для планирования выполнения задачи. Запланированная к выполнению задача должна быть экземпляром класса TimerTask. Вы сначала создаёте объект класса TimerTask, а затем планируете его запуск с помощью класса Timer.
Класс TimerTask реализует интерфейс Runnable и может быть использован для создания потока выполнения.
В классе TimerTask имеется абстрактный метод run(), который следует переопределить. Метод должен содержать исполняемый код.
Метод cancel() прерывает задание и возвращает значение true, если выполнение задания прервано.
Метод scheduleExecutionTime() возвращает время, на которое последний раз планировался запуск задания.
Как только задача создана, она планируется для выполнения объектом класса Timer.
Методы класса Timer:
Между методами schedule() и scheduleAtFixedRate() есть небольшая разница, которая заключается в разном поведении, которое зависит от стартовой точки запуска. Так второй метод работает как startTime + iterationNumber * delayTime и помнит время запуска. А обычный метод schedule() помнит последнее время выполнения и работает по формуле lastExecutionTime + delayTime. Для быстрых операций это не сильно отличается, а при ресурсоёмких задач разница будет заметна, например, при работе сборщика мусора приложение может притормозить и следующая задача может запуститься чуть позже.
Как только объект класса Timer создан, запуск планируется вызовом его метода schedule() и его родственника (см. выше).
Если вам нужно периодически вызывать какое-нибудь событие, то воспользуйтесь следующим примером:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
Timer myTimer;
myTimer = new Timer();
myTimer.schedule(new TimerTask() {
public void run() {
timerTick();
}
}, 0, 5000); // каждые 5 секунд
}
private void timerTick() {
this.runOnUiThread(doTask);
}
private Runnable doTask = new Runnable() {
public void run() {
Toast toast = Toast.makeText(getApplicationContext(), "Мяу!",
Toast.LENGTH_SHORT);
toast.show();
}
};
Через каждые пять секунд будет появляться всплывающее сообщение.
Напишем другой пример. Подготовим разметку.
<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<CheckBox
android:id="@+id/checkBoxSingleShot"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Single Shot" />
<Button
android:id="@+id/buttonStart"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start" />
<Button
android:id="@+id/buttonCancel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Cancel" />
<TextView
android:id="@+id/textViewCounter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="24sp"
android:textStyle="bold" />
</LinearLayout>
Будем использовать два варианта таймера - одиночное и периодическое срабатывание.
package ru.alexanderklimov.timer;
// Если этот код работает, его написал Александр Климов,
// а если нет, то не знаю, кто его писал.
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.TextView;
public class MainActivity extends Activity {
private CheckBox mSingleShotCheckBox;
private Button mStartButton, mCancelButton;
private TextView mCounterTextView;
private Timer mTimer;
private MyTimerTask mMyTimerTask;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mSingleShotCheckBox = findViewById(R.id.checkBoxSingleShot);
mStartButton = findViewById(R.id.buttonStart);
mCancelButton = findViewById(R.id.buttonCancel);
mCounterTextView = findViewById(R.id.textViewCounter);
mStartButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
if (mTimer != null) {
mTimer.cancel();
}
// re-schedule timer here
// otherwise, IllegalStateException of
// "TimerTask is scheduled already"
// will be thrown
mTimer = new Timer();
mMyTimerTask = new MyTimerTask();
if (mSingleShotCheckBox.isChecked()) {
// singleshot delay 1000 ms
mTimer.schedule(mMyTimerTask, 1000);
} else {
// delay 1000ms, repeat in 5000ms
mTimer.schedule(mMyTimerTask, 1000, 5000);
}
}
});
mCancelButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mTimer != null) {
mTimer.cancel();
mTimer = null;
}
}
});
}
class MyTimerTask extends TimerTask {
@Override
public void run() {
Calendar calendar = Calendar.getInstance();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(
"dd:MMMM:yyyy HH:mm:ss a", Locale.getDefault());
final String strDate = simpleDateFormat.format(calendar.getTime());
runOnUiThread(new Runnable() {
@Override
public void run() {
mCounterTextView.setText(strDate);
}
});
}
}
}
Допустим нам нужны ежедневные показания термометра. Но мы не можем ждать весь день при написании программ. Поэтому мы можем случайным образом создавать показания с небольшим интервалом и проверить поведение приложения.
Для удобства создадим отдельный класс-утилиту.
package ru.alexanderklimov.supercat;
import java.util.Random;
class Utils {
private static final Random RANDOM = new Random();
static int randInt(int min, int max) {
return RANDOM.nextInt((max - min) + 1) + min;
}
}
Создадим в классе активности метод для генерации значений и вызовем в onCreate().
package ru.alexanderklimov.supercat;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import java.util.Timer;
import java.util.TimerTask;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setValues();
}
private void setValues() {
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
final float value = Utils.randInt(-10, 35);
runOnUiThread(new Runnable() {
@Override
public void run() {
Log.i("Info", "Value: " + value);
}
});
}
}, 0, 3500);
}
}