Освой Java играючи

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

Шкодим

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

Таймер - классы Timer и TimerTask

Классы Timer и TimerTask из пакета java.util позволяют планировать запуск задания на определённое время в будущем. Вы можете создать поток, выполняющий в фоновом режиме и ожидающий заданное время. Когда время истечёт, задача, связанная с этим потоком, будет запущена. С помощью параметров можно запланировать задачу на повторяющий запуск либо на запуск по определённой дате. Вам не нужно создавать поток с помощью класса Thread, так как таймер упрощает эту задачу.

Учитывайте обстоятельство, что таймер выполняется в своём потоке и не должен задействовать UI-элементы, которые выполняются в своём потоке. Для решения этой проблемы можете использовать метод runOnUiThread() для обновления данных у компонентов.

Классы Timer и TimerTask работают в связке. Класс Timer используется для планирования выполнения задачи. Запланированная к выполнению задача должна быть экземпляром класса TimerTask. Вы сначала создаёте объект класса TimerTask, а затем планируете его запуск с помощью класса Timer.

Класс TimerTask реализует интерфейс Runnable и может быть использован для создания потока выполнения.

В классе TimerTask имеется абстрактный метод run(), который следует переопределить. Метод должен содержать исполняемый код.

Метод cancel() прерывает задание и возвращает значение true, если выполнение задания прервано.

Метод scheduleExecutionTime() возвращает время, на которое последний раз планировался запуск задания.

Как только задача создана, она планируется для выполнения объектом класса Timer.

Методы класса Timer:

  • void cancel() - прерывает поток таймера
  • int purge() - удаляет прерванные задания из очереди таймера
  • void schedule (TimerTask task, long delay) - задание task планируется к выполнению через задержку в миллисекундах, переданный в параметре delay
  • void schedule (TimerTask task, long delay, long period) - задание task планируется к выполнению через задержку в миллисекундах, переданный в параметре delay. Затем задание повторяется повторно периодически - каждые period миллисекунд
  • void schedule (TimerTask task, Date when) - задание task планируется на время, указанное в параметре when
  • void schedule(TimerTask task, Date when, long period) - задание task планируется на время, указанное в параметре when. Затем задание выполняется повторно периодически - каждые period миллисекунд
  • void scheduleAtFixedRate (TimerTask task, long delay, long period) - задание task планируется к выполнению через задержку в миллисекундах, переданный в параметре delay. Затем задание выполняется повторно периодически - каждые period миллисекунд. Время каждого повтора задаётся относительно первого запуска.
  • void scheduleAtFixedRate (TimerTask task, Date when, long period) - задание task планируется к выполнению на время, указанное в параметре when. Задание затем выполняется повторно периодически - каждые period миллисекунд. Время каждого повтора задаётся относительно первого запуска.

Между методами 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);
    }
}
Реклама