IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Android Discussion :

[Optimisation] Utilisation de CursorLoader au lieu de StartManagingCursor


Sujet :

Android

  1. #1
    Membre régulier
    Profil pro
    Inscrit en
    Décembre 2007
    Messages
    348
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2007
    Messages : 348
    Points : 103
    Points
    103
    Par défaut [Optimisation] Utilisation de CursorLoader au lieu de StartManagingCursor
    Bonjour à tous,

    Ce topic fait suite à ces deux précédents (même projet):

    http://www.developpez.net/forums/d12...ableau-string/

    http://www.developpez.net/forums/d12...ite-supprimer/

    J'ai réussi à afficher dans une ListView le contenu d'une base de données SQLite, qui contient dans une table Reservation les attributs ID (long), Reference et LastName (String), Reservation étant un objet Java créé par moi-même, ayant ces 3 attributs.

    Voici l'activité qui affiche la ListView en question:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    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
    package com.example.mon.app;
     
    import android.app.AlertDialog;
    import android.content.DialogInterface;
    import android.content.Intent;
    import android.database.Cursor;
    import android.os.Bundle;
    import android.support.v4.app.FragmentActivity;
    import android.support.v4.app.LoaderManager;
    import android.support.v4.content.CursorLoader;
    import android.support.v4.content.Loader;
    import android.support.v4.widget.SimpleCursorAdapter;
    import android.util.Log;
    import android.view.Menu;
    import android.view.View;
    import android.widget.AdapterView;
    import android.widget.AdapterView.OnItemClickListener;
    import android.widget.AdapterView.OnItemLongClickListener;
    import android.widget.ListView;
     
    public class FlightBook extends FragmentActivity implements LoaderManager.LoaderCallbacks<Cursor>
    {
    	private ListView ReservationList = null;
    	private Cursor c = null;
    	private SimpleCursorAdapter adapter = null;
    	private ReservationsBDD rdb = new ReservationsBDD(this);
     
    	private static final int LOADER_ID = 0;
    	private static final String[] PROJECTION = new String[] {MaBaseSQLite.COL_ID, MaBaseSQLite.getColReference(), MaBaseSQLite.getColLastname()};
    	private LoaderManager.LoaderCallbacks<Cursor> mCallbacks;
     
        @SuppressWarnings("deprecation")
    	@Override
        public void onCreate(Bundle savedInstanceState) 
        {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_flight_book);
     
            ReservationList = (ListView) findViewById(R.id.flightList);
     
            //Initialiser la base de données
            rdb.open();
     
            String[] from = { MaBaseSQLite.getColReference(), MaBaseSQLite.getColLastname() };
            int[] to = { R.id.reference_entry, R.id.lastname_entry };
     
            //TODO: NE PAS UTILISER LES METHODES DECONSEILLEES PAR ECLIPSE
            Cursor cursor = managedQuery(ReservationProvider.CONTENT_URI, PROJECTION, null, null, null);
    	    adapter = new SimpleCursorAdapter(this, R.layout.flight_book_list, cursor, from, to);
     
            ReservationList.setAdapter(adapter);
            mCallbacks = this;
     
            //ReservationList.setAdapter(mAdapter);
            getSupportLoaderManager().initLoader(LOADER_ID, null, mCallbacks);
     
            //Gérer le clic simple sur un élément de la listView
            ReservationList.setOnItemClickListener(new OnItemClickListener() 
            {
            	long selectedItemID = 0;
    			public void onItemClick(AdapterView<?> parent, View view, int position, long id) 
    			{
    				//Requêter la réservation par son ID
    				Reservation res = rdb.getReservationWithID(id);
     
    		        //Afficher l'activité ReservationInfo
    		        Intent myIntent = new Intent(view.getContext(), ReservationInfo.class);
    		        myIntent.putExtra("ReservationReference", res.getReference());
    		        myIntent.putExtra("LastName", res.getLastname());
    		        startActivityForResult(myIntent, 0);
    			}
            });
     
            //Gestion du clic long sur un élément de la liste
            ReservationList.setOnItemLongClickListener (new OnItemLongClickListener() 
            {
            	public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) 
            	{
            		return onLongListItemClick(view, position, id);
            	}
     
    	        protected boolean onLongListItemClick(final View v, final int pos, long id) 
    	        {
    	           final String str=ReservationList.getItemAtPosition(pos).toString();
    	           Log.i("ListView", "onLongListItemClick string=" + str);
    	           AlertDialog.Builder builder = new AlertDialog.Builder(FlightBook.this);
    	           builder.setMessage("Delete this entry from Flight Book?")
    	           .setCancelable(false)
    	           .setPositiveButton("Yes", new DialogInterface.OnClickListener() 
    	           {
    	        	   public void onClick(DialogInterface dialog, int id) 
    	        	   {
    	        		   Log.i("ID",""+pos);
     
    	        		   //TODO: UTILISER LE CURSORLOADER SI POSSIBLE!
    	        		   rdb.removeReservationWithID((int)adapter.getItemId(pos));
     
    	        		   //TODO: Gérer la mise à jour de la liste dans l'affichage en temps réel
     
    	        		   //Refresh ListView automatically
    	        		   //Cursor c = rdb.queueAll();
    	        	       //startManagingCursor(c);
    	        	       //mAdapter.changeCursor(c);
     
    			           /*Cursor c = getContentResolver().query(Uri.withAppendedPath(ReservationProvider.CONTENT_URI, String.valueOf(id)), PROJECTION, null, null, null);
    			           if (c.moveToFirst()) 
    			           {
    			               String reservationUrl = c.getString(0);
    			               //tutSelectedListener.onTutSelected(reservationUrl);
    			           }*/
    	        	   }
    	           })
    	           .setNegativeButton("No", new DialogInterface.OnClickListener() 
    	           {
    	               public void onClick(DialogInterface dialog, int id) 
    	               {
    	                   dialog.cancel();
    	               }
    	           });
    	           AlertDialog alert = builder.create();
    	           alert.show();
    	           return true;
                }
            });
        }
     
        public void onPause()
        {
        	super.onPause();
        }
     
        public void onDestroy()
        {
        	super.onDestroy();
        	rdb.close();
        }
     
        public void onStart()
        {
        	super.onStart();
        }
     
        public void onStop()
        {
        	super.onStop();
        }
     
        public void onResume(Bundle savedInstanceState) 
        {
        	super.onResume();
        }
     
        @Override
        public boolean onCreateOptionsMenu(Menu menu) 
        {
            getMenuInflater().inflate(R.menu.activity_flight_book, menu);
            return true;
        }
     
    	public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) 
    	{
    		// TODO Auto-generated method stub
    		return new CursorLoader(FlightBook.this, ReservationProvider.CONTENT_URI, PROJECTION, null, null, null);
    	}
     
    	//@Override -> won't work, says "must override a superclass method"
    	public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) 
    	{
    		adapter.swapCursor(cursor);
    	}
     
    	//@Override -> won't work, says "must override a superclass method"
    	public void onLoaderReset(Loader<Cursor> loader) 
    	{
    		adapter.swapCursor(null);
    	}
    }
    Je l'ai beaucoup optimisée de sorte à utiliser un CursorLoader et un ContentProvider. Ce n'était franchement pas nécessaire, parce que ça marchait très bien avec startManagingCursor(), mais je me suis lancé quand même, et j'ai du mal à arriver jusqu'au bout.

    D'une part, je n'arrive pas à gérer la mise à jour de l'affichage de la liste en temps réel quand je supprime un élément de la base, et deuxièmement, je n'arrive pas à comprendre comment faire pour supprimer un élément de la base avec le ContentProvider/CursorLoader (je ne sais pas lequel s'en charge) au lieu de le faire avec le DAO sur ma base (si c'est nécessaire).

    Que puis-je faire pour optimiser le code ci-dessus?
    Merci d'avance.

  2. #2
    Expert éminent

    Homme Profil pro
    Ingénieur systèmes et réseaux
    Inscrit en
    Février 2007
    Messages
    4 253
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Ingénieur systèmes et réseaux
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Février 2007
    Messages : 4 253
    Points : 7 618
    Points
    7 618
    Billets dans le blog
    3
    Par défaut
    Les "Loader" sont apparus pour faciliter la vie des développeurs, et éviter qu'ils réimplémentent sans arrêt des AsyncTask...

    D'ailleurs, on va hériter sans doute de AsyncTaskLoader avec:
    onCreateLoader <= onPreExecute
    onLoadFinished <= onPostExecute

    Comme toutes les opérations "longues" doivent être faites dans des tâches à part (lecture de fichier / accès internet / ...), l'utilisation d'une query sur un DB en fait partie...
    Pre 3.0 on utilisait donc une AsyncTask pour gérer le Cursor.
    Post 3.0 on utilise un CursorLoader.

    L'unique différence est donc sur le code d'obtention du Cursor.... et le onLoadFinished() qui va appeler un swapCursor() sur l'Adapter.

    Voilà pour le CursorLoader, rien à voir avec la gestion des éléments dans la base donc.


    Pour le ContentProvider...
    Un ContentProvider sert à fournir les données à des applications externes... Référencement par type (mime-type) et URL de chaque donnée.
    Le ContentProvider peut aussi fournir des fonctions pour ajouter/supprimer des données mais ce n'est pas obligatoire.
    Une application externe a juste besoin d'utiliser un ContentProviderClient récupéré par l'interface ContentResolver.

    Dans ton application:
    Suppression de donnée => Appel au DAO.deleteXXXX()

    Dans une autre application
    Suppression de données => Appel au ContentProviderClient.delete => systeme => Appel au ContentProvider.delete de ton application => Appel au DAO.deleteXXXX()
    N'oubliez pas de cliquer sur mais aussi sur si un commentaire vous a été utile !
    Et surtout

  3. #3
    Membre régulier
    Profil pro
    Inscrit en
    Décembre 2007
    Messages
    348
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2007
    Messages : 348
    Points : 103
    Points
    103
    Par défaut
    C'est bien ce que j'avais compris. Donc selon toi, utiliser le ContentProvider pour effectuer des modifications dans la base en lieu et place du DAO est inutile au sein même de l'application?

    Pour ce qui est des ASyncTask, j'ai beaucoup beaucoup cherché, mais je n'ai pas trouvé comment m'en servir algorithmiquement parlant dans mon exemple à moi. Qu'est-ce que ça apporterait de plus par rapport à ce que j'ai (et qui marche sans)?

  4. #4
    Expert éminent

    Homme Profil pro
    Ingénieur systèmes et réseaux
    Inscrit en
    Février 2007
    Messages
    4 253
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Ingénieur systèmes et réseaux
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Février 2007
    Messages : 4 253
    Points : 7 618
    Points
    7 618
    Billets dans le blog
    3
    Par défaut
    Il suffit de lire le premier paragraphe des ContentProvider :

    Content providers manage access to a structured set of data. They encapsulate the data, and provide mechanisms for defining data security. Content providers are the standard interface that connects data in one process with code running in another process.....
    (...)
    You don't need to develop your own provider if you don't intend to share your data with other applications.
    Si on reste toujours dans le même process, pas besoin de passer par des ContentProvider....

    Qu'est-ce que ça apporterait de plus par rapport à ce que j'ai (et qui marche sans)?
    Le code sans AsyncTask / Loader "fonctionne" tant que les requêtes sont rapides... Encore une fois, le rôle du thread UI est de répondre aux évenements utilisateurs le plus vite possible....
    Prenons un exemple... la requête dure 1s....
    T=0s, l'utilisateur appuie sur le bouton (ou tout autre evenement) pour démarrer la requete.
    T=0.010s, la fonction démarre la requête.
    T=0.2s, l'utilisateur appuie sur 'back' (quitter l'application)
    ...
    T=1.010s, la fonction recoit la requête.
    T=1.050s, le résultat de la requête est présenté (Adapter)
    T=1.060s, le 'back' est géré par l'appli....

    Entre T=0.2s et T=1.06s , on a presque 0,9s pendant lesquelles l'utilisateur attend que l'appli quitte ! => mauvais retour.

    Si la requête est plus longue c'est encore pire, et risque même de "triggerer" un ANR (Application Non Responsive), le système détectant que l'appui sur back n'a toujours pas été géré en moins de X secondes, le système va demander à l'utilisateur si il veut fermer (hard) l'application......... TRES mauvais retour ^^
    A noter que ceci est vrai pour tout évenement (touch / menu / ...), pas seulement le 'back'....

    Donc tout ce qui est "potentiellement" long (lecture fichier, database, accès réseau), il faut tout le temps passer par une AsyncTask.
    Comme dans 95% des cas, le but est de "charger des données", Android3.0+ propose les Loaders (qui, au passage, s'appuient sur AsyncTask, mais permettent de discutailler avec l'activité plus facilement).
    N'oubliez pas de cliquer sur mais aussi sur si un commentaire vous a été utile !
    Et surtout

  5. #5
    Membre régulier
    Profil pro
    Inscrit en
    Décembre 2007
    Messages
    348
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2007
    Messages : 348
    Points : 103
    Points
    103
    Par défaut
    Merci pour l'exemple.
    J'ai testé chez moi, l'application met environ 3 à 4 secondes à charger la webView, mais quand j'appuie frénétiquement sur Retour, c'est bien géré, aucun problème. En tout cas, je suis sûr que cette prise de conscience me sera utile pour des applications plus complexes.

Discussions similaires

  1. [Placement] Utiliser le CSS au lieu d'un tableau
    Par Marco85 dans le forum Mise en page CSS
    Réponses: 4
    Dernier message: 19/12/2006, 16h09
  2. Utiliser /bin/sh au lieu de bash
    Par Miksimus dans le forum Shell et commandes GNU
    Réponses: 12
    Dernier message: 25/10/2006, 13h57
  3. Utiliser une variable au lieu de perdre du temp?
    Par mejrs dans le forum Général JavaScript
    Réponses: 4
    Dernier message: 22/10/2006, 11h56
  4. Réponses: 3
    Dernier message: 03/05/2006, 15h08
  5. Utiliser des procédures au lieu des classes
    Par ahage4x4 dans le forum ASP
    Réponses: 5
    Dernier message: 29/06/2005, 10h53

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo