Обновление пользовательского интерфейса уничтоженной активности из Handler Runnable

Следующий код обновляет TextView до тех пор, пока определенное условие не станет ложным, а затем Handler postDelayed больше не вызывается.

Однако, если действие уничтожено, оно попытается обновить значение null TextView. Как правильно с этим справиться? Я знаю, что могу поставить защитную нулевую проверку на TextView, но это все еще не является по-настоящему безопасным потоком.

@Override
protected void onCreate(Bundle savedInstanceState) {
     Handler durationHandler = new Handler();
     durationHandler.postDelayed(updateSeekBarTime, 50);
}

private Runnable updateSeekBarTime = new Runnable() {
            public void run() {

                timeElapsed = mediaPlayer.getCurrentPosition();

                double timeRemaining = finalTime - timeElapsed;

                timeLeft.setText(String.format("%d", TimeUnit.MILLISECONDS.toSeconds((long) timeRemaining)));

                if (timeRemaining >= 1000)
                    durationHandler.postDelayed(this, 200);
            }
        };

Другими словами, updateSeekBarTime может в любой момент выполнения попытаться получить доступ к элементам данных уже уничтоженного действия, как предотвратить это?


person 2cupsOfTech    schedule 18.08.2015    source источник


Ответы (3)


Запустите обработчик в onResume(). В onPause() остановите обработчик с помощью

handler.removeCallbacksAndMessages(null); //null removes everything
person ElDuderino    schedule 18.08.2015
comment
Я думаю, что .removeCallbacksAndMessages избавится от любых Runnables, которые не начали выполнение. Мой вопрос очень конкретен о том, как предотвратить доступ к дераспределенному TextView - person 2cupsOfTech; 18.08.2015
comment
Думаю, тогда вы не обойдете нулевую проверку. Или сделайте это, как этот парень: stackoverflow.com/a/5844433/2001247 - person ElDuderino; 18.08.2015
comment
как указал там человек, переключатель уничтожения может только предотвратить запуск Runnable, но не в середине Runnable - person 2cupsOfTech; 18.08.2015
comment
Неправильно, то, что внутри run(), никогда не выполняется после переключателя уничтожения... private void run() { if(killMe) return; /* делай свою работу */ } - person ElDuderino; 18.08.2015
comment
поэтому скажем, что killMe становится истинным после выполнения условия if(killMe), тогда остальная часть кода внутри run() все равно будет выполняться - person 2cupsOfTech; 18.08.2015
comment
Итак, в чем проблема с нулевой проверкой? - person ElDuderino; 19.08.2015
comment
Давайте продолжим это обсуждение в чате. - person 2cupsOfTech; 19.08.2015

Я создаю собственное приложение Music Player и использую

durationHandler.removeCallbacks(updateSeekBarTime);

в onStop(). Меня устраивает.

РЕДАКТИРОВАТЬ:

Приведенной выше строке помогает тот факт, что я предотвращаю уничтожение Activity с помощью

@Override
public void onBackPressed() {
    moveTaskToBack(true);
}

Это гарантирует, что действие будет свернуто, а не уничтожено, поэтому при повторном открытии оно будет очень быстрым.

РЕДАКТИРОВАТЬ 2:

private Runnable updateSeekBarTime = new MyRunnable();

private class MyRunnable extends Runnable {
        private boolean dontWriteText = false;
        @Override
        public void run() {

            timeElapsed = mediaPlayer.getCurrentPosition();

            double timeRemaining = finalTime - timeElapsed;

        if(!dontWriteText)
            timeLeft.setText(String.format("%d", TimeUnit.MILLISECONDS.toSeconds((long) timeRemaining)));

            if (timeRemaining >= 1000)
                durationHandler.postDelayed(this, 200);
        }
        public void dontWriteText() {
            dontWriteText = true;
        }
    };

Затем вызовите updateSeekBarTime.dontWriteText() в onDestroy() или onStop(). Я бы предпочел onStop().

