Bases d'Android: exécuter du code dans le thread d'interface utilisateur

450

Du point de vue de l'exécution de code dans le thread d'interface utilisateur, y a-t-il une différence entre:

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

ou

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

et

private class BackgroundTask extends AsyncTask<String, Void, Bitmap> {
    protected void onPostExecute(Bitmap result) {
        Log.d("UI thread", "I am the UI thread");
    }
}
Luky
la source
Pour clarifier ma question: je suppose que ce code a été appelé à partir d'un thread de service, généralement un écouteur. J'ai également supposé qu'il y avait un gros travail à accomplir soit dans la fonction doInBackground () de l'AsynkTask ou dans une nouvelle tâche (...) appelée avant les deux premiers extraits. Quoi qu'il en soit onPostExecute () de la AsyncTask est mis à la fin de la file d'attente des événements, non?
Luky

Réponses:

288

Aucun de ces éléments n'est exactement le même, bien qu'ils aient tous le même effet net.

La différence entre le premier et le second est que si vous vous trouvez sur le thread principal de l'application lors de l'exécution du code, le premier ( runOnUiThread()) exécutera Runnableimmédiatement. Le second ( post()) place toujours le Runnableà la fin de la file d'attente des événements, même si vous êtes déjà sur le thread d'application principal.

Le troisième, en supposant que vous créez et exécutez une instance de BackgroundTask, perdra beaucoup de temps à extraire un thread du pool de threads, pour exécuter un no-op par défaut doInBackground(), avant de finalement faire ce qui équivaut à a post(). C'est de loin le moins efficace des trois. À utiliser AsyncTasksi vous avez réellement du travail à faire dans un fil d'arrière-plan, pas seulement pour l'utilisation de onPostExecute().

CommonsWare
la source
27
Notez également que AsyncTask.execute()vous devez de toute façon appeler à partir du thread d'interface utilisateur, ce qui rend cette option inutile pour le cas d'utilisation de simplement exécuter du code sur le thread d'interface utilisateur à partir d'un thread d'arrière-plan, sauf si vous déplacez tout votre travail d'arrière-plan doInBackground()et l'utilisez AsyncTaskcorrectement.
kabuko
@kabuko comment puis-je vérifier que j'appelle à AsyncTaskpartir d'un thread d'interface utilisateur?
Neil Galiaskarov
@NeilGaliaskarov Cela ressemble à une option solide: stackoverflow.com/a/7897562/1839500
Dick Lucas
1
@NeilGaliaskarovboolean isUiThread = (Looper.getMainLooper().getThread() == Thread.currentThread());
ban-
1
@NeilGaliaskarov pour les versions supérieures ou égales à l'utilisation MLooper.getMainLooper().isCurrentThread
Balu Sangem
252

J'aime celui du commentaire HPP , il peut être utilisé n'importe où sans aucun paramètre:

new Handler(Looper.getMainLooper()).post(new Runnable() {
    @Override
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});
bombardier
la source
2
Est-ce efficace? Est-ce la même chose que les autres options?
EmmanuelMess
59

Il existe une quatrième façon d'utiliser Handler

new Handler().post(new Runnable() {
    @Override
    public void run() {
        // Code here will run in UI thread
    }
});
vasart
la source
56
Vous devez être prudent avec cela. Parce que si vous créez un gestionnaire dans un thread non UI, vous publierez des messages sur le thread non UI. Un gestionnaire envoie par défaut un message au thread où il est créé.
lujop
108
à exécuter sur le thread d'interface utilisateur principal do new Handler(Looper.getMainLooper()).post(r), ce qui est la manière préférée de faire Looper.getMainLooper()un appel statique à main, alors que postOnUiThread()doit avoir une instance de MainActivityin scope.
HPP
1
@HPP Je ne connaissais pas cette méthode, ce sera un excellent moyen si vous n'avez ni activité ni vue. Fonctionne très bien! merci beaucoup!
Sulfkain
@lujop Il en va de même avec la méthode de rappel onPreExecute d'AsyncTask.
Sreekanth Karumanaghat
18

