Основи 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");
    }
}

Щоб уточнити моє запитання: я вважав, що цей код викликався з сервісного потоку, як правило, із слухача. Я також гадаю, що існує велика робота над виконанням функції doInBackground () AsynkTask або в новому завданні (...), яке називається перед першими двома фрагментами. У будь-якому випадку onPostExecute () AsyncTask ставиться в кінці черги подій, правда?
Луки

Відповіді:


288

Жоден із них не є абсолютно однаковим, хоча всі вони матимуть однаковий чистий ефект.

Різниця між першим і другим полягає в тому, що якщо ви трапитесь на основний потік програми під час виконання коду, перший ( runOnUiThread()) виконає Runnableнегайно. Другий ( post()) завжди ставить Runnableкінець черги подій, навіть якщо ви вже в основному потоці програми.

Третій, якщо припустити, що ви створюєте та виконуєте екземпляр BackgroundTask, витратить багато часу, захоплюючи нитку з пулу потоків, щоб виконати за замовчуванням неоперацію doInBackground(), перш ніж в кінцевому підсумку зробити те, що становить a post(). Це далеко не найефективніший з цих трьох. Використовуйте, AsyncTaskякщо у вас насправді є робота у фоновому потоці, а не лише для використання onPostExecute().


27
Також зауважте, що так AsyncTask.execute()чи інакше потрібно зателефонувати з потоку користувальницького інтерфейсу, що робить цю опцію марною для випадку використання просто запущеного коду на потоці інтерфейсу з фонового потоку, якщо ви не пересунете всю свою фонову роботу doInBackground()та не використовуєте AsyncTaskналежним чином.
kabuko

@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");
    }
});

2
Наскільки це ефективно? Це те саме, що й інші варіанти?
EmmanuelMess

59

Існує четвертий спосіб використання Handler

new Handler().post(new Runnable() {
    @Override
    public void run() {
        // Code here will run in UI thread
    }
});

56
Вам слід бути обережними з цим. Тому що, якщо ви створите обробник у потоці, що не належить користувальницькому інтерфейсу, ви будете розміщувати повідомлення в потоці, що не використовує інтерфейс користувача. Обробник за замовчуванням повідомлення повідомлення до потоку, де воно створене.
lujop

108
виконувати на головному потоці інтерфейсу do new Handler(Looper.getMainLooper()).post(r), що є кращим способом, оскільки Looper.getMainLooper()робить статичний виклик основним, тоді як postOnUiThread()повинен мати примірник області MainActivityдії.
ГЕС

1
@HPP Я не знав цього методу, буде чудовим способом, коли у вас немає ні активності, ні перегляду. Чудово працює! дякую тобі дуже дуже!
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
BTW, 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

@Nativ Nope, Runnable можна запускати кілька разів. Нитка не може.
Збишек

8

Станом на Android P ви можете використовувати getMainExecutor():

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

З документів розробника Android :

Поверніть Виконавця, який буде виконувати задані завдання на головній темі, пов'язаній з цим контекстом. Це потік, який використовується для відправки викликів до компонентів додатків (діяльності, служб тощо).

З CommonsBlog :

Ви можете зателефонувати getMainExecutor () на Context, щоб отримати Виконавця, який буде виконувати свої завдання в основній темі програми. Є й інші способи досягти цього, використовуючи 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");
    }
});

Тому що в такій ситуації, як фрагмент пейджера, буде нульове виключення вказівника


1

привіт, хлопці, це одне основне питання, будь-яке, я говорю

використовувати Handler

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()якщо воно не викликається з потоку інтерфейсу користувача.
Рок Боронат
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.