Android : Le fournisseur de contenu

30 décembre 2012 rdorigny 0 commentaires

Nous avons vu dans les chapitres précédents les méthodes pour conserver des données dans une application (PreferedShared & SQLite). Le problème est que ces données ne sont pas partageables. Sous Android, seul l'application a accès à ses propres données stockées. Or, il y a de nombreux cas, où partager des données avec les autres applications est intéressant.

Android fournit une méthodologie particulière pour partager ces données, ce sont les fournisseurs de contenu.

Nous verrons que nativement Android en propose quelques uns, mais il est possible également pour le développeur de les créer. Et donc, par ce biais de proposer un service.

1)Accéder au fournisseur de contenu

1.1)Généralités

De plus en plus, il est nécessaire dans un monde ouvert de partager nos données. Ici, on agit alors comme un fournisseur de contenu, en anglais on parle de provider. L'objectif du fournisseur de contenu masque le format des données, seul le concepteur connait la structure.

Android fournit une classe android.content.COnntentResolver pour accéder à ses fournisseurs de contenu.

ContentResolver contentResolver=getContentResolver();


Les URI, kézako?
Chaque fournisseur de contenu est identifié par une URI (Uniform Resource Identifier). L'URI est une chaîne de caractère qui identifie des ressources sur le réseau ou localement. Nous allons les utiliser abondamment dans l'utilisation du développement d'application Android.

