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

Introduction

Soumission de travaux à un service

Retour graphique d'un service

Exemple : un service toaster

Ecriture du service :

public class ToastService extends Service {

    /** An action that has an unique name */
    public static final String TOAST_ACTION = ToastService.class.getName() + ".displayToast";

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (intent.getAction().equals(TOAST_ACTION)) {
            String id = intent.getStringExtra("id");
            String message = intent.getStringExtra("message");
            boolean longDuration = intent.getBooleanExtra("longDuration", false);
            Toast t = Toast.makeText(this, message, longDuration ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT);
            t.show();
        }
        return START_NOT_STICKY; // if the service is killed, the intent will not be redelivered
    }

    @Override
    public IBinder onBind(Intent intent) {
        throw new UnsupportedOperationException("Not implemented (since we do not use RPC methods)");
    }
}

Ecriture de l'activité envoyant des tâches :

/** An activity submitting messages to a service displaying them as toasts */
class ToastServiceActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_toast_service)
        sendButton.setOnClickListener {
            val intent = Intent(this, ToastService::class.java)
            intent.action = ToastService.TOAST_ACTION
            intent.putExtra("message", messageView.text.toString())
            intent.putExtra("longDuration", longDurationView.isChecked)
            startService(intent)
        }
    }
}

Notifications

Utilisation

Notification 1 Notification 2

Services en avant-plan

Exemple : chronomètre notificateur

Exemple de service proposant un chronomètre avec une notification du temps écoulé (le mode highPriority affiche la notification en heads-up sans nécessité de dérouler le tiroir de notification).

public class ChronoService extends Service 
{
	public static final String ACTION_START = 
			NotifiedChronometer.class.getPackage().getName() + ".startChronometer";
	public static final String ACTION_STOP = 
			NotifiedChronometer.class.getPackage().getName() + ".startChronometer";
	public static final String ACTION_RESET = 
			NotifiedChronometer.class.getPackage().getName() + ".resetChronometer";

	// We don't use the RPC capability
	@Override public IBinder onBind(Intent intent) { return null; }

	private ChronoService instance = null;
	private long cumulatedTime = 0;
	private long startTime = -1;
	private static boolean running = false;

	private Thread updateThread = null;

	/** Return if the chronometer is running */
	public static boolean isRunning()
	{
		return running;
	}

	/** Identifier of the channel used for notification (required since API 26) */
	public static final String CHANNEL_ID =
			ChronoService.class.getName() + ".CHRONO_CHANNEL";

	/** This method creates a new notification channel (required for API 26+)
	 *  It is copied from https://developer.android.com/training/notify-user/build-notification
	 */
	private void createNotificationChannel()
	{
		// Create the NotificationChannel, but only on API 26+ because
		// the NotificationChannel class is new and not in the support library
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
			CharSequence name = "Chronometer channel";
			String description = "Channel for notifications of the chronometer service";
			int importance = NotificationManager.IMPORTANCE_DEFAULT;
			NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance);
			channel.setDescription(description);
			// Register the channel with the system; you can't change the importance
			// or other notification behaviors after this
			NotificationManager notificationManager = getSystemService(NotificationManager.class);
			notificationManager.createNotificationChannel(channel);
		}
	}


	private Notification createNotification(String text, boolean highPriority)
	{
		NotificationCompat.Builder mBuilder =
				new NotificationCompat.Builder(this, CHANNEL_ID)
					.setSmallIcon(android.R.drawable.ic_media_play)
					.setContentTitle("Chronometer")
					.setContentText(text)
					.setPriority(NotificationCompat.PRIORITY_DEFAULT)
					.setSilent(true);
		// if high priority is set, the notification is displayed in a heads-up fashion
		if (highPriority) {
			mBuilder.setPriority(NotificationCompat.PRIORITY_HIGH);
			mBuilder.setDefaults(Notification.DEFAULT_VIBRATE);
		}
		// Associate an action to the notification to start a linked Activity
		Intent resultIntent = new Intent(this, NotifiedChronometer.class)
			.putExtra("running", startTime >= 0);
		// do not start the activity again if it is already on top
		resultIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
		mBuilder.setContentIntent(PendingIntent.getActivity(this, 0, resultIntent, 0));
		return mBuilder.build();
	}

	@Override
	public void onCreate()
	{
		super.onCreate();
		instance = this; // set the singleton instance
		createNotificationChannel();
	}

	/** Arbitrary ID for the notification (with different IDs a service can manage several notifications) */
	public static final int NOTIFICATION_ID = 1;

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) 
	{
		final NotificationManager nm = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
		if (intent == null) return Service.START_STICKY_COMPATIBILITY;
		if (intent.getAction().equals(ACTION_START) && startTime == -1)
		{
			Log.i(getClass().getName(), "Action started intercepted");
			final boolean highPriority = intent.getBooleanExtra("highPriority", false);
			startTime = System.nanoTime();
			// Put in the foreground
			running = true;
			startForeground(NOTIFICATION_ID, createNotification("Running chrono", highPriority));
			// we could post update runnables on an handler instead of using a thread
			updateThread = new Thread(() -> {
				while (! Thread.interrupted())
				{
					long time = (cumulatedTime + System.nanoTime() - startTime) / 1000000000;
					nm.notify(NOTIFICATION_ID, createNotification("Running: " + time + " s", highPriority));
					System.err.println("Notify " + time);
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e)
					{
						return; // the thread was interrupted (if the service is destroyed for example)
					}
				}
			});
			updateThread.start();
		}
		else if (intent.getAction().equals(ACTION_STOP) && startTime >= 0)
		{
			cumulatedTime += System.nanoTime() - startTime;
			stopForeground(true);
			running = false;
			// remove the notification and stop the thread
			nm.cancel(NOTIFICATION_ID);
			updateThread.interrupt();
			startTime = -1;
			// stopSelf();
		}
		else if (intent.getAction().equals(ACTION_RESET))
		{
			if (startTime >= 0) startTime = System.nanoTime();
			cumulatedTime = 0;
		}
		return START_NOT_STICKY; // do not restart automatically the service if it is killed
		// however with startForeground, probability of service killing is weak
	}

	@Override
	public void onDestroy()
	{
		// do not forget to stop the thread if the service is destroyed
		if (updateThread != null) updateThread.interrupt();
	}
}