2010-12-02 6 views
7

J'utilise un CursorAdapter personnalisé pour obtenir des données à partir d'une base de données SQLite et les afficher dans une liste. La base de données contient 2 colonnes avec environ 8.000 lignes. donc je cherche un moyen d'interroger et de montrer toutes les données aussi vite que possible. Je l'ai fait avec AsyncTask est ici le code:Utilisation de Cursor avec l'adaptateur ListView pour une grande quantité de données

private class PrepareAdapter extends AsyncTask<Void,Void,CustomCursorAdapter > { 

@Override 
protected void onPreExecute() { 
    dialog.setMessage("Wait"); 
    dialog.setIndeterminate(true); 
    dialog.setCancelable(false); 
    dialog.show(); 


    Log.e("TAG","Posle nov mAdapter"); 
} 

@Override 
protected CustomCursorAdapter doInBackground(Void... unused) { 

    Cursor cursor = myDbNamesHelper.getCursorQueryWithAllTheData(); 
    mAdapter.changeCursor(cursor); 
    startManagingCursor(cursor); 
    Log.e("TIME","posle start managing Cursor" + String.valueOf(SystemClock.elapsedRealtime()-testTime)+ " ms"); 
    testTime=SystemClock.elapsedRealtime(); 

    mAdapter.initIndexer(cursor); 
    return mAdapter; 
} 

protected void onPostExecute(CustomCursorAdapter result) { 

    TabFirstView.this.getListView().setAdapter(result); 
    Log.e("TIME","posle adapterSet" + String.valueOf(SystemClock.elapsedRealtime()-testTime)+ " ms"); 
    testTime=SystemClock.elapsedRealtime(); 
    dialog.dismiss(); 
} 

}

cela fonctionne bien, sauf la partie quand j'ai besoin de mettre le résultat dans un adaptateur. J'ai fait quelques tests de temps et il faut environ 700 ms pour dépasser le startManagingCursor. le problème est qu'il faut environ 7 secondes pour dépasser le setAdapter (résultat) et que cela fonctionne dans le thread de l'interface utilisateur, ce qui rend mon application inactive (la boîte de dialogue de progression se bloque et parfois l'application). comment puis-je faire ce temps moins? puis-je faire cela aussi en arrière-plan ou de toute façon pour augmenter la réactivité?

tnx.

public class CustomCursorAdapter extends SimpleCursorAdapter implements OnClickListener,SectionIndexer,Filterable, 
            android.widget.AdapterView.OnItemClickListener{ 

    private Context context; 
    private int layout; 
    private AlphabetIndexer alphaIndexer; 

    public CustomCursorAdapter (Context context, int layout, Cursor c, String[] from, int[] to) { 
     super(context, layout, c, from, to); 
     this.context = context; 
     this.layout = layout; 

    } 
    public void initIndexer(Cursor c){ 
     alphaIndexer=new AlphabetIndexer(c, c.getColumnIndex(DataBaseNamesHelper.COLUMN_NAME), " ABCDEFGHIJKLMNOPQRSTUVWXYZ"); 
    } 

    @Override 
    public View newView(Context context, Cursor cursor, ViewGroup parent) { 

     Cursor c = getCursor(); 

     final LayoutInflater inflater = LayoutInflater.from(context); 
     View v = inflater.inflate(layout, parent, false); 

     int nameCol = c.getColumnIndex(DataBaseNamesHelper.COLUMN_NAME); 

     String name = c.getString(nameCol); 

     /** 
     * Next set the name of the entry. 
     */ 
     TextView name_text = (TextView) v.findViewById(R.id.name_entry); 
     if (name_text != null) { 
      name_text.setText(name); 
     } 

     int favCol = c.getColumnIndex(DataBaseNamesHelper.COLUMN_FAVOURITED); 
     int fav = c.getInt(favCol); 

     int idCol = c.getColumnIndex(DataBaseNamesHelper.COLUMN_ID); 

     Button button = (Button) v.findViewById(R.id.Button01); 
     button.setOnClickListener(this); 
     button.setTag(c.getInt(idCol)); 
     if(fav==1){ 
      button.setVisibility(View.INVISIBLE); 
     } 
     else button.setVisibility(View.VISIBLE); 

     return v; 
    } 

    @Override 
    public void bindView(View v, Context context, Cursor c) { 

     int nameCol = c.getColumnIndex(DataBaseNamesHelper.COLUMN_NAME); 

     String name = c.getString(nameCol); 

     /** 
     * Next set the name of the entry. 
     */ 
     TextView name_text = (TextView) v.findViewById(R.id.name_entry); 
     if (name_text != null) { 
      name_text.setText(name); 
     } 
     int favCol = c.getColumnIndex(DataBaseNamesHelper.COLUMN_FAVOURITED); 
     int fav = c.getInt(favCol); 

     Button button = (Button) v.findViewById(R.id.Button01); 
     button.setOnClickListener(this); 
     int idCol = c.getColumnIndex(DataBaseNamesHelper.COLUMN_ID); 
     button.setTag(c.getInt(idCol)); 
     // Log.e("fav",String.valueOf(fav)); 
     if(fav==1){ 
      button.setVisibility(View.INVISIBLE); 
     } else button.setVisibility(View.VISIBLE); 
    } 



    @Override 
    public int getPositionForSection(int section) { 
     return alphaIndexer.getPositionForSection(section); 
    } 

    @Override 
    public int getSectionForPosition(int position) { 
      return alphaIndexer.getSectionForPosition(position); 
    } 

    @Override 
    public Object[] getSections() { 
      return alphaIndexer.getSections(); 
    } 
    @Override 
    public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) { 
     Log.e("item Click", arg1.toString()+ " position> " +arg2); 
    } 

    @Override 
    public void onClick(View v) { 
      if(v.getId()==R.id.Button01){ 
       //Log.e("Button Click", v.toString()+ " position> " +v.getTag().toString()); 
       v.setVisibility(View.INVISIBLE); 
       DataBaseNamesHelper dbNames = new DataBaseNamesHelper(context); 
       dbNames.setFavouritesFlag(v.getTag().toString()); 
      } 

     } 



} 
+1

