image/svg+xml $ $ ing$ ing$ ces$ ces$ Res Res ea ea Res->ea ou ou Res->ou r r ea->r ch ch ea->ch r->ces$ r->ch ch->$ ch->ing$ T T T->ea ou->r

Communication inter-threads par message

Thread principale

File de messages

Pratique courante :

  1. Créer un Handler comme champ d'une activité à partir la méthode onCreate() (lié à la thread principale)
  2. Utiliser ce champ dans les threads secondaires pour envoyer des messages

Exemple #1 : affichage d'un compteur mis à jour chaque seconde en utilisant une thread secondaire

// Code sample from Coursand [http://igm.univ-mlv.fr/~chilowi/], under the Apache 2.0 License

package fr.upem.coursand.counter;

import android.os.Handler;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

import fr.upem.coursand.R;

public class ThreadCounterActivity extends AppCompatActivity {

    private TextView counterView;
    private Handler handler;
    private Thread refreshThread = null;

    public void onCreate(Bundle b) {
        super.onCreate(b);
        setContentView(R.layout.activity_thread_counter);
        handler = new Handler();
        counterView = findViewById(R.id.counterView);
    }

    /**
     * we start here the incrementing thread
     */
    @Override
    protected void onStart() {
        super.onStart();
        refreshThread = new Thread( ()-> {
                int counter = 0;
                while (!Thread.interrupted()) {
                    // UI code must be executed in the UI thread (we post the code on the handler)
                    final int counterNow = counter;
                    handler.post( () -> counterView.setText("" + counterNow));
                    // we could also use this
                    // runOnUiThread( () -> { counterView.setText("" + counter); } );
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        return; // exit from the thread
                    }
                    counter++;
                }
        });
        refreshThread.start();
    }

    /**
     * we stop here the incrementing thread
     */
    @Override
    public void onStop() {
        super.onStop();
        // testing nullity is a precaution (refreshThread should be not null)
        if (refreshThread != null) refreshThread.interrupt();
    }
}

Exemple #2 : affichage d'un compteur incrémenté sans utiliser de thread secondaire

// Code sample from Coursand [http://igm.univ-mlv.fr/~chilowi/], under the Apache 2.0 License

package fr.upem.coursand.counter;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.Handler;
import android.widget.TextView;

import fr.upem.coursand.R;

/**
 * Example activity incrementing a counter without using an additional thread
 * (only a Handler is employed)
 */
public class HandlerCounterActivity extends AppCompatActivity {

    private TextView counterView;
    private Handler handler;
    private long counterNow = 0L;

    public void onCreate(Bundle b) {
        super.onCreate(b);
        setContentView(R.layout.activity_thread_counter); // use the same layout than the thread version
        handler = new Handler();
        counterView = findViewById(R.id.counterView);
    }

    /** Code executed periodically to display and increment the counter */
    private final Runnable incrementRunnable = () -> {
        counterView.setText("" + counterNow++);
        // recursive call of increment runnable 1 second later
        handler.postDelayed(getIncrementRunnable(), 1000);
    };

    /** Required to allow recursive call from increment runnable */
    private Runnable getIncrementRunnable() {
        return incrementRunnable;
    }

    /**
     * we start here the incrementation
     */
    @Override
    protected void onStart() {
        super.onStart();
        incrementRunnable.run();
    }

    /**
     * we stop here the incrementation
     */
    @Override
    public void onStop() {
        super.onStop();
        handler.removeCallbacks(incrementRunnable);
    }
}

Remarques :

Tâches asynchrones (AsyncTask)

Utilisation de AsyncTask

Exemple : calcul de fib(n) avec une tâche asynchrone

// Code sample from Coursand [http://igm.univ-mlv.fr/~chilowi/], under the Apache 2.0 License

package fr.upem.coursand.fibocomputer;

import java.lang.ref.WeakReference;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.WeakHashMap;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.AsyncTask.Status;
import android.os.Bundle;
import android.text.method.ScrollingMovementMethod;
import android.view.Menu;
import android.view.View;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ToggleButton;

import fr.upem.coursand.R;

public class FiboComputer extends Activity 
{
	public static final int PROGRESS_STEP = 10;
	
