Почему публикация и отмена выполнения в представлении и обработчике приводят к разным результатам?

Я играл с Runnable и обнаружил, что если вы postDelayed Runnable на View, то удаление обратного вызова не сработает, однако, если вы сделаете то же самое, но опубликуете Runnable на Handler, тогда удаление обратного вызова сработает.

Почему это работает (код Runnable run() никогда не выполняется):

Runnable runnable = new Runnable() {
    @Override
    public void run() {
        // execute some code
    }
};

Handler handler = new Handler();
handler.postDelayed(runnable, 10000);
handler.removeCallbacks(runnable);

где это не так (код Runnable run() всегда выполняется)?:

Runnable runnable = new Runnable() {
    @Override
    public void run() {
        // execute some code
    }
};

View view = findViewById(R.id.some_view);
view.postDelayed(runnable, 10000);
view.removeCallbacks(runnable);

person Martyn    schedule 19.03.2012    source источник
comment
Вы проверяли возвращаемое значение от removeCallbacks()?   -  person CommonsWare    schedule 19.03.2012
comment
Я не видел этого, вы можете объяснить, как это может помочь? Я прочитал документацию, но не вижу, как это может помочь в приведенном выше примере View.   -  person Martyn    schedule 19.03.2012
comment
View.removeCallbacks() всегда будет return true; (по крайней мере, в ICS - остальные, вероятно, тоже) см. здесь   -  person zapl    schedule 19.03.2012
comment
@zapi: Ой, да, извините, не подумал об этом до конца.   -  person CommonsWare    schedule 19.03.2012
comment
@CommonsWare View ведет себя не так, как говорится в документах, на самом деле это не ваша вина :)   -  person zapl    schedule 19.03.2012


Ответы (3)


Если View не прикреплен к окну, я вижу, что это происходит, благодаря тому, что выглядит как ошибка в Android. Таким образом, с тактической точки зрения это может быть вопросом времени, чтобы убедиться, что вы не размещаете и не удаляете Runnable до тех пор, пока View не будет прикреплено к окну.

Если у вас есть пример проекта, который воспроизводит эту проблему, я хотел бы взглянуть на него. В противном случае я попытаюсь сделать свой собственный, чтобы у меня было что-то, что я мог бы использовать, чтобы сообщить о своей предполагаемой ошибке.


ОБНОВЛЕНИЕ

Как упоминалось в комментариях, removeCallbacks() работает с более обычными виджетами, поэтому похоже, что это проблема, специфичная для WebView, согласно образцу кода OP.

