2011-03-26 2 views
1

Je veux faire SQLiteOpenHelper avec quelques méthodes supplémentaires (comme getStreetsCursor) qui retourne des données de mon DB. Donc, je l'ai écrit quelque chose comme ceci:android SQLiteOpenHelper et les fuites

public class DBHelper extends SQLiteOpenHelper { 
    public static final String DB_NAME="some.db"; 
    public static final String T1_NAME="streets"; 
    public static final String T1_FNAME1="name"; 
    public static final String T2_NAME="addresses"; 
    public static final String T2_FNAME1="name"; 
    public static final String T2_FNAME2="address"; 

    private Context appContext; 

    public DBHelper(Context context) { 
    super(context, DB_NAME, null, 1); 
    appContext=context; 
    } 

    public Cursor getStreetsCursor(String chars) { 
    SQLiteDatabase dbReadable=this.getReadableDatabase(); 
     Cursor curStreets = dbReadable.query(DBHelper.T1_NAME, 
       new String[] {"_id",DBHelper.T1_FNAME1}, 
       DBHelper.T1_FNAME1+" LIKE(\""+chars.toUpperCase()+"%\")", 
       null, null, null, DBHelper.T1_FNAME1); 

    return curStreets; 
    } 

Il existe plusieurs méthodes comme getStreetsCursor (getAddresses, getAddress4 etc.) définis dans DBHelper. Je suppose que si c'est un DB Helper il devrait certainement avoir de telles méthodes, je veux dire le DBHelper est un espace réservé logique pour eux.

Ce que je fais dans l'activité est de créer une nouvelle instance DBHelper et de la stocker dans un champ privé (appelé mDBHelper) d'activité. De plus, dans la méthode d'activité onDestroy, j'ai mDBHelper.close().

private DBHelper mDBHelper; 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     mDBHelper = new DBHelper(this); 
... 
    @Override 
    protected void onDestroy() { 
     if (mDBHelper!=null){ 
      mDBHelper.close(); 
      Log.i(APP_TAG,"mDBHelper.close() in "+this.getClass()); 
     } 
     super.onDestroy(); 
    } 

Ces activités utilise le mDBHelper que d'une manière - en l'appelant est des méthodes personnalisées comme mDBHelper.getStreetsCursor(). Finalement, j'ai trouvé un message d'exception dans logcat à propos de mon application a des fuites dans de telles activités qui utilise DBHelper. Il dit quelque chose comme "la base de données n'a jamais été fermée". J'ai donc décidé d'ajouter un appel à la méthode close() dans chacune de mes méthodes personnalisées juste avant le retour. Il semble donc que:

 public Cursor getStreetsCursor(String chars) { 
      SQLiteDatabase dbReadable=this.getReadableDatabase(); 
      Cursor curStreets = dbReadable.query(DBHelper.T1_NAME, 
        new String[] {"_id",DBHelper.T1_FNAME1}, 
        DBHelper.T1_FNAME1+" LIKE(\""+chars.toUpperCase()+"%\")", 
        null, null, null, DBHelper.T1_FNAME1); 

      dbReadable.close(); 
      return curStreets; 
     } 

Maintenant, j'ai obtenu aucune fuite mais nous avons eu le problème suivant - seulement le premier appel à mDBHelper.getStreetsCursor() exécute vraiment. Tous les appels suivants renvoient null. C'est dû à dbReadable.close(); ligne. Si je l'enlève tout fonctionne bien mais j'ai encore des fuites. Donc, je ne peux pas comprendre ce qui ne va pas. Dans chaque méthode personnalisée, j'ai obtenu SQLiteDatabase dbReadable = this.getReadableDatabase(); ligne qui devrait renvoyer une instance lisible, mais après l'exécution de la méthode close() ce n'est pas le cas. Je pense qu'il s'agit de mes méthodes personnalisées car elles appellent .getReadableDatabase() dans une instance de DBHelper. Si je place ces méthodes directement dans l'activité tout fonctionne bien - pas d'exception de fuite et chaque fois que les méthodes renvoient des données correctes. Mais je veux placer ces méthodes dans ma classe DBHelper. Donc, la question principale est - quel est le problème et comment le faire correctement?

Répondre

0

Années après ... Je veux juste dire que maintenant je vais mieux utiliser le moteur de Realm.io DB et je le fais maintenant dans mes projets. En ce qui concerne ma vieille question, je tiens également à noter que l'utilisation de getReadableDatabase() est une mauvaise idée car la fragmentation de périphériques rencontrée quelques bibs sqlite bug-lisibles instance sera automatiquement fermé juste après une exécution de la requête qui est un comportement inattendu. Donc, je recommande toujours utiliser la méthode getWritableDatabase() même à des fins de lecture. Et enfin je finis avec Dmytro Danylyk solution - give it a try, Dmytro is Google Developer Expert:

public class DatabaseManager { 

