Connexion à un service
-
Un composant appelle Context.bindService(Intent service, ServiceConnection conn, int flags) pour se lier à un service
-
ServiceConnection est un listener avec les méthodes :
- onServiceConnected(ComponentName name, IBinder service) : appelé lors de la connexion au service
- onServiceDisconnected(ComponentName name) : appelé lorsque la connexion est perdue
-
flags (combinaison avec ou logique de constantes) :
- BIND_AUTO_CREATE (démarre le service automatiquement)
- BIND_NOT_FOREGROUND (n'accorde pas une priorité de 1er plan au service)
- BIND_ABOVE_CLIENT (priorité service > priorité client)
- BIND_WAIVE_PRIORITY (pas d'impact du contexte sur la priorité)
-
ServiceConnection est un listener avec les méthodes :
- La méthode onBind() du service est appelée : elle doit être redéfinie pour retourner un IBinder exposant les méthodes publiques.
- On utilise la méthode Context.unbindService(ServiceConnection conn) avec le ServiceConnection précédemment utilisé pour se déconnecter du service ; la déconnexion a également lieu lors de l'arrêt du composant appelant
- Lors de la déconnexion du service, sa méthode onUnbind() est appelée ; si cette méthode retourne true, une prochaine connexion appelera onRebind() et non onBind().
Cycle de vie d'un service
- Le service peut être tué par le système pour pénurie de ressources.
- Sa susceptibilité d'être tué dépend de sa priorité.
-
Par défaut un service hérite de la plus forte priorité des contextes qui sont connectés
- Il est possible de changer le comportement par défaut (drapeaux lors de bindService(), passage d'une activité en 1er plan avec notification
-
Les méthodes du service doivent avoir une exécution rapide ; pour les longs travaux, on utilise des threads séparées
- Il faut interrompre les threads de travail dans onDestroy()
AIDL
Utilisation de AIDL
- Android Interface Definition Language (AIDL) = langage de définition d'interface (IDL) pour évocation distante de méthodes (RPC)
-
Procédé
- Définition des méthodes d'un service à exposer dans un fichier AIDL
- Génération d'un proxy (client) pour l'appel des méthodes et d'un stub (serveur) pour l'implantation d'un service avec méthodes appelables
- A l'exécution, le proxy envoie un message de son binder vers celui du service ; le service lui répond ; les messages sont constitués de types primitifs sérialisés dans des paquets (Parcel).
Langage AIDL
-
Une interface AIDL ressemble à une interface Java à quelques exceptions près :
- seulement des méthodes, pas de champ statique, pas de modificateurs
- les types primitifs (ainsi que String, CharSequence, List<T> implanté comme une ArrayList<T>, Map non générique comme une HashMap) sont utilisables sans précaution particulière
- pas de support des exceptions
- pas de support d'héritage d'interface
- les classes Java utilisées comme type de retour ou arguments doivent implanter l'interface Parcelable ; les types doivent être préfixés par in, out ou inout selon la directionnalité de la communication des objets
- Une interface AIDL est compilable en classe Java fournissant une souche (Stub), i.e. une classe abstraite Java offrant un serveur RPC avec méthodes à définir ; il faut redéfinir cette classe et la retourner par la méthode onBind() du service.
Parcelable
- Parcelable : pour la sérialisation/désérialisation de classes Java par le mécanisme d'IPC
-
Une classe T implantant Parcelable doit :
- Implanter une méthode void writeToParcel(Parcel out) pour sérialiser ses données dans un Parcel
- Implanter une méthode int describeContents() retournant 0
-
Avoir un champ static final CREATOR de type Parcelable.Creator<T> avec les méthodes implantées :
- T createFromParcel(Parcel source) pour désérialiser une instance depuis un Parcel
- T[] newArray(int size) : pour créer un nouveau tableau de références nulles de taille size de type TInvocation de méthodes distances sur un service Android
- Un en-tête dans un fichier .aidl doit être présent pour chaque classe complexe référencée dans une interface AIDL
Une bibliothèque matricielle
Classe Matrix serialisable dans un Parcel
// Code sample from Coursand [http://igm.univ-mlv.fr/~chilowi/], under the Apache 2.0 License
package fr.upem.matrixlibrary;
import java.util.Arrays;
import android.os.Parcel;
import android.os.Parcelable;
/* A matrix of integers */
public class Matrix implements Parcelable
{
private final int rows, cols;
private final int[][] content;
public Matrix(int rows, int cols)
{
this.rows = rows; this.cols = cols;
this.content = new int[rows][];
// Initialize the matrix
for (int i = 0; i < rows; i++) this.content[i] = new int[cols];
}
public static Matrix createIdentity(int n)
{
Matrix m = new Matrix(n, n);
for (int i = 0; i < n; i++) m.set(i, i, 1);
return m;
}
public int getRows() { return rows; }
public int getCols() { return cols; }
public int get(int i, int j) { return content[i][j]; }
public void set(int i, int j, int value) { content[i][j] = value; }
@Override public int describeContents() { return 0; }
@Override
public void writeToParcel(Parcel dest, int flags)
{
dest.writeInt(rows);
dest.writeInt(cols);
// Write the matrix row by row
for (int i = 0; i < rows; i++)
for (int j = 0; j < cols; j++)
dest.writeInt(content[i][j]);
}
public static final Creator<Matrix> CREATOR =
new Creator<Matrix>()
{
@Override
public Matrix createFromParcel(Parcel source)
{
int rows = source.readInt(), cols = source.readInt();
Matrix m = new Matrix(rows, cols);
for (int i = 0; i < rows; i++)
for (int j = 0; j < cols; j++)
m.set(i, j, source.readInt());
return m;
}
@Override
public Matrix[] newArray(int size)
{
return new Matrix[size];
}
};
@Override
public String toString()
{
return Arrays.deepToString(content);
}
}
En-tête AIDL pour la classe Matrix
// Code sample from Coursand [http://igm.univ-mlv.fr/~chilowi/], under the Apache 2.0 License
package fr.upem.matrixlibrary;
import java.util.Arrays;
import android.os.Parcel;
import android.os.Parcelable;
/* A matrix of integers */
public class Matrix implements Parcelable
{
private final int rows, cols;
private final int[][] content;
public Matrix(int rows, int cols)
{
this.rows = rows; this.cols = cols;
this.content = new int[rows][];
// Initialize the matrix
for (int i = 0; i < rows; i++) this.content[i] = new int[cols];
}
public static Matrix createIdentity(int n)
{
Matrix m = new Matrix(n, n);
for (int i = 0; i < n; i++) m.set(i, i, 1);
return m;
}
public int getRows() { return rows; }
public int getCols() { return cols; }
public int get(int i, int j) { return content[i][j]; }
public void set(int i, int j, int value) { content[i][j] = value; }
@Override public int describeContents() { return 0; }
@Override
public void writeToParcel(Parcel dest, int flags)
{
dest.writeInt(rows);
dest.writeInt(cols);
// Write the matrix row by row
for (int i = 0; i < rows; i++)
for (int j = 0; j < cols; j++)
dest.writeInt(content[i][j]);
}
public static final Creator<Matrix> CREATOR =
new Creator<Matrix>()
{
@Override
public Matrix createFromParcel(Parcel source)
{
int rows = source.readInt(), cols = source.readInt();
Matrix m = new Matrix(rows, cols);
for (int i = 0; i < rows; i++)
for (int j = 0; j < cols; j++)
m.set(i, j, source.readInt());
return m;
}
@Override
public Matrix[] newArray(int size)
{
return new Matrix[size];
}
};
@Override
public String toString()
{
return Arrays.deepToString(content);
}
}
Définition AIDL pour un service de multiplication matricielle
// Code sample from Coursand [http://igm.univ-mlv.fr/~chilowi/], under the Apache 2.0 License
package fr.upem.matrixlibrary;
import java.util.Arrays;
import android.os.Parcel;
import android.os.Parcelable;
/* A matrix of integers */
public class Matrix implements Parcelable
{
private final int rows, cols;
private final int[][] content;
public Matrix(int rows, int cols)
{
this.rows = rows; this.cols = cols;
this.content = new int[rows][];
// Initialize the matrix
for (int i = 0; i < rows; i++) this.content[i] = new int[cols];
}
public static Matrix createIdentity(int n)
{
Matrix m = new Matrix(n, n);
for (int i = 0; i < n; i++) m.set(i, i, 1);
return m;
}
public int getRows() { return rows; }
public int getCols() { return cols; }
public int get(int i, int j) { return content[i][j]; }
public void set(int i, int j, int value) { content[i][j] = value; }
@Override public int describeContents() { return 0; }
@Override
public void writeToParcel(Parcel dest, int flags)
{
dest.writeInt(rows);
dest.writeInt(cols);
// Write the matrix row by row
for (int i = 0; i < rows; i++)
for (int j = 0; j < cols; j++)
dest.writeInt(content[i][j]);
}
public static final Creator<Matrix> CREATOR =
new Creator<Matrix>()
{
@Override
public Matrix createFromParcel(Parcel source)
{
int rows = source.readInt(), cols = source.readInt();
Matrix m = new Matrix(rows, cols);
for (int i = 0; i < rows; i++)
for (int j = 0; j < cols; j++)
m.set(i, j, source.readInt());
return m;
}
@Override
public Matrix[] newArray(int size)
{
return new Matrix[size];
}
};
@Override
public String toString()
{
return Arrays.deepToString(content);
}
}
Implantation du service
On implante la souche de IMatrixMultiplier. La souche a été auto-générée depuis l'interface AIDL.
// Code sample from Coursand [http://igm.univ-mlv.fr/~chilowi/], under the Apache 2.0 License
package fr.upem.matriservice;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import fr.upem.matrixlibrary.IMatrixMultiplier;
import fr.upem.matrixlibrary.Matrix;
/** A service providing multiplications on matrices */
public class MatrixMultiplierService extends Service
{
private final IBinder multiplier = new IMatrixMultiplier.Stub()
{
@Override
public Matrix pow(Matrix m, int power) throws RemoteException
{
if (power == 0) return Matrix.createIdentity(m.getRows());
if (power % 2 == 0)
{
Matrix m2 = pow(m, power / 2);
return multiply(m2, m2);
} else
{
// power % 2 == 1
return multiply(m, pow(m, power-1));
}
}
@Override
public Matrix multiply(Matrix m1, Matrix m2) throws RemoteException
{
if (m1.getRows() != m2.getCols())
throw new IndexOutOfBoundsException("Dimension of matrices not compatible");
Matrix r = new Matrix(m1.getRows(), m2.getCols());
for (int i = 0; i < r.getRows(); i++)
for (int j = 0; j < r.getCols(); j++)
{
int v = 0;
for (int k = 0; k < m1.getCols(); k++)
v += m1.get(i, k) * m2.get(k, j);
r.set(i, j, v);
}
return r;
}
};
@Override
public IBinder onBind(Intent arg0) { return multiplier; }
}
Connexion au service
ServiceConnection conn = new ServiceConnection()
{
@Override public void onServiceDisconnected(ComponentName name)
{
Toast.makeText(MatrixMultiplier.this, "Disconnected", Toast.LENGTH_LONG).show();
}
@Override public void onServiceConnected(ComponentName name, IBinder service)
{
Toast.makeText(MyActivity.this, "Connected", Toast.LENGTH_LONG).show();
IMatrixMultiplier matmul = IMatrixMultiplier.Stub.asInterface(service) ;
Matrix m = new Matrix(2,2);
m.set(0,0, 1); m.set(0,1, 2);
m.set(1,0, 3); m.set(1,1, 4);
Matrix r = null;
try {
r = matmul.multiply(m, m);
} catch (RemoteException e) {
e.printStackTrace();
}
if (r != null)
t.setText(r.toString());
}
};
boolean connected =
bindService(new Intent().setClassName("fr.upemlv.matriservice",
"fr.upemlv.matriservice.MatrixMultiplierService"),
conn, Context.BIND_AUTO_CREATE);
Service local
Inutile de mettre en œuvre une interface AIDL si le service n'est utilisé que localement dans l'application.
public class LocalService extends Service
{
public class LocalBinder extends Binder
{
LocalService getService()
{
return LocalService.this ;
}
}
private final IBinder binder = new LocalBinder() ;
@Override public IBinder onBind(Intent intent)
{
return binder;
}
...
}
On peut ensuite récupérer le LocalBinder dès la connexion au service et récupérer avec la méthode getService() l'instance du LocalService (et ainsi potentiellement manipuler tous ses champs et méthodes publiques).