Vos utilisateurs veulent vraiment faire défiler 8000 lignes dans un ListView? Y a-t-il un moyen pour eux de filtrer la liste en premier? – EboMike

+0

J'ai un fastscroll avec l'indexation implémentée. et je vais ajouter une option de recherche plus tard pour filtrer. – DArkO

+0

Dans tous les cas, vous POUVEZ faire la requête dans un fil séparé, puis l'assigner à l'adaptateur, mais cela laisserait votre application noire pendant 7 secondes. Sauf si vous avez un moyen de divertir l'utilisateur pendant 7 secondes? – EboMike

Répondre

25

La raison de la lenteur du chargement de l'adaptateur est l'appel interne que CursorAdapter fait à Cursor.getCount().

Les curseurs dans Android sont chargés paresseusement. Les résultats ne sont pas chargés tant qu'ils ne sont pas nécessaires. Lorsque le CursorAdapter appelle getCount(), la requête est entièrement exécutée et les résultats sont comptés.

Voici quelques liens traitant de ce problème.

http://groups.google.com/group/android-developers/browse_thread/thread/c1346ec6e2310c0c

http://www.androidsoftwaredeveloper.com/2010/02/25/sqlite-performance/

Ma suggestion serait de diviser votre requête. Chargez uniquement le nombre d'éléments de liste visibles à l'écran. Lorsque l'utilisateur fait défiler, chargez l'ensemble suivant. Très semblable aux applications GMail et Market. Malheureusement, je n'ai pas un exemple pratique :(

Cela ne répond pas à votre question, mais nous espérons qu'il donne une idée :)

2

Eh bien, je ne peux que vous offrir une suggestion stupide à ce point - Prépare une requête dans un thread séparé qui passera par la base de données complète et créez votre curseur 8000 rangs.

Dans votre thread d'interface utilisateur, créez un curseur où vous définissez la limite à 100 ou plus, et utilisez-le pour votre adaptateur. Si l'utilisateur fait défiler vers le bas de votre liste, vous pouvez ajouter une ligne avec un ProgressBar ou indiquer autrement qu'il n'y en a plus à venir.

Une fois votre deuxième thread terminé, remplacez l'adaptateur. Alternativement, vous pouvez avoir un adaptateur de tableau ou quelque chose de similaire, faire un tas de requêtes dans le deuxième thread avec 100 lignes chacune, et les ajouter à votre adaptateur de tableau comme ils viennent. Cela rendrait également plus facile d'ajouter une rangée factice au fond qui indique aux utilisateurs de tenir leurs chevaux.

Une note - Je suppose qu'il est sûr de lire à partir de la base de données à partir de deux threads en même temps, mais faites attention quand il s'agit d'écrire. Mon application avait un bug merveilleux où il a saccagé la base de données quand j'ai eu des opérations de base de données dans un thread séparé jusqu'à ce que j'ai ajouté un verrouillage correct (que vous pouvez également activer dans la base de données).

EDIT: Bien sûr, AsyncQueryHandler, je savais qu'il y avait quelque chose comme ça, mais je ne pouvais pas me souvenir du nom. Plus élégant qu'un fil séparé, bien sûr.

Btw - comment stockez-vous la base de données? Est-ce juste un fichier de base de données régulier dans votre stockage interne?

+0

oui juste une base de données interne régulière. Je n'utilise pas de fournisseur de contenu car je n'ai pas besoin de partager des données entre applications. J'essaye de placer l'adaptateur d'abord pour vider et puis pour changer le curseur dans l'adapteur, mais aucune chance jusqu'ici cela me donne seulement un listView vide. Ce qui me dérange, c'est que la requête ne prend que 600 ms à compléter, mais l'affectation de la liste 7 secondes, donc je suppose que la réponse lente ne vient pas de la requête correcte ou ai-je raté quelque chose? – DArkO

+0

Oh génial, j'ai mal lu ça. Quel type d'adaptateur utilisez-vous? Je vois CustomCursorAdapter - quelle est la partie personnalisée? – EboMike

+0

Il étend SimpleCursorAdapter. voici ce que j'avais l'habitude de faire. http://thinkandroid.wordpress.com/2010/01/11/custom-cursoradapters/ – DArkO

3

Vous ne savez pas pourquoi setAdapter devrait prendre autant de temps. J'ai fait environ 5000 lignes beaucoup plus vite que cela. Cependant, je crée habituellement un adaptateur sans curseur d'abord, appelez setAdapter avec l'adaptateur "vide", puis lancez une requête asynchrone en utilisant une sous-classe AsyncQueryHandler. Ensuite, dans onQueryComplete, j'appelle changeCursor sur l'adaptateur.

1

salut im essayant atteind cela avec plus de 50000rows. Je pense que mon code peut vous donner les meilleurs résultats car vous n'avez que 8000 enregistrements.

1) utiliser le fournisseur de contenu au lieu de sqlite. 2) utiliser les callbacks loadermanager pour charger le curseur de manière asynchrone 3) pour la recherche, utiliser les tables FTS. 4) optimiser ur db avec indexation aussi. Croyez-moi cela fonctionne très bien avec 8000 lignes même avec sectionIndexer et avec défilement rapide aussi.

laissez-moi savoir si vous avez des doutes. P. si vous avez trouvé une meilleure solution, que vous pensez pouvoir gérer 50000 lignes, faites-le moi savoir.

Questions connexes