Как согласовать задержку в бесконечных циклах в разных потоках?

Рейтинг: 1Ответов: 0Опубликовано: 14.01.2023

Недавно начал изучать создание приложений. Ситуация следующая - При нажатии кнопки возврата на главный экран сменяется исходный фрагмент ("сеттингов окна") на фрагмент, представляющий из себя подтверждение выхода (Дальше - ФПВ), где есть две кнопки: "остаться" (в коде - кнопка неперехода) и "главная" (Я слышал о 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();  // Закрываем активити и переходим на главный экран
}
// Обработка метода слушателя нажатия кнопки перехода на главный экран во фрагменте подтверждения выхода = конец

Ответы

Ответов пока нет.