Что именно делает метод публикации?

106

Я столкнулся с очень странной особенностью.

Когда я пытаюсь запустить анимацию в основном потоке, она не запускается. Когда я запускаю указанную анимацию, используя

getView().post(new Runnable() {
            @Override
            public void run() {
                getView().startAnimation(a);
            }
        });

Это действительно начинается.

Я распечатал CurrentThreadперед запуском анимации, и оба печатаются main.

Очевидно, мне здесь чего-то не хватает, так как оба должны запускать анимацию в основном потоке ... Я предполагаю, что, когда сообщение добавляет задачу в очередь, она запускается в более "правильное время", но я хотел бы знать что здесь происходит более подробно.

РЕДАКТИРОВАТЬ: Позвольте мне прояснить ситуацию - мой вопрос в том, почему запуск анимации в сообщении вызывает ее запуск, а при запуске анимации в основном потоке - нет.

Гал
источник
Это специфическое поведение для версии Android? На Android 4.1.2 воспроизвести не смог!
Акдениз,
Я воспроизвел это поведение на Android 2.3.3. Но ведь AnimationDrawable! Обычный Animationэкземпляр начал успешно анимироваться при каждой настройке. В AnimationDrawableслучае; когда вы пытаетесь запустить его onCreate, он не запускается, потому что в этот момент не привязан к просмотру. Так что это не проблема многопоточности AnimationDrawable. Может быть, то же самое относится Animation? developer.android.com/guide/topics/graphics/…
Акдениз

Ответы:

161

post : post вызывает добавление Runnable в очередь сообщений,

Runnable: представляет команду, которую можно выполнить. Часто используется для запуска кода в другом потоке.

run () : запускает выполнение активной части кода класса. Этот метод вызывается при запуске потока, созданного с помощью класса, реализующего Runnable.

getView().post(new Runnable() {

         @Override
         public void run() {
             getView().startAnimation(a);
         }
     });

код :getView().startAnimation(a);

в вашем коде,

post заставляет Runnable ( код будет запущен в другом потоке) добавить очередь сообщений.

Итак, startAnimation будет запущен в новом потоке, когда он будет получен из messageQueue.

[ИЗМЕНИТЬ 1]

Почему мы используем новый поток вместо потока пользовательского интерфейса (основного потока)?

UI поток:

  • При запуске приложения Ui Thread создается автоматически

  • он отвечает за отправку событий в соответствующие виджеты, включая события рисования.

  • Это также поток, с которым вы взаимодействуете с виджетами Android.

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

Что произойдет, если пользователь нажмет кнопку, которая будет работать долго?

((Button)findViewById(R.id.Button1)).setOnClickListener(           
             new OnClickListener() {        
        @Override
    public void onClick(View v) {
            final Bitmap b = loadImageFromNetwork();
            mImageView.setImageBitmap(b);
}
});

Пользовательский интерфейс зависает. Программа может даже вылететь.

public void onClick(View v) {
  new Thread(new Runnable() {
    public void run() {
        final Bitmap b = loadImageFromNetwork();
        mImageView.setImageBitmap(b);
    }
  }).start();
}

Это нарушает правило Android, которое никогда не обновляет пользовательский интерфейс непосредственно из рабочего потока.

Android предлагает несколько способов доступа к потоку пользовательского интерфейса из других потоков.

  • Activity.runOnUiThread (запускаемый)
  • View.post (запускаемый)
  • View.postDelayed (запускается, долго)
  • Обработчик

Как показано ниже,

View.post (запускаемый)

public void onClick(View v) {
  new Thread(new Runnable() {
    public void run() {
      final Bitmap b = loadImageFromNetwork();
      mImageView.post(new Runnable() {
        public void run() {
          mImageView.setImageBitmap(b);
        }
      });
    }
  }).start();
}

Обработчик

final Handler myHandler = new Handler(Looper.getMainLooper());

(new Thread(new Runnable() {

    @Override
    public void run() {
       final Bitmap b = loadImageFromNetwork();
      myHandler.post(new Runnable() {                           

        @Override
        public void run() {
           mImageView.setImageBitmap(b);
          }
        });
      }
    })).start();                
}

введите описание изображения здесь

Для получения дополнительной информации

http://android-developers.blogspot.com/2009/05/pxygen-threading.html

http://www.aviyehuda.com/blog/2010/12/20/android-multithreading-in-a-ui-environment/

