Как согласовать задержку в бесконечных циклах в разных потоках?
Недавно начал изучать создание приложений. Ситуация следующая - При нажатии кнопки возврата на главный экран сменяется исходный фрагмент ("сеттингов окна") на фрагмент, представляющий из себя подтверждение выхода (Дальше - ФПВ), где есть две кнопки: "остаться" (в коде - кнопка неперехода) и "главная" (Я слышал о DialogFragment, но пока решил делать с обычными Fragment). Цель состоит в том, чтобы после появления ФПВ каждые 10 секунд срабатывала анимация "напоминания" (я назвал ее так и применяю к FragmentContainerView). Для этого при каждом новом отображении ФПВ я создаю новый поток Thread, где запускаю бесконечный цикл, (в котором есть сама задержка и запуск анимации) который по значению булевой переменной (флага) flag_notify делает проверку о выходе из цикла (и, по идее, поток завершается). Значение этого флага я меняю при нажатии кнопки "остаться", которая к тому же удаляет ФПВ. (Активити реализует интерфейс, созданный в ФПВ, который имеет методы stayButtonListener (для "остаться") и goButtonListener (тут идет переход на другую активити)
Проблема в том, что если понажимать на кнопку возврата (которая, напомню, отображает ФПВ), затем на "остаться" несколько раз с интервалом меньше, чем 10 секунд (т.е. быстрее, чем успеет отработать поток), анимация начнет срабатывать непредсказуемо (я так понял, что каждый поток ждет "свои" 10 секунд и получается рассинхрон).
У кого есть предложения, как это лучше реализовать? Я также слышал о Handler, synchronized, wait, post, но так и не понял, как это применить. Прикрепляю мой текущий код (см. комментарии):
Контейнер фрагментов XML:
<androidx.fragment.app.FragmentContainerView
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
android:background="@color/setting_field_bg"
tools:layout="@layout/fragment_setting_field">
</androidx.fragment.app.FragmentContainerView>
Упоминаемый мной флаг
private static boolean flag_notify = false; // Флаг для потока для анимации напоминания
В методе onCreate активити
// Нижний фрагмент = начало
// Инициализируем контейнер фрагментов
container = (FragmentContainerView) findViewById(R.id.container);
fragmentManager = getSupportFragmentManager(); // Инициализируем менеджер фрагментов
// Инициализируем фрагмент для подтверждения выхода
confirmationDialogFragment = new ConfirmationDialogFragment();
// Инициализируем фрагмент для сеттингов окна
settingFieldFragment = new SettingFieldFragment();
addSettingFieldFragment(); // Первоначально отображаем фрагмент сеттингов окна
// Нижний фрагмент = конец
Кнопка возврата на главный экран
// Кнопка возврата на главный экран = начало
return_home = (CardView) findViewById(R.id.return_home);
// Обработка нажатия = начало
return_home.setOnTouchListener((v, event) -> {
if (event.getAction() == MotionEvent.ACTION_DOWN){
// Коснулся до кнопки
return_home.startAnimation(button_pressed_animation); // Запускаем анимацию нажатой кнопки
}
else if (event.getAction() == MotionEvent.ACTION_UP) {
// Оторвался от кнопки
return_home.clearAnimation(); // Удаляем анимацию, чтобы кнопка не зависла
// Накладываем фрагмент для подтверждения выхода
addConfirmationDialogFragment();
}
return true;
});
// Обработка нажатия = конец
// Кнопка возврата на главный экран = конец
Далее - основные методы
// Метод для инициализации транзакции = начало
private FragmentTransaction mInitFragmentTransaction(int enter_anim, int exit_anim,
boolean add_to_back_stack){
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); // Инициализируем новую транзакцию
fragmentTransaction.setReorderingAllowed(true); // Для корректной работы всегда указываем
if (enter_anim != 0 && exit_anim != 0){
// Значит, анимации заданы
fragmentTransaction.setCustomAnimations(enter_anim, exit_anim); // Задаем анимации
}
if (add_to_back_stack){
// Значит, нужно добавить фрагмент в стек
fragmentTransaction.addToBackStack(null); // Добавляем в стек фрагмент
}
return fragmentTransaction; // Возвращаем транзакцию
}
// Метод для инициализации транзакции = конец
// Метод для задания доступности экрана = начало
private void mDisplayEnable(boolean enable) {
return_home.setEnabled(enable); // Кнопка возврата на главный экран
person_avatar.setEnabled(enable); // Кнопка меню-аватара
play.setEnabled(enable); // Кнопка запуска
content_field.setEnabled(enable); // Поле контента пользователя
content_field_text.setEnabled(enable); // Текстовое поле пользователя
}
// Метод для задания доступности экрана = конец
// Наложение фрагмента для подтверждения выхода = начало
private void addConfirmationDialogFragment(){
FragmentTransaction fragmentTransaction = mInitFragmentTransaction(R.anim.confirmaton_fragment_start, R.anim.setting_fragment_end,
true); // Базовая инициализация транзакции
fragmentTransaction.hide(settingFieldFragment); // Скрываем фрагмент сеттингов окна, чтобы "Заблокировать" его
fragmentTransaction.add(R.id.container, confirmationDialogFragment); // Накладываем
mDisplayEnable(false); // "Блокируем" наш макет
fragmentTransaction.commit(); // Подтверждаем транзакцию
// Запускаем поток для повтора анимации напоминания для контейнера фрагментов = начало
flag_notify = false; // Обновляем значение флага
new Thread(() -> {
// Бесконечный цикл для повтора анимации напоминания = начало
while (true){
try {
Thread.sleep(10000); // Задаем задержку в 10 сек
Log.d(LOG_TAG, "" + flag_notify); // Отображаем в логах значение флага
Log.d(LOG_TAG, "Текущее количество потоков: " + Thread.activeCount());
if (flag_notify){
Thread.currentThread().interrupt();
break;
}
// Действия на основном потоке = начало
runOnUiThread(() -> {
// Запускаем анимацию напоминания для контейнера фрагментов
container.startAnimation(notify_animation);
});
// Действия на основном потоке = конец
} catch (InterruptedException e) {
e.printStackTrace(); // Автоматический код
}
}
// Бесконечный цикл для повтора анимации напоминания = конец
}).start(); // Запускаем
// Запускаем поток для повтора анимации напоминания для контейнера фрагментов = конец
}
// Наложение фрагмента для подтверждения выхода = конец
// Отображение фрагмента для сеттингов окна = начало
private void addSettingFieldFragment(){
FragmentTransaction fragmentTransaction = mInitFragmentTransaction(0, 0,
false); // Базовая инициализация транзакции
// Отображем фрагмент для сеттингов окна
fragmentTransaction.replace(R.id.container, settingFieldFragment);
fragmentTransaction.commit(); // Подтверждаем транзакцию
}
// Отображение фрагмента для сеттингов окна = конец
// Обработка метода слушателя нажатия кнопки неперехода во фрагменте подтверждения выхода = начало
@Override
public void stayButtonListener() {
flag_notify = true; // Устанавливаем флаг для завершения потока
// Удаляем анимацию напоминания с контейнера фрагментов [на всякий случай]
container.clearAnimation();
// Инициализируем новую транзакцию
FragmentTransaction fragmentTransaction = mInitFragmentTransaction(R.anim.setting_fragment_start, R.anim.confirmation_fragment_end,
false); // Базовая инициализация транзакции
fragmentTransaction.remove(confirmationDialogFragment); // Удаляем фрагмент подтверждения выхода
fragmentTransaction.show(settingFieldFragment); // Отображаем фрагмент сеттингов окна
fragmentTransaction.commit(); // Подтверждаем транзакцию
mDisplayEnable(true); // "Разблокируем" наш макет
}
// Обработка метода слушателя нажатия кнопки неперехода во фрагменте подтверждения выхода = конец
// Обработка метода слушателя нажатия кнопки перехода на главный экран во фрагменте подтверждения выхода = начало
@Override
public void goButtonListener() {
flag_notify = true; // Устанавливаем флаг для завершения потока
finish(); // Закрываем активити и переходим на главный экран
}
// Обработка метода слушателя нажатия кнопки перехода на главный экран во фрагменте подтверждения выхода = конец