Exécution de code dans le thread principal à partir d'un autre thread


326

Dans un service Android, j'ai créé des threads pour effectuer une tâche d'arrière-plan.

J'ai une situation où un thread doit publier certaines tâches dans la file d'attente de messages du thread principal, par exemple a Runnable.

Existe-t-il un moyen d'obtenir Handlerle fil principal et de le poster Message/ Runnableà partir de mon autre fil?

Merci,


2
Vous pouvez également utiliser un récepteur de diffusion personnalisé .... essayez ma réponse ici, [Récepteur de diffusion interne] [1] [1]: stackoverflow.com/a/22541324/1881527
Melbourne Lopes

Il y a plusieurs façons. Outre la réponse de David et le commentaire de dzeikei dans sa réponse, (3) vous pouvez utiliser un récepteur de diffusion, ou (4) transmettre le gestionnaire dans les extras d'intention utilisés pour démarrer le service, puis récupérer le gestionnaire du thread principal dans le service à l'aide de getIntent ( ) .getExtras ().
Ashok Bijoy Debnath

2
@ sazzad-hissain-khan, Pourquoi marquer cette question de 2012 avec principalement des réponses en Java avec la balise kotlin?
Tenfour04

Réponses:


617

REMARQUE: Cette réponse a suscité tellement d'attention que j'ai besoin de la mettre à jour. Depuis que la réponse originale a été publiée, le commentaire de @dzeikei a reçu presque autant d'attention que la réponse originale. Voici donc 2 solutions possibles:

1. Si votre thread d'arrière-plan a une référence à un Contextobjet:

Assurez-vous que vos threads de travail d'arrière-plan ont accès à un objet Context (peut être le contexte Application ou le contexte Service). Ensuite, faites-le dans le thread de travail en arrière-plan:

// Get a handler that can be used to post to the main thread
Handler mainHandler = new Handler(context.getMainLooper());

Runnable myRunnable = new Runnable() {
    @Override 
    public void run() {....} // This is your code
};
mainHandler.post(myRunnable);