La réponse de Pomber est acceptable, mais je ne suis pas un grand fan de la création de nouveaux objets à plusieurs reprises. Les meilleures solutions sont toujours celles qui tentent d'atténuer le manque de mémoire. Oui, il existe une collecte automatique des déchets, mais la conservation de la mémoire dans un appareil mobile entre dans les limites des meilleures pratiques. Le code ci-dessous met à jour un TextView dans un service.

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;
    }

}

Il peut être utilisé de n'importe où comme ceci:

textViewUpdater.setText("Hello");
        textViewUpdaterHandler.post(textViewUpdater);
Joe
la source
7
+1 pour mentionner le GC et la création d'objets. CEPENDANT, je ne suis pas nécessairement d'accord avec The best solutions are always the ones that try to mitigate memory hog. Il existe de nombreux autres critères best, et cela a une légère odeur de premature optimization. Autrement dit, sauf si vous savez que vous l'appelez suffisamment pour que le nombre d'objets créés soit un problème (par rapport aux dix mille autres façons dont votre application crée probablement des ordures), ce qui peut être bestd'écrire le plus simple (le plus facile à comprendre ) et passez à une autre tâche.
ToolmakerSteve
2
BTW, textViewUpdaterHandlerserait mieux nommé quelque chose comme uiHandlerou mainHandler, car il est généralement utile pour n'importe quel message dans le thread d'interface utilisateur principal; il n'est pas du tout lié à votre classe TextViewUpdater. Je l'éloignerais du reste de ce code, et je préciserais qu'il peut être utilisé ailleurs ... Le reste du code est douteux, car pour éviter de créer un objet dynamiquement, vous cassez ce qui pourrait être un seul appel dans deux étapes setTextet post, qui reposent sur un objet à longue durée de vie que vous utilisez comme temporaire. Complexité inutile et non thread-safe. Pas facile à entretenir.
ToolmakerSteve
Si vous vraiment avez une situation où on parle tant de fois qu'il est la mise en cache vaut uiHandleret textViewUpdater, puis améliorer votre classe en changeant de public void setText(String txt, Handler uiHandler)et en ajoutant la ligne de la méthode uiHandler.post(this); Ensuite , l' appelant peut faire en une seule étape: textViewUpdater.setText("Hello", uiHandler);. À l'avenir, si elle doit être sécurisée pour les threads, la méthode peut encapsuler ses instructions dans un verrou uiHandleret l'appelant reste inchangé.
ToolmakerSteve
Je suis sûr que vous ne pouvez exécuter une Runnable qu'une seule fois. Ce qui vous brise absolument l'idée, ce qui est bien dans l'ensemble.
Nativ
@Nativ Nope, Runnable peut être exécuté plusieurs fois. Un fil ne peut pas.
Zbyszek
8

Depuis Android P, vous pouvez utiliser getMainExecutor():

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

À partir des documents des développeurs Android :

Renvoie un exécuteur qui exécutera les tâches mises en file d'attente sur le thread principal associé à ce contexte. Il s'agit du thread utilisé pour envoyer les appels aux composants d'application (activités, services, etc.).

Du CommonsBlog :

Vous pouvez appeler getMainExecutor () sur Context pour obtenir un exécuteur qui exécutera ses travaux sur le thread principal de l'application. Il existe d'autres façons d'accomplir cela, en utilisant Looper et une implémentation d'Executor personnalisée, mais c'est plus simple.

Dick Lucas
la source
7

Si vous devez utiliser dans Fragment, vous devez utiliser

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

au lieu de

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

Parce qu'il y aura une exception de pointeur nul dans certaines situations comme un fragment de pager

Beyaz
la source
1

salut les gars celui-ci est une question de base tout de suite je dis

utiliser le gestionnaire

new Handler().post(new Runnable() {
    @Override
    public void run() {
        // Code here will run in UI thread
    }
});
raghu kambaa
la source
Y a-t-il une raison pour laquelle vous avez choisi d'utiliser le gestionnaire à usage général plutôt que runOnUiThread?
ThePartyTurtle
Cela donnera un java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()s'il n'est pas appelé à partir du thread d'interface utilisateur.
Roc Boronat