Реализация таймера.

Ответить
brucemax
Сообщения: 117
Зарегистрирован: 01 апр 2012, 16:09
Откуда: Минск
Контактная информация:

Реализация таймера.

Сообщение brucemax » 26 ноя 2012, 16:42

Задача такова. Приложение для отсчёта раундов. Использую CountDownTimer.. а именно несколько экземпляров этого класса. Схема такова, что методе onFinish() одного, запускается другой.. и так далее.. до срабатывания некоторого условия. В методах onTick() выводиться оставшееся время в textview. Вопрос в следующем как организовать паузу.. ?? Очень хотелось бы чтоб в этом таймере метод pause() был..=) В голову приходит вариант сделать в новом потоке и тормозить сам поток.. но может можно иначе.. просто я в программировании не гуру.. вот примерно, что сейчас есть:

Код: Выделить всё

public class MainActivity extends Activity {
	TextView tvMainTime;
	TextView tvCurRound;
	Button btnFight;
	CountDownTimer roundTimer;
	CountDownTimer restTimer;
	SimpleDateFormat sdf;  // формат времени
	int currentRound;  // для подсчёта раундов	
	SharedPreferences shPref;

       ......
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
         ....       
 
        tvMainTime = (TextView) findViewById(R.id.tvMainTime);
        btnFight = (Button) findViewById(R.id.btnFight); 
        shPref = PreferenceManager.getDefaultSharedPreferences(this);        
        sdf = new SimpleDateFormat("mm:ss");
        
    }
    
	public void onClickBtnFight(View v) {
         // ТОDO здесь будет проверка запущен ли таймер и в соответствии с этим
         // запуск сначала или остановка или продолжение после паузы
			StartFight(
					Integer.valueOf(shPref.getString("rounds", "3")),
					myStrToInt(shPref.getString("timeRound", "3:00")),
					myStrToInt(shPref.getString("timeRest", "1:00")));
	}
		
	// Запуск таймера с исзодными параметрами
	void StartFight(final int iNumberRounds, int iTimeRound, int iTimeRest) {
		 ....
		currentRound = 1;
	
		roundTimer = new CountDownTimer(iTimeRound*1000, 200) {			
			@Override
			public void onTick(long millisUntilFinished) {				
				tvMainTime.setText(sdf.format(new Date(millisUntilFinished)));				
			}		
			@Override
			public void onFinish() {
				Log.d(LOG_TAG, " Таймер раунда закончен, инкремент текущего раунда");
				playSound(soundIdGong); // Конец раунда
				if(currentRound == iNumberRounds) {
					Log.d(LOG_TAG, "Бой закончен!");
					ResetTimer();
				} else {
				currentRound++;
				Log.d(LOG_TAG, "Старт таймера перерыва ");
				restTimer.start();
				}
			}
		}.start();
		
		restTimer = new CountDownTimer(iTimeRest*1000, 200) {
			
			@Override
			public void onTick(long millisUntilFinished) {
				tvMainTime.setText(sdf.format(new Date(millisUntilFinished)));					
			}
			
			@Override
			public void onFinish() {
				Log.d(LOG_TAG, "Таймер перерыва завершён и стартует таймера раунда ");
				roundTimer.start();
				if(m_bTimerWarn) warnTimer.start(); // старт предупредительного таймера если он используется
				tvCurRound.setText("Round "+String.valueOf(currentRound)+"/"+shPref.getString("rounds", "3"));
				playSound(soundIdGong); // Начало раунда				
			}
		};
		
	} // startFight
	
	public void ResetTimer() {
		if (roundTimer!=null) roundTimer.cancel();
    	        if (prepTimer!=null) prepTimer.cancel();
    	        if (restTimer!=null) restTimer.cancel();
		tvCurRound.setText("Round "+"1"+"/"+shPref.getString("rounds", "3"));
		tvMainTime.setText(shPref.getString("timeRound", "3:00"));
	
	}

        .......
	
}
В итоге кнопка будет работать как старт/пауза.

Аватара пользователя
damager82
Администратор
Сообщения: 1383
Зарегистрирован: 07 янв 2012, 11:32
Контактная информация:

Re: Реализация таймера.

Сообщение damager82 » 27 ноя 2012, 15:32

