La combinaison de LoaderManager et MediaStore.Audio sous Android

Bonjour,

Je vais vous parler de deux sujets dans cet article :
– comment récupérer les informations concernant la musique stockée sur un téléphone android
– comment gérer un chargement en tâche de fond avec les Loader

Obtenir des informations sur vos fichiers musicaux :

Le framework android procède un ContentProvider qui fourni des informations sur la musique stockée sur l’appareil. Il est donc possible de l’interroger comme n’importe quel autre ContentProvider.
Vous trouverez toutes les constantes intéressantes dans les sous classes de MediaStore.Audio.
http://developer.android.com/reference/android/provider/MediaStore.Audio.html

Ce qui vous permettra d’avoir des informations sur les playlists, les genres musicaux, les albums, les artistes, etc…

Par exemple pour obtenir la liste de tous les artistes correspondant à votre musique. Il suffit de faire comme ceci :

public List<String> getArtistsNames(Activity act) {
	ArrayList<String> artists = new ArrayList<String>();
	
	String[] proj = { MediaStore.Audio.Artists.ARTIST };
	Cursor musicCursor = act.managedQuery(MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI,proj, null, null, null);
	
	if(musicCursor.moveToFirst()) {
		do {
			String artistName = musicCursor.getString(0);				
			artists.add(artistName);
			
			if( EtLog.isEnabled()) {
				EtLog.d(TAG,String.format("Identified Artist name is [%s]",artistName));
			}
		}
		while(musicCursor.moveToNext());
	}
	
	return artists;
}

Utiliser le LoaderManager pour gérer la recherche :

Nous allons utiliser une méthode disponible depuis une Activity ou un Fragment pour gérer un chargement en tâche de fonds.
Cette API a été ajoutée à partir du SDK 11, c’est à dire la version 3.0 (HoneyComb). Mais heureusement elle est disponible aussi sur les versions antérieures grâce au package de compatibilité dont j’ai déjà fait mention dans un article précédant.

Il y a plusieurs avantages à utiliser l’ API sur les Loader :
– Elle gère les chargements des données en asynchrone
– Elle gère le rafraîchissement des données
– Elle gère automatiquement le cycle de vie de telle sorte que le Loader n’est pas détruit/reconstruit à chaque changement de configuration

Le dernier point ici (gestion du cycle de vie) est très intéressant, car cela nous affranchit d’une tâche assez périlleuse. Je parle de la gestion d’une tâche asynchrone dans une activité, sans que la tâche soit perturbée par le cycle de vie de l’activité.

La classe LoaderManager est accessible directement depuis une Activity ou un Fragment grâce à la méthode getLoaderManager().

On peut remarquer dans les classes filles de Loader, qu’il existe une implémentation CursorLoader. Elle permet donc d’interroger un ContentProvider de manière asynchrone.
Il est possible cependant de créer son propre Loader en héritant de AsyncTaskLoader.

Pour créer un Loader il faut que votre Activity implémente LoaderManager.LoaderCallbacks, vous aurez donc 3 méthodes à implémenter :

public Loader<C> onCreateLoader(int id, Bundle args) ;
public void onLoadFinished(Loader<C> loader, C data);
public void onLoaderReset(Loader<C> loader);

onCreateLoader() ne sera appelé que si un nouveau Loader a besoin d’être créé. Cela n’est pas le cas lors d’un changement d’orientation puisque le LoaderManager se charge de déconnecter et reconnecter le Loader de l’Activity lors d’un changement de configuration.
onCreateFinished() : permet de récupérer le résultat d’un chargement.
onLoaderReset() est appelé lorsque que l’on quitte l’activité.

La dernière chose à laquelle il faut penser, c’est d’appeler la méthode initLoader.

 
initLoader (int id, Bundle args, LoaderCallbacks<D> callback)

Cette méthode ce charge de la création du Loader si besoin.
Vous remarquerez qu’il est possible de passer un id à cette méthode. Celui-ci sert à identifier le loader, car le LoaderManager peut gérer plusieurs loader.
On récupère donc cet identifiant dans la callback onCreateLoader().

Conclusion :

Voici un exemple d’utilisation d’un CusorLoader qui affiche la liste des artistes du Téléphone avec le nombre de musique associé à cet artiste.

 
public class CursorLoaderListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Cursor>{
	// Adapter utilisé pour afficher la liste
    SimpleCursorAdapter mAdapter;

    @Override public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        // On configure un texte à afficher si il n'y a pas de data
        setEmptyText("No artits music!");


        // On crée un adapter vide (le 3eme paramètre qui doit recevoir un Cursor est null)
        mAdapter = new SimpleCursorAdapter(getActivity(),
                android.R.layout.simple_list_item_2, null,
                new String []{  MediaStore.Audio.Artists.ARTIST , MediaStore.Audio.Artists.NUMBER_OF_ALBUMS},
                new int[] { android.R.id.text1, android.R.id.text2 }, 0);
        setListAdapter(mAdapter);

        // On affiche une progressbar en attendant
        setListShown(false);

        // Prépare le Loader: on se reconnect avec celui existant ou on en cré un nouveau et on le démarre
        getLoaderManager().initLoader(0, null, this);
    }

    // Ce sont sont les colonnes que l'on va recevoir
    static final String[] ARTIST_SUMMARY_PROJECTION = { MediaStore.Audio.Artists._ID, MediaStore.Audio.Artists.ARTIST , MediaStore.Audio.Artists.NUMBER_OF_ALBUMS};

    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    	//Ceci est applelé lorsqu'on d'un nouveau Loader a besoin d'être créé.
    	//Cet exemple a besoin d'un seul Loader, donc nous ne nous occupons pas de l'ID.
    	//l'ID correspond à celui envoyé dans le 1er argument de la méthode getLoaderManager().initLoader(...)

        String select = null;
        return new CursorLoader(getActivity(), MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI,
        		ARTIST_SUMMARY_PROJECTION, select, null,
                null);
    }

    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    	//On change le Loader associé à l'adapter (Le framework s'occupe de fermer l'ancien Cursor) 
        mAdapter.swapCursor(data);

        // On peut afficher la liste maintenant
        if (isResumed()) {
            setListShown(true);
        } else {
            setListShownNoAnimation(true);
        }
    }

    public void onLoaderReset(Loader<Cursor> loader) {

        mAdapter.swapCursor(null);
    }