Основы Android: запуск кода в потоке пользовательского интерфейса

450

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

MainActivity.this.runOnUiThread(new Runnable() {
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});

или

MainActivity.this.myView.post(new Runnable() {
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});

а также

private class BackgroundTask extends AsyncTask<String, Void, Bitmap> {
    protected void onPostExecute(Bitmap result) {
        Log.d("UI thread", "I am the UI thread");
    }
}
Luky
источник
Чтобы прояснить мой вопрос: я предположил, что этот код вызывался из служебного потока, обычно слушателя. Я также предположил, что есть тяжелая работа для выполнения либо в функции doInBackground () AsynkTask, либо в новой Задаче (...), вызываемой перед первыми двумя фрагментами. В любом случае onPostExecute () AsyncTask помещается в конец очереди событий, верно?
Luky

Ответы:

289

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

Разница между первым и вторым, что если вам случится быть на главном потоке приложения при выполнении кода, первый один ( runOnUiThread()) будет выполняться Runnableнемедленно. Второй post()метод ( ) всегда ставит Runnableв конец очередь событий, даже если вы уже находитесь в главном потоке приложения.

Третий, предполагая, что вы создаете и выполняете экземпляр BackgroundTask, потратит много времени на извлечение потока из пула потоков, чтобы выполнить no-op по умолчанию doInBackground(), прежде чем в конечном итоге сделать то, что равно a post(). Это, безусловно, наименее эффективный из трех. Используйте, AsyncTaskесли у вас действительно есть работа в фоновом потоке, а не только для использования onPostExecute().

CommonsWare
источник
27
Также обратите внимание, что AsyncTask.execute()в любом случае требуется , чтобы вы вызывали из потока пользовательского интерфейса, что делает эту опцию бесполезной для случая использования простого запуска кода в потоке пользовательского интерфейса из фонового потока, если только вы не переместили всю свою фоновую работу doInBackground()и не использовали ее AsyncTaskдолжным образом.
Кабуко
@kabuko, как я могу проверить, что я звоню AsyncTaskиз потока пользовательского интерфейса?
Нил Галиаскаров
@NeilGaliaskarov Это похоже на солидный вариант: stackoverflow.com/a/7897562/1839500
Дик Лукас
1
@NeilGaliaskarovboolean isUiThread = (Looper.getMainLooper().getThread() == Thread.currentThread());
запрет геоинженерии
1
@NeilGaliaskarov для версий больше или равно MLooper.getMainLooper().isCurrentThread
Balu Sangem
252

Мне нравится комментарий из ГЭС , его можно использовать где угодно без каких-либо параметров:

new Handler(Looper.getMainLooper()).post(new Runnable() {
    @Override
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});
pomber
источник
2
Насколько это эффективно? Это так же, как другие варианты?
Эммануэль
59

Существует четвертый способ использования Handler

new Handler().post(new Runnable() {
    @Override
    public void run() {
        // Code here will run in UI thread
    }
});
vasart
источник
56
Вы должны быть осторожны с этим. Потому что, если вы создадите обработчик в не-пользовательской ветке, вы будете публиковать сообщения в не-пользовательской ветке. Обработчик по умолчанию отправляет сообщение в ветку, где оно создано.
Lujop
108
выполнить в основном потоке пользовательского интерфейса do new Handler(Looper.getMainLooper()).post(r), что является предпочтительным способом, так как Looper.getMainLooper()делает статический вызов main, тогда как postOnUiThread()должен иметь экземпляр MainActivityв области видимости.
ГЭС
1
@HPP Я не знал этот метод, будет отличным способом, когда у вас нет ни Activity, ни View. Работает отлично! большое спасибо очень большое!
Sulfkain
@lujop То же самое имеет место с методом обратного вызова onPreExecute метода AsyncTask.
Sreekanth Karumanaghat
18

Ответ Помбера приемлем, однако я не большой поклонник создания новых объектов неоднократно. Лучшие решения - это всегда те, которые пытаются уменьшить нагрузку на память. Да, есть автоматическая сборка мусора, но сохранение памяти в мобильном устройстве находится в пределах наилучшей практики. Код ниже обновляет TextView в сервисе.