person David Heisnam    schedule 18.08.2015
comment
и это имеет смысл, потому что все ожидающие Runnables удаляются из вашей очереди обработчика, но как насчет того, который все еще, возможно, выполняется параллельно с основным потоком пользовательского интерфейса? - person 2cupsOfTech; 18.08.2015
comment
так что ваша активность onDestroy() никогда не вызывается? Как насчет ротации активности? Если система не убьет вашу активность, я думаю, что в этом случае будет вызываться onDestroy(). - person 2cupsOfTech; 18.08.2015
comment
@2cupsOfTech Я абсолютно никогда не сталкивался с проблемой, даже когда я поворачиваю экран, даже с задержкой 100 мс в postDelayed(). В любом случае, вы видели мой обновленный ответ? Он должен работать для ваших нужд. - person David Heisnam; 18.08.2015
comment
Да, я видел ваш ответ, и я думаю, что это умный/хакерский способ решения проблемы, однако проверьте ответ, который я опубликовал. - person 2cupsOfTech; 19.08.2015
comment
@2cupsOfTech Вчера я подумывал предложить WeakReference, но отказался от этого, потому что вы не передаете никаких ссылок, слабых или сильных, в исполняемый файл. В любом случае, рад узнать, что вы нашли свой собственный ответ. - person David Heisnam; 19.08.2015

Итак, после некоторого поиска кода и чтения блогов я нашел ответ в примере кода Communicating с потоком пользовательского интерфейса

Хотя вы можете и должны удалять обратные вызовы из обработчика:

handler.removeCallbacksAndMessages(null)

Но это не мешает существующему запущенному потоку получить доступ к уничтоженному действию или его представлениям.

Я искал ответ WeakReference.

Создайте слабую ссылку на элемент TextView или UI, к которому вы будете обращаться. Слабая ссылка предотвращает утечку памяти и сбои, поскольку она автоматически отслеживает «состояние» переменной, которую поддерживает. Если ссылка становится недействительной, слабая ссылка удаляется сборщиком мусора. Этот метод важен для обращения к объектам, которые являются частью жизненного цикла компонента. Использование жесткой ссылки может вызвать утечку памяти, поскольку значение продолжает изменяться; что еще хуже, это может привести к сбоям, если базовый компонент будет уничтожен. Использование слабой ссылки на представление гарантирует, что ссылка носит более временный характер.

Вы по-прежнему должны проверять ссылку на null, но теперь представление будет установлено на null активным потоком/исполняемым объектом, поэтому вы не столкнетесь с состоянием гонки.

person 2cupsOfTech    schedule 19.08.2015
comment
Если вы опубликуете runnable в потоке пользовательского интерфейса (что вы должны сделать), а затем удалите его в событии жизненного цикла, runnable не будет выполнен, потому что событие и runnable не могут выполняться вместе на одном и том же нить. Использование WeakReference — хорошая идея, но в этом подходе есть нечто большее, а именно создание исполняемого статического внутреннего класса и добавление к активности флага mIsDestroyed. Не путайте уничтоженное с собранным мусором. - person Gil Vegliach; 19.08.2015
comment
@GilVegliach, пожалуйста, посмотрите комментарии в других ответах - person 2cupsOfTech; 22.08.2015
comment
Я читал их и все еще думаю, что вы путаете вещи. Во-первых, уничтоженная активность != активность, подходящая для gc'd. Даже в формулировке вашего вопроса, если вы держите уничтоженную активность, текстовое представление внутри нее не будет нулевым при запуске исполняемого файла. Тем не менее, использование слабых ссылок для разрешения операции gc'd — хорошая идея, просто имейте в виду, что destroyed здесь (в Android) имеет очень специфическое значение, см.: developer.android.com/reference/android/app/ - person Gil Vegliach; 22.08.2015
comment
@GilVegliach, когда я говорю «уничтоженная активность», я имею в виду, что ссылка не относится к активности или любому из ее представлений. На самом деле это будет утечка памяти, если я буду ссылаться на любое из его представлений: android-developers.blogspot.com/2009/01/ - person 2cupsOfTech; 23.08.2015
comment
именно, вы имеете в виду «подходящий для gc», а не уничтоженный. В этом случае также обязательно превратите анонимный внутренний исполняемый объект в статический внутренний класс, иначе все равно произойдет утечка. - person Gil Vegliach; 23.08.2015