person CommonsWare    schedule 19.03.2012
comment
@Martyn: Да, как я и подозревал, WebView в этот момент не привязан к окну (например, getWindowToken() возвращает null), и поэтому вы споткнетесь об этой ошибке в Android. Я подам вопрос об этом в ближайшее время. А пока вам нужно будет использовать Handler для надежного removeCallbacks(). - person CommonsWare; 19.03.2012
comment
@Martyn: Похоже, проблема более тонкая, чем я думал. Я попытался воспроизвести вашу проблему, используя Button, и removeCallbacks() преуспел, хотя Button не был прикреплен к окну во время postDelayed(). Теперь я предполагаю, что ваша трудность может быть более специфичной для WebView. Несмотря на это, вы, вероятно, должны просто использовать Handler сейчас. - person CommonsWare; 19.03.2012
comment
Спасибо, Марк. Я также получил removeCallback(), работающий с TextView, и пытался выяснить, почему WebView не работал, но ничего не добился - если вы это узнаете, я бы хотел знать. - person Martyn; 19.03.2012
comment
Что бы это ни стоило, Handler.removeCallbacks() работает всегда. Из-за непредсказуемого характера View.removeCallbacks() я избегаю использования View.post...(), когда необходимо управлять обратным вызовом. - person James Wald; 25.01.2014
comment
вы не публикуете и не удаляете Runnable до тех пор, пока представление не будет прикреплено к окну. Если я вызову post для представления, которое я только что добавил, вызвав addView, есть ли шанс, что Runnable не будет опубликован? Я использую эту технику для получения размера представления или что-то вроде того. - person Jenix; 01.11.2018
comment
Недавно я запустил свой проект как на своем старом Samsung Note 2 (Android 4.4.2), так и на программном эмуляторе HTC One (4.3) и заметил некоторые неожиданные проблемы, с которыми я никогда не сталкивался на текущем устройстве (Android 8.0). Я подозреваю, что проблема как-то связана с вызовом представлений, которые еще не добавлены (но был вызван addView). Я должен был воспроизвести проблему с новым пустым проектом, но не смог. Но что я могу сказать вам точно, так это то, что никогда не звонят Runnable, которые публикуются сразу после звонка addView, 7 из 10 на Note 2 и 2 из 10 на HTC One. - person Jenix; 01.11.2018
comment
Причина, по которой он работает так случайным образом, заключается в том, что мой проект одновременно отправляет много Runnable из других рабочих потоков. И сегодня я увидел один комментарий по ссылке, которую я добавил выше, в которой говорится, что этот метод прекрасно работает для большинства устройств, но не работает на Nexus S (под управлением 2.2 и 4.0). А также увидел ваш ответ здесь, поэтому я спрашиваю, есть ли какие-либо известные ошибки, связанные с View's post. - person Jenix; 01.11.2018
comment
@Jenix: Если я вызову сообщение для представления, которое я только что добавил, вызвав addView, есть ли шанс, что Runnable не будет опубликован? -- Я бы не ожидал, что это будет проблемой. Вы всегда можете вызвать post() для Handler или для некоторого стабильного View (например, android.R.id.content). Насколько я знаю, View — это просто ворота для публикации вещей. - person CommonsWare; 01.11.2018
comment
Да, вы правы, в большинстве случаев мне не нужно было бы поступать таким образом. Но в моем недавнем проекте причина, по которой я называю конкретные представления post, заключается в том, что мне нужно знать те размеры, которые определяются после их первого прохода макета. А также есть последующие задания, которые необходимо выполнить после инициализации, выполненной этими первыми Runnable. Поэтому я заранее публиковал такие вакансии через определенные Views. Но во многих тестовых прогонах (я имею в виду только мой проект. Как я уже сказал выше, я не мог воспроизвести ту же проблему с новым простым проектом) - person Jenix; 01.11.2018
comment
оказалось, что первые несколько Runnable никогда не вызывались, которые были опубликованы сразу после вызова addView. Поэтому я попытался отложить публикацию этих Runnable примерно на 3 секунды, и на этот раз никаких проблем. Как я уже сказал, у меня никогда не было такой проблемы с последними устройствами. Поэтому я не уверен, что это старая ОС Android или проблема некоторых конкретных старых устройств. Или, может быть, мой проект отправляет слишком много Runnable в поток пользовательского интерфейса, а медленные старые не успевают за ними. Во всяком случае, я уверен, что некоторые из первых Runnables отсутствуют, когда я вызываю post сразу после вызова addView. - person Jenix; 01.11.2018

По разным причинам обработчик представления (view.getHandler()) может быть не готов, когда вы хотите инициировать анимацию.

Поэтому вам, вероятно, следует подождать, прежде чем назначать runnable для представления.

Предполагая, что вы пытаетесь сделать это из действия, вот код, который ожидает, пока обработчик будет доступен, прежде чем опубликовать исполняемый файл:

private void assignRunnable(final View view, final Runnable runnable, final int delay)
{
  if (view.getHandler() == null) {
    // View is not ready, postpone assignment
    this.getView().postDelayed(new Runnable() {
      @Override
      public void run() {
        assignRunnable(view, runnable, delay);
      }
    }, 100);

    //Abort
    return;
  }

  //View is ready, assign the runnable
  view.postDelayed(runnable, delay);
}
person Tom Susel    schedule 02.03.2014

Глядя на ViewRootImpl.java, семантика View.removeCallbacks() кажется по меньшей мере неясной.

RunQueue.removeCallbacks просто удаляет Runnables из ArrayList. См. здесь.

Если RunQueue.executeActions вызывается перед removeCallbacks, то ArrayList очищается во всех случаях, что делает removeCallbacks неработоспособным. См. здесь.

RunQueue.executeActions вызывается для каждого обхода... См. здесь.

Поэтому, если я что-то не упущу, View.removeCallbacks не будет работать, если обход произошел с тех пор, как вы вызвали View.post.

Я буду придерживаться комментария @james-wald выше и не буду использовать View.post

person mbonnin    schedule 07.04.2016