TextViewUpdater textViewUpdater = new TextViewUpdater();
Handler textViewUpdaterHandler = new Handler(Looper.getMainLooper());
private class TextViewUpdater implements Runnable{
    private String txt;
    @Override
    public void run() {
        searchResultTextView.setText(txt);
    }
    public void setText(String txt){
        this.txt = txt;
    }

}

Его можно использовать откуда угодно:

textViewUpdater.setText("Hello");
        textViewUpdaterHandler.post(textViewUpdater);
Джо
источник
7
+1 за упоминание GC и создание объекта. ОДНАКО, я не обязательно согласен с The best solutions are always the ones that try to mitigate memory hog. Есть много других критериев best, и это имеет легкий запах premature optimization. То есть, если вы не знаете, что называете это достаточно, что количество созданных объектов является проблемой (по сравнению с десятью тысячами других способов, которыми ваше приложение, вероятно, создает мусор.), Что может быть bestнаписанием самого простого (самого простого для понимания ) и перейдите к другой задаче.
ToolmakerSteve
2
Кстати, textViewUpdaterHandlerлучше было бы назвать что-то вроде uiHandlerили mainHandler, так как это обычно полезно для любого сообщения в основной теме пользовательского интерфейса; он совсем не привязан к вашему классу TextViewUpdater. Я бы отодвинул его от остальной части этого кода и дал бы понять, что его можно использовать в другом месте ... Остальная часть кода сомнительна, потому что во избежание динамического создания одного объекта вы разбиваете то, что может быть одним вызовом, на два шага, setTextи postэто зависит от долгоживущего объекта, который вы используете в качестве временного. Ненужная сложность, а не потокобезопасность. Не легко поддерживать.
ToolmakerSteve
Если вы действительно есть ситуации , когда это называется так много раз , что стоит кэширование uiHandlerи textViewUpdater, затем улучшить свой класс, изменив значение параметра public void setText(String txt, Handler uiHandler)и добавления метода линии uiHandler.post(this); Затем вызывающий абонент может сделать один шаг: textViewUpdater.setText("Hello", uiHandler);. Затем в будущем, если это необходимо для обеспечения безопасности потоков, метод может заключить свои операторы в блокировку uiHandler, и вызывающая сторона останется неизменной.
ToolmakerSteve
Я уверен, что вы можете запустить Runnable только один раз. Который абсолютно нарушает вашу идею, что в целом приятно.
Натив
@Nativ Нет, Runnable может быть запущен несколько раз. Нить не может.
Збышек
8

Начиная с Android P вы можете использовать getMainExecutor():

getMainExecutor().execute(new Runnable() {
  @Override public void run() {
    // Code will run on the main thread
  }
});

Из документации для разработчиков Android :

Вернуть Executor, который будет запускать поставленные в очередь задачи в главном потоке, связанном с этим контекстом. Это поток, используемый для отправки вызовов компонентам приложения (действиям, службам и т. Д.).

Из CommonsBlog :

Вы можете вызвать getMainExecutor () для Context, чтобы получить Executor, который будет выполнять свои задания в главном потоке приложения. Есть и другие способы сделать это, используя Looper и пользовательскую реализацию Executor, но это проще.

Дик Лукас
источник
7

Если вам нужно использовать во фрагменте, вы должны использовать

private Context context;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        this.context = context;
    }


    ((MainActivity)context).runOnUiThread(new Runnable() {
        public void run() {
            Log.d("UI thread", "I am the UI thread");
        }
    });

вместо

getActivity().runOnUiThread(new Runnable() {
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});

В некоторых ситуациях, таких как фрагмент пейджера, будет исключение нулевого указателя

Beyaz
источник
1

Привет, ребята, это один из основных вопросов, я говорю

использовать обработчик

new Handler().post(new Runnable() {
    @Override
    public void run() {
        // Code here will run in UI thread
    }
});
рагу камбаа
источник
Есть ли причина, по которой вы решили использовать универсальный обработчик вместо runOnUiThread?
ThePartyTurtle
Это даст, java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()если он не вызывается из потока пользовательского интерфейса.
Рок Боронат