Тальха
источник
15
Так почему же запуск анимации при публикации отличается от запуска ее в основном потоке, когда они оба в конечном итоге запускаются в одном потоке?
Gal
Поскольку эта однопоточная модель может привести к снижению производительности в приложениях Android.
Talha
1
Какое отношение имеет низкая производительность к отсутствию анимации?
Gal
17
Я не думаю, что это ответ на вопрос, это больше похоже на общий ответ для новичков, которые ничего не знают о ui-thread и многопоточности. Это не объясняет, почему бросание анимации вперед в очереди заставляет анимацию работать; предполагается, что анимация должна выполняться непосредственно в ui-потоке без использования каких-либо уловок post () или runOnUiThread ().
carrizo
3
Вся работа пользовательского интерфейса должна выполняться в основном потоке (потоке пользовательского интерфейса). Уловка, которая заставляет анимацию работать, используя post () вместо вызова сразу в основном потоке, - это время: если вы вызываете сразу в основном потоке, это означает, что вы сказали «начать анимацию сейчас», но в это время может быть просмотр не готов для анимации (измерить, нарисовать ...). Но если вы поместите это в post (), он когда-нибудь будет ожидать startAnimation в очереди для подготовки представления, готового для анимации.
NguyenDat
35

Это делается в onCreate или onCreateView? Если это так, приложение может не находиться в состоянии, в котором View прикреплен к окну. Многие алгоритмы, основанные на показателях представления, могут не работать, поскольку такие вещи, как измерения и положение представления, возможно, не были рассчитаны. Анимации Android обычно требуют, чтобы они выполняли математику пользовательского интерфейса.

View.post фактически ставит анимацию в очередь в цикле сообщений View, поэтому, как только представление прикрепляется к окну, оно выполняет анимацию вместо того, чтобы запускать ее вручную.

На самом деле вы запускаете что-то в потоке пользовательского интерфейса, но в другое время

Джо Плант
источник
Принятый ответ вводит в заблуждение, где на плакате указано, что «сообщение вызывает Runnable (код будет запущен в другом потоке)». Это правильный ответ: «Вы действительно запускаете что-то в потоке пользовательского интерфейса, но в другое время» - плюс 1
smitty1
18

Поищите здесь хороший ответ. view.post () во многом аналогичен handler.post (). Он попадает в очередь основного потока и запускается после завершения других ожидающих задач. Если вы вызовете activity.runOnUiThread (), он будет вызываться немедленно в потоке пользовательского интерфейса.

Тас Морф
источник
31
Одно огромное (и чрезвычайно полезное) отличие, которое я обнаружил, - это запускаемый в view.post () будет вызываться при первом отображении View. IE, вы можете настроить его для запуска анимации при расширении представления, а затем в какой-то момент в будущем, наконец, добавить его в иерархию представления. В этот момент анимация будет выполнена, и вам не придется об этом беспокоиться.
DeeV
Фактически, handler.post () не всегда отправляет сообщение / запускается в основном потоке. Это зависит от того, как создается обработчик (он может быть связан с Looper в другом потоке). С другой стороны, view.post () всегда будет запускаться в основном потоке
Яир Кукиелка
4

Я думаю, проблема может заключаться в методе жизненного цикла, в котором вы вызываете метод post (). Вы делаете это в onCreate ()? если да, посмотрите, что я нашел в документации по активности onResume ():

onResume ()

Добавлено в API уровня 1 void onResume () Вызывается после onRestoreInstanceState (Bundle), onRestart () или onPause (), чтобы ваша активность начала взаимодействовать с пользователем. Это хорошее место для начала анимации , открытия устройств с эксклюзивным доступом (например, камеры) и т. Д.

https://developer.android.com/reference/android/app/Activity.html#onResume ()

Итак, как сказал Джо Планте, возможно, представление не готово для запуска анимации в тот момент, когда вы вызываете post (), поэтому попробуйте переместить его в onResume ().

PD: На самом деле, если вы переместите код в onResume (), я думаю, вы можете удалить вызов post (), поскольку вы уже находитесь в потоке пользовательского интерфейса, и представление должно быть готово для запуска анимации.

Carrizo
источник
3
onResumeможет вызываться несколько раз (экраны переходят в спящий режим, активность помещается в стопку и т. д.) после того, как изначально "представление готово". Если onResumeвызывается из , тогда может потребоваться флаг для отслеживания погоды, когда анимация уже была запущена, чтобы избежать (повторного) запуска несколько раз.
samis 04