2. Si votre thread d'arrière-plan n'a pas (ou n'a pas besoin) d' Contextobjet

(suggéré par @dzeikei):

// Get a handler that can be used to post to the main thread
Handler mainHandler = new Handler(Looper.getMainLooper());

Runnable myRunnable = new Runnable() {
    @Override 
    public void run() {....} // This is your code
};
mainHandler.post(myRunnable);

1
Merci David, cela a fonctionné pour moi, une dernière chose si vous pouviez m'aider, si j'étend Handler et impl handleMessage () cela empêcherait-il le thread principal de gérer ses messages? ce n'est qu'une question de curosité ..
Ahmed

2
Non. Si vous sous-classe Handler(ou utilisez l' Handler.Callbackinterface), votre handleMessage()méthode sera UNIQUEMENT appelée pour les messages qui ont été publiés à l'aide de votre gestionnaire. Le thread principal utilise un gestionnaire différent pour publier / traiter les messages afin qu'il n'y ait pas de conflit.
David Wasser

205
Je crois que vous n'aurez même pas besoin de contexte si vous utilisezHandler mainHandler = new Handler(Looper.getMainLooper());
dzeikei

6
Point mineur; votre code ne va pas là où le ... est actuellement. Ça devrait êtrenew Runnable(){@Override public void run() {....}};
Richard Tingle

1
@SagarDevanga Ce n'est pas le bon endroit pour poser une question différente. Veuillez poster une nouvelle question, pas un commentaire à une réponse sans rapport. Vous obtiendrez ainsi une réponse meilleure et plus rapide.
David Wasser

138

Comme un commentateur ci-dessous l'a indiqué correctement, il ne s'agit pas d'une solution générale pour les services, uniquement pour les threads lancés à partir de votre activité (un service peut être un tel thread, mais pas tous). Sur le sujet compliqué de la communication activité-service, veuillez lire l'intégralité de la section Services du document officiel - c'est complexe, il serait donc avantageux de comprendre les bases: http://developer.android.com/guide/components/services.html #Notifications

La méthode ci-dessous peut fonctionner dans les cas les plus simples.

Si je vous comprends bien, vous avez besoin d'un peu de code à exécuter dans le thread GUI de l'application (ne pouvez penser à rien d'autre appelé thread "principal"). Pour cela, il existe une méthode sur Activity:

someActivity.runOnUiThread(new Runnable() {
        @Override
        public void run() {
           //Your code to run in GUI thread here
        }//public void run() {
});

Doc: http://developer.android.com/reference/android/app/Activity.html#runOnUiThread%28java.lang.Runnable%29

J'espère que c'est ce que vous recherchez.


20
OP dit qu'il exécute des threads dans a Service. Vous ne pouvez pas utiliser runOnUiThread()dans un fichier Service. Cette réponse est trompeuse et ne répond pas à la question posée.
David Wasser

31

Il existe un autre moyen simple, si vous n'avez pas accès au contexte.

1). Créez un gestionnaire à partir du looper principal:

Handler uiHandler = new Handler(Looper.getMainLooper());

2). Implémentez une interface exécutable:

Runnable runnable = new Runnable() { // your code here }

3). Publiez votre Runnable sur uiHandler:

uiHandler.post(runnable);

C'est tout ;-) Amusez-vous avec les threads, mais n'oubliez pas de les synchroniser.


29

Si vous exécutez du code dans un thread, par exemple en retardant une action, vous devez invoquer à runOnUiThreadpartir du contexte. Par exemple, si votre code est dans la MainActivityclasse, utilisez ceci:

MainActivity.this.runOnUiThread(new Runnable() {
    @Override
    public void run() {
        myAction();
    }
});

Si votre méthode peut être invoquée à partir du principal (thread d'interface utilisateur) ou d'autres threads, vous avez besoin d'une vérification comme:

public void myMethod() {
   if( Looper.myLooper() == Looper.getMainLooper() ) {
       myAction();
   }
   else {

}

7
OP dit qu'il exécute des threads dans a Service. Vous ne pouvez pas utiliser runOnUiThread()dans un fichier Service.
David Wasser

@DavidWasser Est-ce documenté quelque part? Les documents de méthode ne mentionnent rien à ce sujet. developer.android.com/reference/android/app/…
Greg Brown

1
@GregBrown Comme vous l'avez indiqué par votre lien vers la Activitydocumentation, runOnUiThread()est une méthode de Activity. Ce n'est pas une méthode Service, vous ne pouvez donc pas l'utiliser. Quoi de plus clair que ça?
David Wasser

@DavidWasser Assez juste. Je ne me souviens même pas pourquoi j'ai posé cette question maintenant (elle a été publiée il y a presque un an).
Greg Brown

24

Un bloc de code condensé est le suivant:

   new Handler(Looper.getMainLooper()).post(new Runnable() {
       @Override
       public void run() {
           // things to do on the main thread
       }
   });

Cela n'implique pas de transmettre la référence d'activité ou la référence d'application.

Équivalent Kotlin:

    Handler(Looper.getMainLooper()).post(Runnable {
        // things to do on the main thread
    })

20

Versions de Kotlin

Lorsque vous êtes sur une activité , utilisez

runOnUiThread {
    //code that runs in main
}

Lorsque vous avez un contexte d'activité , mContext puis utilisez

mContext.runOnUiThread {
    //code that runs in main
}

Lorsque vous êtes dans un endroit où aucun contexte n'est disponible , utilisez

Handler(Looper.getMainLooper()).post {  
    //code that runs in main
}

Je ne sais pas de quelle version de Kotlin il s'agit, mais j'ai dû ajouter des accolades:runOnUiThread(){...}
Wex

5

La manière la plus simple, surtout si vous n'avez pas de contexte, si vous utilisez RxAndroid, vous pouvez faire:

AndroidSchedulers.mainThread().scheduleDirect {
    runCodeHere()
}

5

Code Kotlin plus précis à l'aide du gestionnaire:

Handler(Looper.getMainLooper()).post {  
 // your codes here run on main Thread
 }

4

Une méthode à laquelle je peux penser est la suivante:

1) Laissez l'interface utilisateur se lier au service.
2) Exposez une méthode comme celle ci-dessous par le Binderqui enregistre votre Handler:

public void registerHandler(Handler handler) {
    mHandler = handler;
}

3) Dans le thread d'interface utilisateur, appelez la méthode ci-dessus après la liaison au service:

mBinder.registerHandler(new Handler());

4) Utilisez le gestionnaire dans le fil du service pour publier votre tâche:

mHandler.post(runnable);

3

HandlerThread est une meilleure option pour les threads Java normaux dans Android.

  1. Créez un HandlerThread et démarrez-le
  2. Créez un gestionnaire avec Looper à partir de HandlerThread:requestHandler
  3. postune Runnabletâche surrequestHandler

Communication avec l'interface utilisateur depuis HandlerThread

  1. Créer un Handleravec Looperpour le thread principal: responseHandleret remplacer la handleMessageméthode
  2. A l' intérieur Runnabletâche d'autre thread ( HandlerThreaddans ce cas), appelez sendMessageleresponseHandler
  3. Cette sendMessageinvocation de résultat de handleMessagein responseHandler.
  4. Obtenez les attributs de Messageet traitez-le, mettez à jour l'interface utilisateur

