web-dev-qa-db-fra.com

Est-il OK d'avoir une instance de SQLiteOpenHelper partagée par toutes les activités dans une application Android?

Serait-il correct d'avoir une seule instance de SQLiteOpenHelper en tant que membre d'une application sous-classée et que toutes les activités qui ont besoin d'une instance de SQLiteDatabase l'obtiennent à partir du seul assistant?

39
Julian A.

Avoir une seule instance SQLiteOpenHelper peut aider dans les cas de thread. Étant donné que tous les threads partageraient le SQLiteDatabase commun, la synchronisation des opérations est fournie.

Cependant, je ne ferais pas une sous-classe de Application. Ayez simplement un membre de données statique qui est votre SQLiteOpenHelper. Les deux approches vous donnent quelque chose d'accessible de n'importe où. Cependant, il n'y a que ne sous-classe de Application, ce qui rend plus difficile pour vous d'utiliser autre sous-classes de Application (par exemple, GreenDroid nécessite un IIRC). L'utilisation d'un membre de données statiques évite cela. Cependant, utilisez le ApplicationContext lors de l'instanciation de ce SQLiteOpenHelper (paramètre constructeur) statique, afin de ne pas divulguer d'autres Context.

Et, dans les cas où vous ne traitez pas avec plusieurs threads, vous pouvez éviter tout problème de fuite de mémoire en utilisant simplement une instance SQLiteOpenHelper par composant. Cependant, dans la pratique, vous devriez traiter avec plusieurs threads (par exemple, un Loader), donc cette recommandation n'est pertinente que pour les applications triviales, telles que celles trouvées dans certains livres .. . :-)

41
CommonsWare

Cliquez ici pour voir mon article de blog sur ce sujet.


CommonsWare a raison (comme d'habitude). En développant son article, voici un exemple de code qui illustre trois approches possibles. Ceux-ci permettront d'accéder à la base de données tout au long de l'application.

Approche n ° 1: sous-classification de "Application"

Si vous savez que votre application ne sera pas très compliquée (c'est-à-dire si vous savez que vous n'aurez qu'une seule sous-classe de Application), alors vous pouvez créer une sous-classe de Application et avoir votre Activité principale étendre. Cela garantit qu'une instance de la base de données s'exécute tout au long du cycle de vie de l'application.

public class MainApplication extends Application {

    /**
     * see NotePad tutorial for an example implementation of DataDbAdapter
     */
    private static DataDbAdapter mDbHelper;

    /**
     * Called when the application is starting, before any other 
     * application objects have been created. Implementations 
     * should be as quick as possible...
     */
    @Override
    public void onCreate() {
        super.onCreate();
        mDbHelper = new DataDbAdapter(this);
        mDbHelper.open();
    }

    public static DataDbAdapter getDatabaseHelper() {
        return mDbHelper;
    }
}

Approche n ° 2: avoir `SQLiteOpenHelper` être un membre de données statique

Ce n'est pas l'implémentation complète, mais cela devrait vous donner une bonne idée sur la façon de concevoir correctement la classe DatabaseHelper. La méthode d'usine statique garantit qu'il n'existe qu'une seule instance DatabaseHelper à la fois.

/**
 * create custom DatabaseHelper class that extends SQLiteOpenHelper
 */
public class DatabaseHelper extends SQLiteOpenHelper { 
    private static DatabaseHelper mInstance = null;

    private static final String DATABASE_NAME = "databaseName";
    private static final String DATABASE_TABLE = "tableName";
    private static final int DATABASE_VERSION = 1;

    private Context mCxt;

    public static DatabaseHelper getInstance(Context ctx) {
        /** 
         * use the application context as suggested by CommonsWare.
         * this will ensure that you dont accidentally leak an Activitys
         * context (see this article for more information: 
         * http://developer.Android.com/resources/articles/avoiding-memory-leaks.html)
         */
        if (mInstance == null) {
            mInstance = new DatabaseHelper(ctx.getApplicationContext());
        }
        return mInstance;
    }

    /**
     * constructor should be private to prevent direct instantiation.
     * make call to static factory method "getInstance()" instead.
     */
    private DatabaseHelper(Context ctx) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
        this.mCtx = ctx;
    }
}

Approche n ° 3: abstraire la base de données SQLite avec un "ContentProvider"

C'est l'approche que je suggérerais. D'une part, la nouvelle classe LoaderManager s'appuie fortement sur ContentProviders, donc si vous voulez qu'une activité ou un fragment implémente LoaderManager.LoaderCallbacks<Cursor> (Dont je vous suggère de profiter, c'est magique!), Vous ' ll faudra implémenter un ContentProvider pour votre application. De plus, vous n'avez pas à vous soucier de créer un assistant de base de données Singleton avec ContentProviders. Appelez simplement getContentResolver() depuis l'Activité et le système s'occupera de tout pour vous (en d'autres termes, il n'est pas nécessaire de concevoir un modèle Singleton pour empêcher la création de plusieurs instances).