Тормозить поток - это sleep имеете ввиду? Судя по исходникам CountDownTimer это не сработает.
Зато в этих исходниках все достаточно несложно, если знаете Handler. Вполне реально дописать туда свои методы pause и resume и получить свой CountDownTimer с блэкджеком и паузой )
Добро пожаловать на форум сайта StartAndroid
ИзображениеИзображение

AndreyI
Сообщения: 372
Зарегистрирован: 14 май 2012, 16:18

Re: Реализация таймера.

Сообщение AndreyI » 28 ноя 2012, 06:20

damager82 писал(а):Тормозить поток - это sleep имеете ввиду? Судя по исходникам CountDownTimer это не сработает.
Зато в этих исходниках все достаточно несложно, если знаете Handler. Вполне реально дописать туда свои методы pause и resume и получить свой CountDownTimer с блэкджеком и паузой )
Только не используйте стандартных pause/resume из класса Object их не советуют использовать из-за опасности получить взаимоблокировку (deadlock), когда два потока ждут монитора друг у друга и оба останавливаются навечно :)
Хотя соблазн использовать эти методы очень велик, уж больно код получается простым, а при использовании wait/notify нужно писать кучу дополнительного кода с блоками синхронизации. Но работает надежней.
Вообще параллелизм (concurrency) очень важная тема в Java (а значит и в Android тоже), к тому же очень непростая для понимания тема, но разобраться с ней обязательно нужно.

Аватара пользователя
rezak90
Сообщения: 3422
Зарегистрирован: 26 июн 2012, 13:22
Откуда: UA
Контактная информация:

Re: Реализация таймера.

Сообщение rezak90 » 28 ноя 2012, 09:37

wait/notify, pause, resume и т.д. это как мне кажется в андроиде излишне, так как задача стоит не останавливать ui поток, а любой из этих методов остановит, разве что делать поток для управления остальными потоками но это маразм. В андроиде есть пулы, есть дажвавские синхронизации, есть в конце концов хендлеры.
R.id.team
Политика на форуме запрещена

Аватара пользователя
damager82
Администратор
Сообщения: 1383
Зарегистрирован: 07 янв 2012, 11:32
Контактная информация:

Re: Реализация таймера.

Сообщение damager82 » 28 ноя 2012, 10:00

На всякий случай уточняю, что я имел ввиду вовсе не работу с потоками, а управление Handler-ом и парой переменных, отвечающих за время в CountDownTimer. Там все проще.
Добро пожаловать на форум сайта StartAndroid
ИзображениеИзображение

brucemax
Сообщения: 117
Зарегистрирован: 01 апр 2012, 16:09
Откуда: Минск
Контактная информация:

Re: Реализация таймера.

Сообщение brucemax » 28 ноя 2012, 10:59

damager82 писал(а):На всякий случай уточняю, что я имел ввиду вовсе не работу с потоками, а управление Handler-ом и парой переменных, отвечающих за время в CountDownTimer. Там все проще.
Мне кажется я Вас правильно понял. Вот посмотрите пожалуйста, что у меня вышло. Добавил одну переменную, которая запоминает оставшееся время и два метода.. а в них проверки на наличие сообщений в очереди, чтобы когда не надо методы не срабатывали:

Код: Выделить всё

public abstract class MyCountDownTimer {

    private long mMillisInFuture;
    private long mCountdownInterval;
    private long mStopTimeInFuture;
    private long mValuePause; // сколько не досчитал таймер

    public MyCountDownTimer(long millisInFuture, long countDownInterval) {
        mMillisInFuture = millisInFuture;
        mCountdownInterval = countDownInterval;
    }
    
	public synchronized final void pause() {
		if(!mHandler.hasMessages(MSG)) return;
		mValuePause = mStopTimeInFuture - SystemClock.elapsedRealtime(); // сколько не досчитал таймер
		mHandler.removeMessages(MSG);
	}

	public synchronized final void resume() {
		if(mHandler.hasMessages(MSG)) return;
		mStopTimeInFuture = SystemClock.elapsedRealtime() + mValuePause;
		mHandler.sendMessage(mHandler.obtainMessage(MSG));
	}

    public final void cancel() {
        mHandler.removeMessages(MSG);
    }