Exemple : mise TextViewà jour avec des données reçues d'un service Web. Étant donné que le service Web doit être appelé sur un thread non UI, créé HandlerThreadpour le fonctionnement du réseau. Une fois que vous avez obtenu le contenu du service Web, envoyez un message à votre gestionnaire de thread principal (thread d'interface utilisateur) etHandler gérera le message et mettra à jour l'interface utilisateur.

Exemple de code:

HandlerThread handlerThread = new HandlerThread("NetworkOperation");
handlerThread.start();
Handler requestHandler = new Handler(handlerThread.getLooper());

final Handler responseHandler = new Handler(Looper.getMainLooper()) {
    @Override
    public void handleMessage(Message msg) {
        txtView.setText((String) msg.obj);
    }
};

Runnable myRunnable = new Runnable() {
    @Override
    public void run() {
        try {
            Log.d("Runnable", "Before IO call");
            URL page = new URL("http://www.your_web_site.com/fetchData.jsp");
            StringBuffer text = new StringBuffer();
            HttpURLConnection conn = (HttpURLConnection) page.openConnection();
            conn.connect();
            InputStreamReader in = new InputStreamReader((InputStream) conn.getContent());
            BufferedReader buff = new BufferedReader(in);
            String line;
            while ((line = buff.readLine()) != null) {
                text.append(line + "\n");
            }
            Log.d("Runnable", "After IO call:"+ text.toString());
            Message msg = new Message();
            msg.obj = text.toString();
            responseHandler.sendMessage(msg);


        } catch (Exception err) {
            err.printStackTrace();
        }
    }
};
requestHandler.post(myRunnable);

Articles utiles:

handlerthreads-and-why-you-should-be-using-them-in-your-android-apps

android-looper-handler-handlerthread-i


2

Je sais que c'est une vieille question, mais je suis tombé sur une ligne principale à fil unique que j'utilise à la fois dans Kotlin et Java. Ce n'est peut-être pas la meilleure solution pour un service, mais pour appeler quelque chose qui changera l'interface utilisateur à l'intérieur d'un fragment, c'est extrêmement simple et évident.

Java (8):

 getActivity().runOnUiThread(()->{
      //your main thread code
 });

Kotlin:

this.runOnUiThread {
     //your main thread code
}

1

Suivez cette méthode. De cette façon, vous pouvez simplement mettre à jour l'interface utilisateur à partir d'un thread d'arrière-plan. runOnUiThread fonctionne sur le thread principal (UI). Je pense que cet extrait de code est moins complexe et facile, surtout pour les débutants.

AsyncTask.execute(new Runnable() {
            @Override
            public void run() {

            //code you want to run on the background
            someCode();

           //the code you want to run on main thread
 MainActivity.this.runOnUiThread(new Runnable() {

                    public void run() {

/*the code you want to run after the background operation otherwise they will executed earlier and give you an error*/
                        executeAfterOperation();

                   }
                });
            }
        });

dans le cas d'un service

créer un gestionnaire dans le oncreate

 handler = new Handler();

puis utilisez-le comme ça

 private void runOnUiThread(Runnable runnable) {
        handler.post(runnable);
    }

Merci pour cet extrait de code, qui pourrait fournir une aide immédiate limitée. Une explication appropriée améliorerait considérablement sa valeur à long terme en montrant pourquoi c'est une bonne solution au problème, et la rendrait plus utile aux futurs lecteurs avec d'autres questions similaires. Veuillez modifier votre réponse pour ajouter des explications, y compris les hypothèses que vous avez faites.
iBug

1

pour Kotlin, vous pouvez utiliser les corountines Anko :

mettre à jour

doAsync {
   ...
}

obsolète

async(UI) {
    // Code run on UI thread
    // Use ref() instead of this@MyActivity
}

1

Le plus pratique est donc de faire en quelque sorte:

import android.os.AsyncTask
import android.os.Handler
import android.os.Looper

object Dispatch {
    fun asyncOnBackground(call: ()->Unit) {
        AsyncTask.execute {
            call()
        }
    }

    fun asyncOnMain(call: ()->Unit) {
        Handler(Looper.getMainLooper()).post {
            call()
        }
    }
}

Et après:

Dispatch.asyncOnBackground {
    val value = ...// super processing
    Dispatch.asyncOnMain { completion(value)}
}

0
public void mainWork() {
    new Handler(Looper.getMainLooper()).post(new Runnable() {
        @Override
        public void run() {
            //Add Your Code Here
        }
    });
}

Cela peut également fonctionner dans une classe de service sans problème.


Bien que ce code puisse répondre à la question, vous pouvez toujours envisager d'ajouter quelques phrases explicatives car cela augmente la valeur de votre réponse pour les autres utilisateurs!
MBT

0

Avec Kotlin, c'est comme ça dans n'importe quelle fonction:

runOnUiThread {
   // Do work..
}

2
Ce n'est que lorsque vous êtes sur un Activity.
Farsheel
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.