I. Mon application est un ContentProvider (fournisseur de contenu)▲
Un fournisseur de contenu est un objet à qui on passe une URIUnified Resource Identifier (Unified Resource Identifier) et qui renvoie un flux de données. À partir d'une URI, on peut effectuer les opérations CRUD sur les objets récupérés.
Ce chapitre se scinde en deux parties. La première consiste à décrire leur utilisation. La seconde à décrire comment en construire un.
I-A. Utilisation d'un ContentProvider▲
Une URI (Android) est construite de plusieurs morceaux qui, comme pour le chemin absolu d'un fichier, se décompose comme suit :
- content://identifiant d'une URI de type contenu ;
- espace de nom des données (ex. : net.stinfoservices.grh ou com.android.contacts/contacts) ;
- identifiant de l'instance de l'objet (ex. : 5).
Ainsi, content://com.android.contacts/contacts représente une liste d'instances de contacts et content://com.android.contacts/contacts/5 représente le contact dont l'identifiant est 5.
La méthode statique Uri.parse permet de construire un objet URI à partir d'une chaîne de caractères.
Les fournisseurs définissent leurs URI en tant que constante publique de leur classe, CONTENT_URI, ce qui est une bonne pratique.
À partir de cette URI, il est alors possible d'obtenir les données du fournisseur associé. Ce mécanisme d'appel est très proche du SQL. Deux façons de l'aborder sont possibles :
- L'une est une méthode de la classe Activity et permet simplement d'accéder aux données, manageQuery().
- L'autre est plus adaptée à la manipulation des ContentProvider et passe par la classe ContentResolver et sa méthode query().
La deuxième solution est celle qui est préconisée, car avec l'objet ContentResolver, il est possible de consulter, de mettre à jour ou de détruire les données. Ces deux méthodes prennent en paramètres les mêmes éléments.
I-A-1. Récupérer des données▲
2.
ContentResolver cr =
getContentResolver
(
);
cr.query
(
uri, projection, selection, selectionArgs, sortOrder)
Ou bien
managedQuery
(
uri, projection, selection, selectionArgs, sortOrder)
Où les paramètres sont :
- l'URI des données du ContentProvider ;
- une projection listant les colonnes à rapatrier ;
- une clause where avec des jokers (?) facultatifs ;
- un tableau de paramètres remplaçant les jokers (?) ;
- une clause Ordrer by.
Ce qui donne, par exemple :
2.
3.
4.
5.
6.
7.
8.
9.
ContentResolver cr=
getContentResolver
(
);
//Pour ramener toutes les lignes
cr.query
(
MyProvider.CONTENT_URI, null
, null
, null
, null
);
//Pour ramener un sous-ensemble :
String[] projection =
{
Key_ColHuman_FirstName, Key_ColHuman_Name }
;
String where =
Key_ColHuman_EyesColor +
"=? AND"
+
Key_ColHuman_HairColor +
"=?"
;
String[] whereParam =
{
"blue"
,"brown"
}
;
String order =
Key_ColHuman_Name;
cr.query
(
MyProvider.CONTENT_URI, projection, where, whereParam, order);
I-A-2. Insérer des données▲
Il faut utiliser les méthodes insert ou bulkInsert, l'une ajoute un élément, l'autre un tableau d'éléments. L'une renvoie l'URI de la donnée insérée, l'autre le nombre de données insérées.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
ContentResolver cr =
getContentResolver
(
);
//Pour insérer une ligne
ContentValues newLine =
new
ContentValues
(
);
newLine.put
(
Column_Name, newValue);
Uri myDataURI=
cr.insert
(
MyProvied.CONTENT_URI, newLine);
//Pour insérer n lignes
ContentValues[]newLines=
new
ContentValues[5
];
//Remplir le tableau… et insérer les valeurs
Int insertedLines=
cr.bulkInsert
(
MyProvied.CONTENT_URI, newLines);
Javadoc :
public
final
Uri insert (
Uri url, ContentValues values)
Inserts a row into a table at the given URL. If the content provider supports transactions the insertion will be atomic.
Parameters
url |
The URL of the table to insert into. |
values |
The initial values for the newly inserted row. The key is the column name for the field. Passing an empty ContentValues will create an empty row. |
Returns the URL of the newly created row.
public
final
int
bulkInsert (
Uri url, ContentValues[] values)
Inserts multiple rows into a table at the given URL. This function make no guarantees about the atomicity of the insertions.
Parameters
url |
The URL of the table to insert into. |
values |
The initial values for the newly inserted rows. The key is the column name for the field. Passing null will create an empty row. |
Returns the number of newly created rows.
I-A-3. Supprimer des données▲
Il faut utiliser la méthode delete :
2.
3.
4.
5.
6.
7.
ContentResolver cr =
getContentResolver
(
);
//Pour supprimer une ligne connue
cr.delete
(
myRowURI,null
,null
);
//Pour supprimer un sous-ensemble :
String where =
Key_ColHuman_EyesColor+
"=? AND"
+
Key_ColHuman_HairColor+
"=?"
;
String[] whereParam =
{
"blue"
,"brown"
}
;
cr.delete
(
MyProvider.CONTENT_URI, where, whereParam);
Javadoc :
public
final
int
delete (
Uri url, String where, String[] selectionArgs)
Deletes row(s) specified by a content URI. If the content provider supports transactions, the deletion will be atomic.
Parameters
url |
The URL of the row to delete. |
where |
A filter to apply to rows before deleting, formatted as an SQL WHERE clause (excluding the WHERE itself). |
Returns The number of rows deleted.
I-A-4. Mettre à jour des données▲
Il faut utiliser la méthode update :
2.
3.
4.
5.
6.
7.
8.
ContentResolver cr =
getContentResolver
(
);
ContentValues newLineValues =
new
ContentValues
(
);
//Pour mettre à jour une ligne connue
cr.update
(
myRowURI, newLineValues, null
, null
);
//Pour mettre à jour un sous-ensemble :
String where =
Key_ColHuman_EyesColor +
"=? AND"
+
Key_ColHuman_HairColor +
"=?"
;
String[] whereParam =
{
"blue"
,"brown"
}
;
cr.update
(
MyProvider.CONTENT_URI, newLineValues, where, whereParam);
Javadoc :
public
final
int
update (
Uri uri, ContentValues values, String where, String[] selectionArgs)
Update row(s) in a content URI. If the content provider supports transactions the update will be atomic.
Parameters
uri |
The URI to modify. |
values |
The new field values. The key is the column name for the field. A null value will remove an existing field value. |
where |
A filter to apply to rows before updating, formatted as an SQL WHERE clause (excluding the WHERE itself). |
Returns The number of rows updated.
Throws NullPointerException |
if uri or values are null |
I-A-5. Les ContentProviders Android▲
Les URI suivantes sont disponibles sur tous les systèmes Android :
- Browser.BOOKMARKS_URI pour la gestion des favoris du navigateur web ;
- Browser.SEARCHES_URI pour la gestion des recherches du navigateur web ;
- CallLog.CONTENT_URI pour la gestion des appels entrants, sortants ou manqués avec l'identifiant du numéro et la durée de l'appel ;
- Settings.CONTENT_URI pour la gestion des préférences de l'appareil, mais il est plus pertinent de faire appel aux Intents de cette classe pour laisser l'utilisateur modifier lui-même ses préférences ;
- UserDictionary.CONTENT_URI pour la gestion des mots définis par l'utilisateur ;
- UserDictionary.Words.CONTENT_URI pour la gestion des mots définis par l'utilisateur.
Deux autres classes possèdent un ensemble d'URI permettant une gestion fine de leurs entités. Les URI sont les suivantes, à vous de les découvrir et de chercher leur utilisation la plus pertinente.
I-A-5-a. La classe ContactsContract▲
La classe ContactsContract qui permet de gérer les Contacts :
- ContactsContract.AUTHORITY_URI ;
- ContactsContract.Contacts.CONTENT_URI ;
- ContactsContract.Contacts.CONTENT_FILTER_URI ;
- ContactsContract.Contacts.CONTENT_GROUP_URI ;
- ContactsContract.Contacts.CONTENT_LOOKUP_URI ;
- ContactsContract.Contacts.CONTENT_STREQUENT_FILTER_URI ;
- ContactsContract.Contacts.CONTENT_STREQUENT_URI ;
- ContactsContract.Contacts.CONTENT_VCARD_URI ;
- ContactsContract.AggregationExceptions.CONTENT_URI ;
- ContactsContract.Data.CONTENT_URI ;
- ContactsContract.Groups.CONTENT_SUMMARY_URI ;
- ContactsContract.Groups.CONTENT_URI ;
- ContactsContract.PhoneLookup.CONTENT_FILTER_URI ;
- ContactsContract.RawContacts.CONTENT_URI ;
- ContactsContract.RawContactsEntity.CONTENT_URI ;
- ContactsContract.Settings.CONTENT_URI ;
- ContactsContract.StatusUpdates.CONTENT_URI ;
- ContactsContract.SyncState.CONTENT_URI.
Quelques exemples d'utilisation du ContractsContact :
Récupérer tous les numéros de téléphone et les adresses mail de vos contacts :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
//Récupérer la liste des contacts
Cursor cursor =
getContentResolver
(
).query
(
ContactsContract.Contacts.CONTENT_URI, null
, null
, null
, null
);
while
(
cursor.moveToNext
(
)) {
String contactId =
cursor.getString
(
cursor.getColumnIndex
(
ContactsContract.Contacts._ID));
String hasPhone =
cursor.getString
(
cursor.getColumnIndex
(
ContactsContract.
Contacts.HAS_PHONE_NUMBER));
if
(
Boolean.parseBoolean
(
hasPhone)) {
// You know have the number so now query it like this
Cursor phones =
getContentResolver
(
).query
(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null
,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID +
" = "
+
contactId, null
, null
);
while
(
phones.moveToNext
(
)) {
String phoneNumber =
phones.getString
(
phones
.getColumnIndex
(
ContactsContract.CommonDataKinds.Phone.NUMBER));
}
phones.close
(
);
}
Cursor emails =
getContentResolver
(
).query
(
ContactsContract.CommonDataKinds.Email.CONTENT_URI, null
,
ContactsContract.CommonDataKinds.Email.CONTACT_ID +
" = "
+
contactId, null
, null
);
while
(
emails.moveToNext
(
)) {
// This would allow you get several email addresses
String emailAddress =
emails.getString
(
emails
.getColumnIndex
(
ContactsContract.CommonDataKinds.CommonDataColumns.DATA));
}
emails.close
(
);
}
cursor.close
(
);
Récupérer tous les numéros de téléphone et les noms de vos contacts :
2.
3.
4.
5.
6.
7.
8.
Cursor people =
getContentResolver
(
).query
(
ContactsContract.Contacts.CONTENT_URI, null
, null
, null
, null
);
while
(
people.moveToNext
(
)) {
int
nameFieldColumnIndex =
people.getColumnIndex
(
PhoneLookup.DISPLAY_NAME);
String contact =
people.getString
(
nameFieldColumnIndex);
int
numberFieldColumnIndex =
people.getColumnIndex
(
PhoneLookup.NUMBER);
String number =
people.getString
(
nameFieldColumnIndex);
}
people.close
(
);
Ou pour récupérer les notes d'un contact :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
private
String getNote
(
long
contactId) {
String note =
null
;
String[] columns =
new
String[] {
ContactsContract.CommonDataKinds.Note.NOTE }
;
String where =
ContactsContract.Data.RAW_CONTACT_ID +
" = ? AND "
+
ContactsContract.Data.MIMETYPE +
" = ?"
;
String[] whereParameters =
new
String[] {
Long.toString
(
contactId), ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE }
;
Cursor contacts =
getContentResolver
(
).query
(
ContactsContract.Data.CONTENT_URI, projection, where, whereParameters, null
);
if
(
contacts.moveToFirst
(
)) {
note =
contacts.getString
(
0
);
}
contacts.close
(
);
return
note;
}
I-A-5-b. La classe MediaStore▲
La classe MediaStore qui permet la gestion du contenu multimédia de l'appareil. Chaque URI se décline en Internal ou External (internal ou external storage), l'une pour les ressources internes de l'appareil, l'autre pour les ressources externes.
- MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI ;
- MediaStore.Audio.Albums.INTERNAL_CONTENT_URI ;
- MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI ;
- MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI ;
- MediaStore.Audio.Media.EXTERNAL_CONTENT_URI ;
- MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI ;
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI ;
- MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI ;
- MediaStore.Video.Media.EXTERNAL_CONTENT_URI ;
- MediaStore.Video.Thumbnails.EXTERNAL_CONTENT_URI ;
- MediaStore.getMediaScannerUri() pour la gestion des différents médias.
Un exemple d'utilisation de MediaStore pour récupérer la liste des chansons du lecteur externe :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
Uri media =
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
String[] projection =
{
MediaStore.Audio.Media._ID, // 0
MediaStore.Audio.Media.ARTIST, // 1
MediaStore.Audio.Media.TITLE, // 2
MediaStore.Audio.Media.ALBUM_ID, // 3
MediaStore.Audio.Media.ALBUM, // 4
MediaStore.Audio.Media.DATA, // 5
MediaStore.Audio.Media.DISPLAY_NAME, // 6
MediaStore.Audio.Media.DURATION }
; // 7
String selection =
MediaStore.Audio.Media.IS_MUSIC +
" != 0"
;
Cursor cursor =
query
(
c,media,projection,selection,null
,null
);
while
(
cursor.moveToNext
(
)){
songs.add
(
new
Music
(
cursor.getString
(
1
),
cursor.getString
(
2
),
cursor.getString
(
4
),
cursor.getString
(
7
),
cursor.getString
(
5
),
cursor.getLong
(
0
),
cursor.getLong
(
3
)));
}
return
songs;
I-B. Définition d'un ContentProvider▲
Construire un fournisseur de contenu est une activité qui se doit d'être très structurée et de répondre à un cahier des charges très strict.
Tout d'abord, la définition de votre (vos) URI. Elle se compose de la manière suivante : content://MyAuthority/pathToData/data, où :
- MyAuthority est le « nom » de votre fournisseur de contenu ;
- pathToData peut être vide ou pas (on peut avoir une arborescence pour organiser ses données) ;
- data pointe sur une instance de l'objet.
Ainsi, vous pouvez avoir des URI à rallonge ou toutes simples, cela dépend de vos données et de la structuration que vous mettez en place. Android préconise de mettre comme Authority le package de la classe qui implémente votre provider pour assurer l'unicité. Pour une URI qui possède des sous-tables, l'utilisation des pathToData est obligatoire, elle assure l'unicité de l'Authority.
Vous devez toujours mettre vos URI en final static URI CONTENT_URI pour permettre à vos utilisateurs d'avoir une visibilité simple sur celles-ci. Si vous avez plusieurs URI, faites comme ContactsContract et mettez en place des sous-classes publiques à votre fournisseur et pour chacune, déclarez le CONTENT_URI correspondant.
L'exemple d'Android est parfait :
Où :
- Est le préfixe standard qui indique que les données sont gérées par un ContentProvider ;
- L'Authority du ContentProvider, elle définit ce provider. Elle se retrouve dans votre manifest.xml ainsi que dans votre classe sous la constante CONTENT_URI. Par convention, elle est le package de votre provider (pour assurer l'unicité) ;
- Le chemin utilisé par le content provider pour déterminer quel type de données est demandé ;
- L'identifiant (the ID) d'un enregistrement spécifique.
Chaque URI est mappée à un type MIME. Ainsi, une URI qui pointe vers un ensemble de données aura pour type dir, et une URI qui pointe vers une donnée aura pour type item. Pour être plus précis, le premier aura un type vnd.X.cursor.dir/Y, le second un type vnd.X.cursor.item/Y où :
- X est le nom du projet (un seul nom, pas de slash, pas de point ; si celui-ci est spécifique à Android, pourquoi ne pas mettre android ?) ;
- Y est de type vnd.myCompany.contentType (vnd.net.stinfoservces.employee).
Dans la suite de ce chapitre, nous nous appuyons sur l'exemple où vous exposez des données nommées MyData au moyen d'un ContentProvider appelé MyDataProvider.
Enfin, toutes vos données ont un identifiant (ID). En d'autres termes, chacune de vos données exposées possède un champ ID. Si vous travaillez avec une base de données, chacune de vos tables possède donc une colonne ID (qui est de type Integer et s'auto-incrémente).
I-B-1. Création de MyProvider extends ContentProvider▲
Étendre la classe ContentProvider impose l'implémentation des six méthodes suivantes :
- onCreate ;
- getType ;
- query ;
- insert ;
- update ;
- delete.
I-B-1-a. Les Constantes implémentent BaseColumns▲
Votre fournisseur de données se doit d'exposer toutes ces constantes utiles. Au lieu de les exposer en tant que constantes simples de votre classe, une bonne pratique est de les encapsuler dans une classe public static qui implémente BaseColumns. Vous pouvez appeler cette classe du nom de vos données ou juste Constant. C'est une bonne pratique, mais personne ne vous en voudra si vous les mettez à la racine de votre provider.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
public
class
MyDataProvider extends
ContentProvider {
// Mon Authority, mes paths to data et mon URI
// The Authority
public
static
final
String AUTHORITY =
"myAuthority"
;//package de la classe provider est préconisé (utilise des .)
// The path to the data… and explain
public
static
final
String PATH_TO_DATA =
"myPath"
; //Vous pouvez déclarer plusieurs paths (les paths utilisent les /)
// This class aims to show the constant to use for the MyDataProvider
public
class
MyDatas extends
BaseColumns {
// The URI and explain, with example if you want
public
static
final
URI CONTENT_URI =
URI.parse
(
"content://"
+
MyDataProvider.AUTHORITY +
"/"
+
MyDataProvider.PATH_TO_DATA);
// Mon MIME type
// My Column ID and the associated explanation
public
static
final
String MIME_COLLECTION =
"vnd.android.cursor.dir/vnd.myCompany.contentType"
// My Column ID and the associated explanation
public
static
final
String MIME_ITEM =
"vnd.android.cursor.item/vnd.myCompany.contentType"
// Noms de colonnes
// /!\Si vous utilisez une base de données, les noms des colonnes sont les mêmes que ceux de votre base, de même pour les index.
// My Column ID and the associated explanation for end-users
public
static
final
String KEY_COL_ID =
"_id"
; // Mandatory
// My Column Name and the associated explanation for end-users
public
static
final
String KEY_ COL_NAME =
"name"
;
// My Column First Name and the associated explanation for end-users
public
static
final
String KEY_ COL_FIRSTNAME =
"firstName"
;
// My Column Eyes Color and the associated explanation for end-users
public
static
final
String KEY_ COL_EYES_COLOR =
"eyesColor"
;
// My Column Hair color and the associated explanation for end-users
public
static
final
String KEY_ COL_HAIR_COLOR =
"hairColor"
;
// My Column age and the associated explanation for end-users
public
static
final
String KEY_ COL_AGE =
"age"
;
// Index des colonnes
// The index of the column ID
public
static
final
int
ID_COLUMN =
1
;
// The index of the column NAME
public
static
final
int
NAME_COLUMN =
2
;
// The index of the column FIRST NAME
public
static
final
int
FIRSTNAME_COLUMN =
3
;
// The index of the column EYES COLOR
public
static
final
int
EYES_COLOR_COLUMN =
4
;
// The index of the column HAIR COLOR
public
static
final
int
HAIR_COLOR_COLUMN =
5
;
// The index of the column AGE
public
static
final
int
AGE_COLUMN =
6
;
}
...
}
I-B-1-b. onCreate▲
Vous devez mettre en place l'initialisation de votre fournisseur de données dans cette classe. En d'autres termes, vous initialisez l'accès à vos données : soit vers votre base de données, soit vers un fichier, un flux RSS, un site web…
I-B-1-c. getType▲
Vous devez renvoyer le type associé à l'URI (et donc collection ou item).
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
public
String getType
(
Uri uri) {
switch
(
uriMatcher.match
(
uri)) {
case
COLLECTION: return
"vnd.android.cursor.dir/vnd.myCompany.contentType "
;
case
ITEM: return
"vnd.android.cursor.item/vnd.myCompany.contentType "
;
default
:
throw
new
IllegalArgumentException
(
"URI non supportée : "
+
uri);
}
}
// Crée les constantes utilisées pour différencier les requêtes URI
private
static
final
int
ITEM =
1
;
private
static
final
int
COLLECTION =
2
;
private
static
final
UriMatcher uriMatcher;
// Alloue l'objet UriMatcher.
// Une URI terminée par myPathToData correspondra à une requête sur une collection.
// Une URI terminée par'/[rowID]' correspondra à un item.
static
{
uriMatcher =
new
UriMatcher
(
UriMatcher.NO_MATCH);
uriMatcher.addURI
(
"MyAuthority"
, "myPathToData"
, COLLECTION);
uriMatcher.addURI
(
"MyAuthority "
, " myPathToData /#"
, ITEM);
}
I-B-1-d. query▲
Cette méthode doit renvoyer un Cursor en fonction de l'URI passée en paramètre, de la projection, de la clause where ainsi que de la clause OrderBy.
Quand votre provider s'appuie sur une base de données, cela est assez simple, l'utilisation du SQLiteBuilder est préconisée (cf. l'exemple). Sinon, c'est à vous de vous lancer dans une analyse de la requête pour obtenir les résultats attendus.
L'URI permet de savoir si on se place dans le contexte d'une collection ou pas. Dans une collection, l'utilisation paraît adaptée ; pour un item, la requête vise à vérifier que l'item passé en paramètre correspond à la clause where. Il n'y a pas de préconisation globale, à vous de réfléchir à ce problème. Ce questionnement se pose pour les méthodes insert, update, query et delete.
I-B-1-e. Insert▲
Cette méthode insère un objet dans votre contenu. C'est à vous de le mettre en place.
Si vous ne voulez pas d'insertion, vous pouvez renvoyer l'exception :
throw
new
UnsupportedOperationException
(
);
I-B-1-f. Update▲
Cette méthode met à jour un objet ou une collection.
I-B-1-g. Delete▲
Cette méthode supprime un objet ou une collection.
I-B-1-h. Manifest▲
Il faut modifier votre manifeste de manière à référencer votre provider de la manière suivante :
2.
<provider
android
:
name
=
".FullyQualifiedNameOfTheContentProvider"
android
:
authorities
=
"TheAuthorityOfTheProvider"
/>
Vous pouvez mettre une liste d'autorités séparées par des points-virgules.
I-B-1-i. Mise en place de listeners de modifications▲
Les clients de votre fournisseur de contenu peuvent s'abonner aux modifications des données.
Pour cela votre ContentProvider doit appeler sa méthode getContext().getContentResolver().notifyChange() quand l'une de ses données est mise à jour. Il faut passer à cette méthode l'URI du contenu modifié ainsi qu'un ContentObserver. Ce ContentObserver ne sera pas notifié de la modification. Ainsi, si vous n'implémentez pas de modification de données via une partie tierce de votre application, ce paramètre vaudra null.
Les utilisateurs du provider, eux, se doivent d'effectuer plusieurs opérations.
D'une part, il faut qu'ils possèdent une classe (interne ou pas) qui étende ContentObserver. Cette classe a en charge l'écoute des changements et possède deux méthodes.
L'une, deliverSelfNotifications, lui permet de spécifier si elle est intéressée par les changements qu'elle fait sur les curseurs sur lesquels l'observer est enregistré.
L'autre, onChange, lui permet d'être informée qu'un changement vient de se produire sur les données qu'elle observe.
D'autre part, il faut que l'activité qui écoute instancie un Handler qui sera informé des modifications via le ContentObserver et mettra à jour votre activité. Il faut donc lui réécrire sa méthode handleMessage.
Enfin, votre activité doit s'enregistrer comme écouteuse de changement de votre provider :
getContentResolver
(
).registerContentObserver
(
URI, true
, new
MyContentObserver
(
handler));
Ce qui donne, dans le ContentProvider :
getContext
(
).getContentResolver
(
).notifyChange
(
CONTENT_URI\pathToData\Item);
Dans l'écouteur :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
public
class
MyActivity extends
Activity {
...
// The URI of the ContentProvider
URI ContentProviderURI=
new
URI
(
...);
// The handler to manage modifications of data in a thread safe way
Handler myHandler =
new
Handler
(
) {
@Override
public
void
handleMessage
(
Message msg) {
super
.handleMessage
(
msg);
//update your data using the Handler Android pattern
...
}
}
;
...
@Override
public
void
onCreate
(
Bundle icicle) {
...
getContentResolver
(
).registerContentObserver
(
ContentProviderURI, true
,
new
myContentObserver
(
myHandler);
...
}
...
//The inner public class to observe the change on the data of the content Provider
public
class
MyContentObserver extends
ContentObserver {
@Override
public
boolean
deliverSelfNotifications
(
) {
return
super
.deliverSelfNotifications
(
);
}
@Override
public
void
onChange
(
boolean
selfChange) {
super
.onChange
(
selfChange);
//And then make a call to your handler
handler.sendMessage
(
new
Message
(
));
}
}
...
}
Ou de manière plus synthétique :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
public
class
MyActivity extends
Activity {
...
// The URI of the ContentProvider
URI ContentProviderURI =
new
URI
(
...);
// The handler to manage modifications of data in a thread safe way
Handler myHandler =
new
Handler
(
) {
@Override
public
void
handleMessage
(
Message msg) {
super
.handleMessage
(
msg);
// Update your data using the Handler Android pattern
...
}
}
;
...
@Override
public
void
onCreate
(
Bundle icicle) {
... // TheContent Observer
ContentObserver myContentObserver =
new
ContentObserver (
myHandler){
@Override
public
void
onChange
(
boolean
selfChange) {
super
.onChange
(
selfChange);
// And then make a call to your handler
handler.sendMessage
(
new
Message
(
));
}
;
// Then register your Activity as a content observer
getContentResolver
(
).registerContentObserver
(
ContentProviderURI, true
, myContentObserver);
...
}
;
...
}
I-B-1-j. Exemple avec une base de données▲
L'intérêt de cet exemple est double : d'une part, il présente un ContentProvider complet, de l'autre il montre l'utilisation de la classe SQLiteQueryBuilder.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
225.
226.
227.
228.
229.
230.
231.
232.
233.
234.
235.
236.
237.
238.
239.
240.
241.
242.
/**
*
@author
mSeguy
* @goals This class aims to make a exemple of a ContentProvider that uses SQLite
*
<
ul
>
*
<
li
><
/li
>
*
<
/ul
>
*/
public
class
MyDataProvider extends
ContentProvider {
// *************************************************************************************
// The Constants declaration ************************************************************
// *************************************************************************************
// Mon Authority, mes paths to data et mon URI
// The Authority
public
static
final
String AUTHORITY =
"myAuthority"
;// package de la classe provider est préconisé (utilise des .)
// The path to the data … and explain
public
static
final
String PATH_TO_DATA =
"myPath"
; // Vous pouvez déclarer plusieurs paths (les paths utilisent les /)
/**
*
@author
mSeguy
* @goals This class aims to show the constant to use for the MyDataProvider
*/
public
static
class
Constants implements
BaseColumns {
// The URI and explain, with example if you want
public
static
final
Uri CONTENT_URI =
Uri.parse
(
"content://"
+
MyDataProvider.AUTHORITY +
"/"
+
MyDataProvider.PATH_TO_DATA);
// Mon MIME type
// My Column ID and the associated explanation
public
static
final
String MIME_COLLECTION =
"vnd.android.cursor.dir/vnd.myCompany.contentType"
;
// My Column ID and the associated explanation
public
static
final
String MIME_ITEM =
"vnd.android.cursor.item/vnd.myCompany.contentType"
;
// Noms de colonnes
// /!\Si vous utilisez une base de données, les noms des colonnes sont les mêmes que ceux de
// votre base, de même pour les index.
// My Column ID and the associated explanation for end-users
public
static
final
String KEY_COL_ID =
"_id"
;// Mandatory
// My Column Name and the associated explanation for end-users
public
static
final
String KEY_COL_NAME =
"name"
;
// My Column First Name and the associated explanation for end-users
public
static
final
String KEY_COL_FIRSTNAME =
"firstName"
;
// My Column Eyes Color and the associated explanation for end-users
public
static
final
String KEY_COL_EYES_COLOR =
"eyesColor"
;
// My Column Hair color and the associated explanation for end-users
public
static
final
String KEY_COL_HAIR_COLOR =
"hairColor"
;
// My Column age and the associated explanation for end-users
public
static
final
String KEY_COL_AGE =
"age"
;
// Indexes des colonnes
// The index of the column ID
public
static
final
int
ID_COLUMN =
1
;
// The index of the column NAME
public
static
final
int
NAME_COLUMN =
2
;
// The index of the column FIRST NAME
public
static
final
int
FIRSTNAME_COLUMN =
3
;
// The index of the column EYES COLOR
public
static
final
int
EYES_COLOR_COLUMN =
4
;
// The index of the column HAIR COLOR
public
static
final
int
HAIR_COLOR_COLUMN =
5
;
// The index of the column AGE
public
static
final
int
AGE_COLUMN =
6
;
}
// Back To our provider
@Override
public
boolean
onCreate
(
) {
Context context =
getContext
(
);
MyDatabaseHelper dbHelper =
new
MyDatabaseHelper
(
context, DATABASE_NAME, null
,
DATABASE_VERSION);
myDB =
dbHelper.getWritableDatabase
(
);
return
(
myDB ==
null
) ? false
: true
;
}
@Override
public
Cursor query
(
Uri uri, String[] projection, String selection, String[] selectionArgs, String sort) {
SQLiteQueryBuilder qb =
new
SQLiteQueryBuilder
(
);
qb.setTables
(
MY_TABLE);
// S'il s'agit d'une requête sur une ligne, on limite le résultat.
switch
(
uriMatcher.match
(
uri)) {
case
ITEM:
qb.appendWhere
(
Constants.KEY_COL_ID +
"="
+
uri.getPathSegments
(
).get
(
1
));
break
;
default
:
break
;
}
// Si aucun ordre de tri n'est spécifié, tri par date/heure
String orderBy;
if
(
TextUtils.isEmpty
(
sort)) {
orderBy =
Constants.KEY_COL_NAME;
}
else
{
orderBy =
sort;
}
// Applique la requête à la base.
Cursor c =
qb.query
(
myDB, projection, selection, selectionArgs, null
, null
, orderBy);
// Enregistre le ContextResolver pour qu'il soit averti
// si le résultat change.
c.setNotificationUri
(
getContext
(
).getContentResolver
(
), uri);
// Renvoie un curseur.
return
c;
}
@Override
public
Uri insert
(
Uri _uri, ContentValues _initialValues) {
// Insère la nouvelle ligne. Renvoie son numéro en cas de succès
long
rowID =
myDB.insert
(
MY_TABLE, "data"
, _initialValues);
// Renvoie l'URI de la nouvelle ligne.
if
(
rowID >
0
) {
Uri uri =
ContentUris.withAppendedId
(
Constants.CONTENT_URI, rowID);
getContext
(
).getContentResolver
(
).notifyChange
(
uri, null
);
return
uri;
}
throw
new
SQLException
(
"Echec de l'ajout d'une ligne dans "
+
_uri);
}
@Override
public
int
delete
(
Uri uri, String where, String[] whereArgs) {
int
count;
switch
(
uriMatcher.match
(
uri)) {
case
COLLECTION:
count =
myDB.delete
(
MY_TABLE, where, whereArgs);
break
;
case
ITEM:
String segment =
uri.getPathSegments
(
).get
(
1
);
count =
myDB.delete
(
MY_TABLE, KEY_ID +
"="
+
segment
+
(!
TextUtils.isEmpty
(
where) ? " AND ("
+
where +
')'
: ""
), whereArgs);
break
;
default
:
throw
new
IllegalArgumentException
(
"URI non supportée : "
+
uri);
}
getContext
(
).getContentResolver
(
).notifyChange
(
uri, null
);
return
count;
}
@Override
public
int
update
(
Uri uri, ContentValues values, String where, String[] whereArgs) {
int
count;
switch
(
uriMatcher.match
(
uri)) {
case
COLLECTION:
count =
myDB.update
(
MY_TABLE, values, where, whereArgs);
break
;
case
ITEM:
String segment =
uri.getPathSegments
(
).get
(
1
);
count =
myDB.update
(
MY_TABLE, values, Constants.KEY_COL_ID +
"="
+
segment
+
(!
TextUtils.isEmpty
(
where) ? " AND ("
+
where +
')'
: ""
), whereArgs);
break
;
default
:
throw
new
IllegalArgumentException
(
"URI inconnue "
+
uri);
}
getContext
(
).getContentResolver
(
).notifyChange
(
uri, null
);
return
count;
}
@Override
public
String getType
(
Uri uri) {
switch
(
uriMatcher.match
(
uri)) {
case
COLLECTION:
return
"vnd.android.cursor.dir/vnd.myCompany.contentType "
;
case
ITEM:
return
"vnd.android.cursor.item/vnd.myCompany.contentType "
;
default
:
throw
new
IllegalArgumentException
(
"URI non supportée : "
+
uri);
}
}
// Crée les constantes utilisées pour différencier les requêtes URI
private
static
final
int
ITEM =
1
;
private
static
final
int
COLLECTION =
2
;
private
static
final
UriMatcher uriMatcher;
// Alloue l'objet UriMatcher.
// Une URI terminée par myPathToData correspondra à une requête sur une collection.
// Une URI terminée par'/[rowID]' correspondra à un item.
static
{
uriMatcher =
new
UriMatcher
(
UriMatcher.NO_MATCH);
uriMatcher.addURI
(
"MyAuthority"
, "myPathToData"
, COLLECTION);
uriMatcher.addURI
(
"MyAuthority "
, " myPathToData /#"
, ITEM);
}
// Managing the database
// La base de données
private
SQLiteDatabase myDB;
private
static
final
String TAG =
"MyProvider"
;
private
static
final
String DATABASE_NAME =
"myData.db"
;
private
static
final
int
DATABASE_VERSION =
1
;
private
static
final
String MY_TABLE =
"MyTable"
;
// Classe helper pour ouvrir, créer et gérer le contrôle de version de la base
private
static
class
MyDatabaseHelper extends
SQLiteOpenHelper {
private
static
final
String DATABASE_CREATE =
"create table "
+
MY_TABLE +
" ("
+
Constants.KEY_COL_ID +
" integer primary key autoincrement, "
+
Constants.KEY_COL_AGE +
" INTEGER, "
+
Constants.KEY_COL_NAME +
" TEXT, "
+
Constants.KEY_COL_FIRSTNAME +
" TEXT, "
+
Constants.KEY_COL_EYES_COLOR+
" TEXT, "
+
Constants.KEY_COL_HAIR_COLOR +
" TEXT) "
;
public
MyDatabaseHelper
(
Context context, String name, CursorFactory factory, int
version) {
super
(
context, name, factory, version);
}
@Override
public
void
onCreate
(
SQLiteDatabase db) {
db.execSQL
(
DATABASE_CREATE);
}
@Override
public
void
onUpgrade
(
SQLiteDatabase db, int
oldVersion, int
newVersion) {
Log.w
(
TAG, "Mise à jour de la version "
+
oldVersion +
" vers la version "
+
newVersion
+
", les anciennes données seront détruites "
);
db.execSQL
(
"DROP TABLE IF EXISTS "
+
MY_TABLE);
onCreate
(
db);
}
}
}
II. Remerciements▲
Cet article a été publié avec l'aimable autorisation de la société Android2EE.
Nous tenons à remercier ced pour la relecture orthographique de cet article et Djibril et Mickael Baron pour la mise au format Developpez.com.