	public class FiboTask extends AsyncTask<Integer, Integer, BigInteger>
	{
		private int n = -1;
		private int activityId;
		private TextView tv;
		private ToggleButton tb;
		private ProgressBar pb;

		private void updateComponentReferences()
		{
			FiboComputer fc = getActivityInstance(activityId);
			tb = fc.findViewById(R.id.fiboComputeButton);
			tv = fc.findViewById(R.id.fiboResultTextView);
			pb = fc.findViewById(R.id.fiboProgressBar);
		}

		/** Implentation of the computation, the code is run in a new secondary thread */
		@Override
		protected BigInteger doInBackground(Integer... params) 
		{
			n = params[0];
			BigInteger a = BigInteger.ONE, b = BigInteger.ONE;
			if (n < 3) return a;
			for (int i = 3; i < n; i++)
			{
				if (isCancelled()) return null;
				BigInteger tmp = a;
				a = b;
				b = tmp.add(a);
				if (i % PROGRESS_STEP == 0) publishProgress(i);
			}
			return b;
		}

		/** Called with the result of the computation on the UI thread */
		@Override
		protected void onPostExecute(BigInteger result) 
		{
			updateComponentReferences();
			Toast.makeText(FiboComputer.this, "Fibonacci number has been computed", Toast.LENGTH_LONG).show();
			tv.setText(result.toString());
			tb.setChecked(false);
		}

		/** Called on the UI thread when the computation is stopped */
		@Override
		protected void onCancelled(BigInteger result) 
		{
			updateComponentReferences();
			Toast.makeText(FiboComputer.this, "Fibonacci number computation has been canceled", Toast.LENGTH_LONG).show();
			tv.setText("Cancelled");
			tb.setChecked(false);
		}

		/** We prepare the UI before the computation */
		@Override
		protected void onPreExecute() 
		{
			activityId = FiboComputer.this.activityId;
			updateComponentReferences();
			Toast.makeText(FiboComputer.this, "Starting the computation", Toast.LENGTH_LONG).show();
			tb.setChecked(true);
			tv.setText("");
		}

		/** We update the progress bar with the reached rank */
		@Override
		protected void onProgressUpdate(Integer... values) 
		{
			updateComponentReferences();
			if (pb.getMax() != n) pb.setMax(n);
			pb.setProgress(values[values.length - 1]);
		}
		
	}
	
	private FiboTask fiboTask = null;

	/** This map saves all the activity instances (useful for the AsyncTask to track a recreated activity */
	private static Map<Integer, WeakReference<FiboComputer>> activityInstances = new HashMap<>();
	private static int activityCounter = 0;
	private int activityId;

	private FiboComputer getActivityInstance(int id)
	{
		return activityInstances.get(id).get();
	}

	private void flushActivityInstances()
	{
		// Remove the weak references from the map that are not used anymore
		Iterator<Map.Entry<Integer, WeakReference<FiboComputer>>> it = activityInstances.entrySet().iterator();
		while (it.hasNext())
			if (it.next().getValue().get() == null)
				it.remove();
	}
	
	public void onComputeButtonClick(View v)
	{
		EditText et = FiboComputer.this.findViewById(R.id.fiboQueryEditView);
		if (fiboTask == null || fiboTask.isCancelled() || fiboTask.getStatus() == Status.FINISHED)
		{
			fiboTask = new FiboTask();
			fiboTask.execute(Integer.parseInt(et.getText().toString()));
		} else
		{
			fiboTask.cancel(true);
		}
	}
	
	@Override
	protected void onCreate(Bundle savedInstanceState) 
	{
		super.onCreate(savedInstanceState);
		if (savedInstanceState != null)
			activityId = savedInstanceState.getInt("activityId");
		else
			activityId = activityCounter++;
		activityInstances.put(activityId, new WeakReference<>(this));
		flushActivityInstances();
		setContentView(R.layout.activity_fibo_computer);
		// to allow the result textview to be scrollable:
		((TextView)findViewById(R.id.fiboResultTextView)).setMovementMethod(new ScrollingMovementMethod());
	}

	@Override
	protected void onSaveInstanceState(Bundle outState)
	{
		super.onSaveInstanceState(outState);
		outState.putInt("activityId", activityId);
	}

}