    private AtomicInteger mOpenCounter = new AtomicInteger(); 

    private static DatabaseManager instance; 
    private static SQLiteOpenHelper mDatabaseHelper; 
    private SQLiteDatabase mDatabase; 

    public static synchronized void initializeInstance(SQLiteOpenHelper helper) { 
     if (instance == null) { 
      instance = new DatabaseManager(); 
      mDatabaseHelper = helper; 
     } 
    } 

    public static synchronized DatabaseManager getInstance() { 
     if (instance == null) { 
      throw new IllegalStateException(DatabaseManager.class.getSimpleName() + 
        " is not initialized, call initializeInstance(..) method first."); 
     } 

     return instance; 
    } 

    public synchronized SQLiteDatabase openDatabase() { 
     if(mOpenCounter.incrementAndGet() == 1) { 
      // Opening new database 
      mDatabase = mDatabaseHelper.getWritableDatabase(); 
     } 
     return mDatabase; 
    } 

    public synchronized void closeDatabase() { 
     if(mOpenCounter.decrementAndGet() == 0) { 
      // Closing database 
      mDatabase.close(); 

     } 
    } 
} 

et l'utiliser comme suit.

SQLiteDatabase database = DatabaseManager.getInstance().openDatabase(); 
database.insert(...); 
// database.close(); Don't close it directly! 
DatabaseManager.getInstance().closeDatabase(); // correct way 
0

Faire SQLiteDatabase dbReadable un membre de la classe, et mettre en œuvre

public void close() 
{ 
    dbReadable.close(); 
} 

Ensuite, lorsque vous appelez mDBHelper.close(); dans onDestroy() de votre activité fonction, il devrait être ok.

+0

thanx. bonne idée. – Stan

5

Vous ne devriez pas avoir un SQLiteDatabase dbReadable=this.getReadableDatabase(); dans chaque méthode getSomethingCursor comme demander un objet de base de données est cher (je pense que je l'ai lu dans SO).

Vous pouvez donc créer l'objet de votre constructeur

SQLiteDatabase dbReadable; 

    public DBHelper(Context context) { 
    super(context, DB_NAME, null, 1); 
    appContext=context; 
    dbReadable=this.getReadableDatabase() 
    } 


    public Cursor getStreetsCursor(String chars) { 
      Cursor curStreets = dbReadable.query(DBHelper.T1_NAME, 
        new String[] {"_id",DBHelper.T1_FNAME1}, 
        DBHelper.T1_FNAME1+" LIKE(\""+chars.toUpperCase()+"%\")", 
        null, null, null, DBHelper.T1_FNAME1); 

      return curStreets; 
    } 

Créer une méthode pour fermer la base de données poignée:

public closeDb() { 
    if (dbReadable != null) { dbReadable.close();} 
} 

Et vous l'activité:

@Override 
    protected void onDestroy() { 
     if (mDBHelper!=null){ 
      mDBHelper.closeDb(); 
      Log.i(APP_TAG,"mDBHelper.close() in "+this.getClass()); 
     } 
     super.onDestroy(); 
    } 

et utilisation startManagingCursor (si SDK < HoneyComb) pour gérer votre activité notre curseur (fermeture sur onDestroy par exemple

+0

Merci. Mais si c'est la première fois, je veux dire qu'il n'y a pas encore de db créé. Je suppose que je ne peux pas utiliser dbReadable = this.getReadableDatabase() dans le constructeur est ce cas ... – Stan

+0

et ne devrais-je pas utiliser la finalize au lieu de créer closeDb? – Stan

+0

Avant de passer un appel à votre méthode, vous devez instancier votre classe. Donc, lorsque vous l'instanciez, vous obtenez en même temps votre objet 'SQLiteDatabase'. – ccheneson

0

Faire une nouvelle fonction

public void DBclose() 
{ 
curstreets.close(); 
dbReadable.close(); 
} 

Ensuite, vous pouvez appeler cela dans votre activité dans onDestroy() fucntion.

Questions connexes