L'URI est constitué de 3 parties:

  • protocole : content,
  • autority : c'est le fournisseur que l'on rattache au nom package, exemple : fr.doritique.provider,
  • chemin : type de données et éventuellement l'identifiant unitaire _ID.

  • Donc de la forme : content://fr.doritique.provider/data/12

    A noter que l'URI:
  • content://fr.doritique.provider/data : pour accéder aux données,
  • content://fr.doritique.provider/data/12 : pour accéder aux données de l'_ID 12,

  • A noter qu'Android a des fournisseurs prédéfinis avec API qui définissent des constantes (exemple : ContactsContract.contacts.CONTENT_URI est l'URI de base pour les contacts ou Browser.BOOKMARK_URI pour les signets du navigateur).

    Les fournisseurs sont des tableaux des données à partager avec en première colonne le champ _ID. Nous allons voir que la classe ContentResolver définit des méthodes pour accéder au fournisseur d'accès : query(), insert(), update() et delete(). Le retour de ces requêtes est renvoyé sous forme de Cursor que nous avons étudié au chapitre précédent avec SQLite.

    1.2)Faire une sélection

    Les fonctions sont très proches de celles vu avec la BDD SQLite. La méthode query() a un nom trompeur, car elle est utilisée pour réaliser un select SQL.

    Cursor query (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) avec :
  • uri : Uri du provider à requêter,
  • projection: les colonnes que l'on veut recevoir,
  • selection: le where de la requête SQL pour filtrer les éléments qui seront envoyés,
  • sortorder : pour trier, correspond aux champs DESC et ASC du SQL.
  • A noter qu'il existe une méthode managedQuery qui fait la même chose et gére en plus la suppression des variables de curseur.
    Nous allons voir un exemple d'usage de query en utilisant le fournisseur de contenu standard Brownser. L'objectif de notre exemple est de lister les favoris du Browser, appelé BookMark.

    La prmière chose à faire est d'autoriser notre application à lire les favoris, de base c'est interdit sous Android. Pour cela il suffit de rajouter dans le fichier manifest la ligne suivante:
    <uses-permission android:name="com.android.browser.permission.READ_HISTORY_BOOKMARKS" />

    Pour le layout principal:
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <ListView android:id="@android:id/android:list" android:layout_width="fill_parent" android:layout_height="wrap_content" /> </LinearLayout>

    Pour personnaliser une ligne de la liste : row.xml
    <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="wrap_content" > <TextView android:id="@+id/text1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="18dip" /> <TextView android:id="@+id/text2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="18dip" /> </LinearLayout>

    Le programme principal:
    package fr.doritique.provider; import android.os.Bundle; import android.provider.Browser; import android.app.Activity; import android.app.ListActivity; import android.database.Cursor; import android.view.Menu; import android.widget.SimpleCursorAdapter; import android.widget.Toast; public class MainActivity extends ListActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); String[] projection = new String[] {Browser.BookmarkColumns._ID,Browser.BookmarkColumns.TITLE, Browser.BookmarkColumns.URL}; String[] displayFields = new String[] {Browser.BookmarkColumns.TITLE, Browser.BookmarkColumns.URL}; int[] displayViews = new int[] { android.R.id.text1,android.R.id.text2 }; Cursor cur = managedQuery(android.provider.Browser.BOOKMARKS_URI,projection, null, null, null); setListAdapter(new SimpleCursorAdapter(this, android.R.layout.simple_list_item_2, cur, displayFields, displayViews)); // mCur.moveToFirst(); // int index = mCur.getColumnIndex(Browser.BookmarkColumns.TITLE); // while (mCur.isAfterLast() == false) { // Toast toast=Toast.makeText(this,mCur.getString(index),Toast.LENGTH_LONG); // toast.show(); // mCur.moveToNext(); // } } }

    Ce qui donne:

    1.3)Récupération d'une variable prédéfinie

    Une seconde méthodologie pour récupérer la valeurs d'une variable connue, consiste à utiliser des méthodes setters existantes. Par exemple, l'identifiant _ID est souvent utilisé,alors:
    long varId=ContentUris.parseId(uri)
    Uri uri=Uri.parse("content://fr.doritique.android.provider"); //ContentUris est une méthode qui permet de préparer de la requête pour _Id=12. Uri var12=ContentUris.withAppendedId(uri,12); //Requête de récupération Cursor cur=managedQuery(var12,null,null,null,null);

    1.4)Modifier des données au travers du fournisseur de contenu

    Les fournisseurs de contenus permettent l'accès aux données en lecture, mais aussi en écriture. Il est donc possible d'ajouter, de mettre à jour ou de supprimer des données. Commençons par l'ajout de données.

    Voici un exemple (qui fonctionne, pas facile avec une API qui ne cesse de changer...) pour ajouter un contact dans l'annuaire d'Android. Première chose à faire, il faut autoriser les modifications dans le fichier manifest.xml .
    <uses-permission android:name="android.permission.WRITE_CONTACTS" />

    Ensuite, le fichier de l'activité principale:
    package fr.doritique.recupcontact; import java.util.ArrayList; import android.net.Uri; import android.os.Bundle; import android.os.RemoteException; import android.provider.ContactsContract; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.CommonDataKinds.StructuredName; import android.provider.ContactsContract.Data; import android.provider.ContactsContract.RawContacts; import android.widget.Toast; import android.app.Activity; import android.content.ContentProviderOperation; import android.content.ContentProviderResult; import android.content.ContentUris; import android.content.ContentValues; import android.content.OperationApplicationException; import android.database.Cursor; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //Ne marche plus cette méthodologie est deprecated suite au changement d'API // //Préparation de la donnée à ajouter // ContentValues ins=new ContentValues(); // ins.put(ContactsContract.Contacts.DISPLAY_NAME, "Martin"); // //Insertion de notre ContentValue // Uri uri_ins=getContentResolver().insert(ContactsContract.Contacts.CONTENT_URI, ins); ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); int rawContactInsertIndex = ops.size(); ops.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI) .withValue(RawContacts.ACCOUNT_TYPE, null) .withValue(RawContacts.ACCOUNT_NAME,null ) .build()); ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactInsertIndex) .withValue(Data.MIMETYPE,Phone.CONTENT_ITEM_TYPE) .withValue(Phone.NUMBER, "9X-XXXXXXXXX") .build()); ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI) .withValueBackReference(Data.RAW_CONTACT_ID, rawContactInsertIndex) .withValue(Data.MIMETYPE,StructuredName.CONTENT_ITEM_TYPE) .withValue(StructuredName.DISPLAY_NAME, "Mike Sullivan") .build()); try { ContentProviderResult[] res = getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); } catch (RemoteException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (OperationApplicationException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }

    Ce qui donne:

    2)Créer son fournisseur de contenu

    2.1)Les bases

    Le framework Android permet également de construire ses propres fournisseurs de contenus. Deux cas d'utilisation en interne à l'application, ou externe pour permettre aux autres applications d’accéder à nos données.

    Comment faire cela:
  • 1)Créer une classe spécifique dans votre application et la faire hériter à la classe ContentProvider,
  • 2)Ensuite, il suffit de surcharger les méthodes selon le tableau ci-dessous

  • Méthode à surcharger

    Définition de la méthode

    onCreate() Cette méthode est appelée lors de la création du fournisseur de contenu, elle retourne un booléen pour signaler le succès ou non de l'opération,
    query() Cette méthode permet de faire un select SQL, les données renvoyées sont transmises par l'intermédiaire d'un objet Cursor,
    delete() Supprime un jeu de données,
    insert() Insère des données,
    update() Mise à jour de données,
    getType() Donne un retour MIME.

    2.2)Exemple de mise en oeuvre

    Le mieux comme toujours, c'est de proposer un exemple de code. Pour cela nous allons reprendre, l'application que nous avions réalisée avec SQLite dans laquel nous allons insérer notre provider que nous nommerons "monfournisseur".

    Pour la View, nous afficherons la liste des données de la base de données sans modifier le code, et nous nous contenterons simplement de faire une insertion par l’intermédiaire de notre fournisseur de contenu. A noter que nous avons choisis un exemple minimaliste car la mise en oeuvre devient très rapidement complexe et assez peu lisible.
    Pour le layout principal:
    <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical"> <EditText android:id="@+id/edt_ajouter" android:hint="Ajouter un commentaire..." android:layout_width="fill_parent" android:layout_height="wrap_content"/> <Button android:id="@+id/bt_ajouter" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Ajouter"/> </LinearLayout> <ListView android:id="@android:id/list" android:layout_width="fill_parent" android:layout_height="wrap_content"/> </LinearLayout>

    Rappelons que pour l'utilisation de SQLite on utilise une classe d'adaption:
    package fr.doritique.testprovider; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; public class CommentAdapter { public static final String KEY_ROWID="_id"; public static final String KEY_COMMENT="comment"; public static final String DATABASE_TABLE="comments"; private static final String BDD_NAME="mabase"; private static final int BDD_VERSION=1; private SQLiteDatabase db; private MybddOpenHelper dbhelper; private Context context; public CommentAdapter(Context context){ //dbhelper=new MybddOpenHelper(context, BDD_NAME, null, BDD_VERSION); this.context=context; } public CommentAdapter open(){ dbhelper=new MybddOpenHelper(context, BDD_NAME, null, BDD_VERSION); db=dbhelper.getWritableDatabase(); return this; } public void close() { dbhelper.close(); } public long savecomment(String comment){ ContentValues val=new ContentValues(); val.put(KEY_COMMENT, comment); return db.insert(DATABASE_TABLE, null, val); } public long savecomment2(ContentValues val){ //ContentValues val=new ContentValues(); //val.put(KEY_COMMENT, comment); return db.insert(DATABASE_TABLE, null, val); } public Cursor fetchAllComments(){ return db.query(DATABASE_TABLE,new String[] {KEY_ROWID,KEY_COMMENT},null,null,null,null,null); } public Cursor getCommentCurseur(int id){ Cursor cur=db.query(DATABASE_TABLE, new String[]{KEY_ROWID,KEY_COMMENT}, null, null, null, KEY_ROWID +"="+id, null); return cur; } }

    Sans oublier, la classe de de création de la base de données:
    package fr.doritique.testprovider; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteOpenHelper; public class MybddOpenHelper extends SQLiteOpenHelper{ private static final String SQL="create table comments (_id integer primary key autoincrement, comment text not null);"; private static final String BDD_NAME="mabase"; private static final int BDD_VERSION=1; public MybddOpenHelper(Context context,String nom, CursorFactory cursorfactory, int version) { super(context, BDD_NAME, null, BDD_VERSION); // TODO Auto-generated constructor stub } @Override public void onCreate(SQLiteDatabase db) { // TODO Auto-generated method stub db.execSQL(SQL); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // TODO Auto-generated method stub //db.execSQL("drop table comments;"); //onCreate(db); } }

    Mais tout cela est connu, voyons un peu la classe de création du provider monfournisseur.java . Vous noterez que la classe hérite de la classe mère ContentProvider. Ensuite, il suffit de surcharger les méthodes génériques du ContentProvider. Pour la fonction insert() dédiée à l'insertion, on utilise simplement une fonction savecomment2 de la classe adapter de la base de données. Nous avons fait simple, mais la beauté du concept, c'est que l'on peut faire à côté une multitude de truc en plus. Vu de nos utilisateurs il ne connaîtrons que la fonction insert().
    package fr.doritique.testprovider; import android.content.ContentProvider; import android.content.ContentValues; import android.content.UriMatcher; import android.database.Cursor; import android.net.Uri; public class monfournisseur extends ContentProvider { private static final String authority="fr.doritique.testprovider.monfournisseur"; private static final String uricomments="content://"+authority+"/comments"; public static final Uri CONTENT_URI=Uri.parse(uricomments); private CommentAdapter DBAdapter; private static final int TOUS=0; private static final int UNIQUE=1; private static final UriMatcher uriMatcher; static { uriMatcher=new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI(authority, "comments", TOUS); uriMatcher.addURI(authority, "comments/#", UNIQUE); } @Override public boolean onCreate() { DBAdapter=new CommentAdapter(getContext()); DBAdapter.open(); return true; } @Override public Uri insert(Uri uri, ContentValues values) { long id=DBAdapter.savecomment2(values); return null; } public static final int COLONNE_ID=0; public static final int COLONNE_COMMENT=1; @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { switch(uriMatcher.match(uri)){ case UNIQUE: int id=0; //Récupération de l'index try{ id=Integer.parseInt(uri.getPathSegments().get(1)); } catch (Exception e) { return null; } Cursor retcuseur=DBAdapter.getCommentCurseur(id); retcuseur.setNotificationUri(getContext().getContentResolver(), uri); return retcuseur; } return null; } @Override public String getType(Uri uri) { switch (uriMatcher.match(uri)) { case TOUS: return "vnd.android.cursor.collection/comments"; case UNIQUE: return "vnd.android.cursor.item/comments"; default: throw new IllegalArgumentException("Uri non supportée: "+uri); } } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { // TODO Auto-generated method stub return 0; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // TODO Auto-generated method stub return 0; } }

    Ensuite, il nous suffira de reprendre notre activité principale et de lui demander de faire une insertion en prenant soins d'utiliser notre fonction insert()!
    package fr.doritique.testprovider; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.SimpleCursorAdapter; import android.app.ListActivity; import android.content.ContentResolver; import android.content.ContentValues; import android.database.Cursor; public class MainActivity extends ListActivity { private CommentAdapter db; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //Test d'insertion via le fournisseur de contenu!! db=new CommentAdapter(this); db.open(); ContentValues c = new ContentValues(); c.put(db.KEY_COMMENT, "Ca marche"); getContentResolver().insert(monfournisseur.CONTENT_URI, c); fetchAllComment(); Button bt_ajouter=(Button) findViewById(R.id.bt_ajouter); bt_ajouter.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { EditText edt_ajouter=(EditText) findViewById(R.id.edt_ajouter); saveComment(edt_ajouter.getText().toString()); } }); } private void saveComment(String comment){ db=new CommentAdapter(this); db.open(); if (comment.trim().length()>0) db.savecomment(comment); fetchAllComment(); } private void fetchAllComment(){ String[] from=new String[] {CommentAdapter.KEY_COMMENT}; int[] to=new int[] {android.R.id.text1}; db=new CommentAdapter(this); db.open(); Cursor c=db.fetchAllComments(); SimpleCursorAdapter adapter=new SimpleCursorAdapter(this,android.R.layout.simple_list_item_1,c,from,to); setListAdapter(adapter); } @Override public void onBackPressed() { db.close(); super.onBackPressed(); } @Override protected void onPause() { db.close(); super.onPause(); } }

    Attention, il faut aussi penser à déclarer notre fournisseur de contenu dans le fichier manifest.xml:
    <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="fr.doritique.testprovider" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="17" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="fr.doritique.testprovider.MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <provider android:enabled="true" android:exported="true" android:name="fr.doritique.testprovider.monfournisseur" android:authorities="fr.doritique.testprovider.monfournisseur" /> </application> </manifest>

    Ce qui donne après plusieurs exécution de notre application:

    Conclusion

    Le fournisseur de contenu est un outil puissant car il permet de maîtriser les données transmises. Mais en créer un reste complexe et devra être testé afin de prévoir tous les cas d'utilisation...
    En outre, Android en propose plusieurs qui sont standards, ce qui est super pratique pour accéder aux datas du mobile (contacts, sites visités, historique des appels, listes des fichiers multi-média). Evidemment, il faudra faire attention à la confidentialités des données traitées...










    Pseudonyme (obligatoire) :
    Adresse mail (obligatoire) :
    Site web :

    Pour valider votre commentaire, écrivez le texte affiché sur l'image :



    © 2017 www.doritique.fr par Robert DORIGNY