J'espère que cela pourra aider!

46
Alex Lockwood

J'ai écrit MultiThreadSQLiteOpenHelper qui est un SQLiteOpenHelper amélioré pour Android où plusieurs threads peuvent ouvrir et fermer la même base de données sqlite.

Au lieu d'appeler la méthode close, les threads demandent la fermeture de la base de données, empêchant un thread d'effectuer une requête sur une base de données fermée.

Si chaque thread a demandé une fermeture, une fermeture est en fait effectuée. Chaque activité ou thread (ui-thread et threads utilisateur) effectue un appel ouvert sur la base de données lors de la reprise et demande de fermer la base de données lors d'une pause ou d'une fin.

Code source et exemples disponibles ici: https://github.com/d4rxh4wx/MultiThreadSQLiteOpenHelper

7
d4rxh4wx

J'ai fait beaucoup de recherches sur ce sujet et je suis d'accord avec tous les points mentionnés par commonware. Mais je pense qu'il y a un point important que tout le monde manque ici, la réponse à cette question dépend entièrement de votre cas d'utilisation, donc si votre application lit des bases de données via plusieurs threads et que la lecture en utilisant Singleton a des performances énormes hit car toutes les fonctions sont synchronisées et sont exécutées en série car il n'y a qu'une seule connexion à la base de données Open source est génial, soit dit en passant. Vous pouvez creuser directement dans le code et voir ce qui se passe. De cela et de certains tests, j'ai appris que les points suivants sont vrais:

Sqlite takes care of the file level locking.  Many threads can read, one can write.  The locks prevent more than one writing.
Android implements some Java locking in SQLiteDatabase to help keep things straight.
If you go crazy and hammer the database from many threads, your database will (or should) not be corrupted.

Si vous essayez d'écrire dans la base de données à partir de connexions distinctes réelles en même temps, l'une échouera. Il n'attendra pas que le premier soit terminé, puis rédigera. Il n'écrira tout simplement pas votre changement. Pire, si vous n'appelez pas la bonne version d'insertion/mise à jour sur la SQLiteDatabase, vous n'aurez pas d'exception. Vous recevrez simplement un message dans votre LogCat, et ce sera tout.

Le premier problème, les connexions réelles et distinctes. La grande chose au sujet du code open source est que vous pouvez creuser directement et voir ce qui se passe. La classe SQLiteOpenHelper fait des choses amusantes. Bien qu'il existe une méthode pour obtenir une connexion à une base de données en lecture seule ainsi qu'une connexion en lecture-écriture, sous le capot, c'est toujours la même connexion. En supposant qu'il n'y a pas d'erreurs d'écriture de fichier, même la connexion en lecture seule est vraiment la seule connexion en lecture-écriture. Assez drôle. Donc, si vous utilisez une instance d'aide dans votre application, même à partir de plusieurs threads, vous n'utilisez jamais vraiment plusieurs connexions.

De plus, la classe SQLiteDatabase, dont chaque assistant n'a qu'une seule instance, implémente Java niveau de verrouillage sur lui-même. Ainsi, lorsque vous exécutez réellement des opérations de base de données, toutes les autres opérations db seront verrouillées Donc, même si vous avez plusieurs threads qui font des choses, si vous le faites pour maximiser les performances de la base de données, j'ai de mauvaises nouvelles pour vous. Aucun avantage.

Observations intéressantes

Si vous désactivez un thread d'écriture, donc un seul thread écrit dans la base de données, mais une autre lecture, et les deux ont leurs propres connexions, les performances de lecture augmentent WAY et je ne vois aucun problème de verrouillage. C'est quelque chose à poursuivre. Je n'ai pas encore essayé avec l'écriture par lots.

Si vous prévoyez d'effectuer plusieurs mises à jour de quelque nature que ce soit, encapsulez-les dans une transaction. Il semble que les 50 mises à jour que je fais dans la transaction prennent le même temps que la 1 mise à jour en dehors de la transaction. Je suppose qu'en dehors des appels de transaction, chaque mise à jour tente d'écrire les modifications de la base de données sur le disque. À l'intérieur de la transaction, les écritures sont effectuées en un seul bloc, et la surcharge d'écriture éclipse la logique de mise à jour elle-même.

4
user1530779

Oui, c'est la façon dont vous devez procéder, en ayant une classe d'assistance pour les activités qui nécessitent une instance de la base de données.

1
coder_For_Life22