    public synchronized final MyCountDownTimer start() {
        if (mMillisInFuture <= 0) {
            onFinish();
            return this;
        }
        mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture;
        mHandler.sendMessage(mHandler.obtainMessage(MSG));
        return this;
    }

    public abstract void onTick(long millisUntilFinished);
    public abstract void onFinish();
    private static final int MSG = 1;

    // handles counting down
    private Handler mHandler = new Handler() {

        @Override
        public void handleMessage(Message msg) {

            synchronized (MyCountDownTimer.this) {
                final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime(); // сколько осталось

                if (millisLeft <= 0) {
                    onFinish();
                } else if (millisLeft < mCountdownInterval) {  // если оставшееся время даже меньше чем время для onTick
                    // no tick, just delay until done		      то заходим сюда через оставшееся время и попадём в onFinish
                    sendMessageDelayed(obtainMessage(MSG), millisLeft);
                } else {
                    long lastTickStart = SystemClock.elapsedRealtime(); // сюда попадаем, если оставшееся время >= onTick
                    onTick(millisLeft);

                    // take into account user's onTick taking time to execute
                    long delay = lastTickStart + mCountdownInterval - SystemClock.elapsedRealtime(); // корректировка до следующего тика

                    // special case: user's onTick took more than interval to
                    // complete, skip to next interval
                    while (delay < 0) delay += mCountdownInterval;

                    sendMessageDelayed(obtainMessage(MSG), delay); 
                }
            }
        }
    };
}
Проверил.. пока работает=) И вопрос.. для чего они в методе start() текущий объект возвращают?

О! Ещё возникла необходимость проверять ведёт ли отсчёт таймер в данное время. В связи с этим добавлю метод:

Код: Выделить всё

public synchronized final boolean isActive() {
		if(mHandler.hasMessages(MSG)) return true;
                return false;
	}
Вроде логично..
Последний раз редактировалось brucemax 28 ноя 2012, 13:07, всего редактировалось 2 раза.

brucemax
Сообщения: 117
Зарегистрирован: 01 апр 2012, 16:09
Откуда: Минск
Контактная информация:

Re: Реализация таймера.

Сообщение brucemax » 28 ноя 2012, 11:19

AndreyI писал(а): Только не используйте стандартных pause/resume из класса Object их не советуют использовать из-за опасности получить взаимоблокировку (deadlock), когда два потока ждут монитора друг у друга и оба останавливаются навечно :)
Хотя соблазн использовать эти методы очень велик, уж больно код получается простым.
Видимо из-за отсутствия опыта у меня никакого соблазна не возникло..
А можете своими словами объяснить, что значит ждать монитора.. А то статью на английском читал, там они упоминались и я не чего не понял.. В голову приходит только аналогия с семафорами.. :?:

AndreyI
Сообщения: 372
Зарегистрирован: 14 май 2012, 16:18

Re: Реализация таймера.

Сообщение AndreyI » 28 ноя 2012, 12:02

У всех объектов для синхронизации существует некая защелка, если какой-то поток получает синхронизированный доступ к объекту, то он закрывает доступ других потоков для пользования этим объектом (еще говорят что он получил монитор объекта или вошел в монитор), как только поток выходит из блока синхронизации или из синхронизированного метода он эту защелку снимает и освобождает доступ к объекту для других потоков, другие потоки, если им нужен доступ к объекту смиренно ждут пока он освободится (снимется защелка) при этом их выполнение останавливается, как только объект освобождается, поток входит в его монитор и также закрывает за собой защелку.

brucemax
Сообщения: 117
Зарегистрирован: 01 апр 2012, 16:09
Откуда: Минск
Контактная информация:

Re: Реализация таймера.

Сообщение brucemax » 28 ноя 2012, 12:26

AndreyI писал(а):У всех объектов для синхронизации существует некая защелка, если какой-то поток получает синхронизированный доступ к объекту, то он закрывает доступ других потоков для пользования этим объектом (еще говорят что он получил монитор объекта или вошел в монитор), как только поток выходит из блока синхронизации или из синхронизированного метода он эту защелку снимает и освобождает доступ к объекту для других потоков, другие потоки, если им нужен доступ к объекту смиренно ждут пока он освободится (снимется защелка) при этом их выполнение останавливается, как только объект освобождается, поток входит в его монитор и также закрывает за собой защелку.
Спасибо!! Доступно =)

Ответить