2010-12-28 3 views
71

Diese Woche habe ich alles über ContentProvider gelernt und die SQLiteOpenHelper-Klasse verwendet, um die Erstellung und Aktualisierung der Datenbank innerhalb eines Providers zu verwalten. Insbesondere habe ich das NotePad-Beispiel aus dem Samples-Verzeichnis des SDK gelesen.Schließen der Datenbank in einem ContentProvider

Jetzt kann ich sehen, dass SQLiteOpenHelper eine close() -Methode hat. Ich bin mir bewusst, dass das Öffnen leerer Datenbanken eine schlechte Übung ist und Speicherlecks verursachen kann (außer this Diskussion geht in die richtige Richtung). Wenn ich die Klasse in einer Aktivität verwenden würde, würde ich einfach close() in der onDestroy() Methode aufrufen, aber soweit ich weiß, hat ContentProvider nicht den gleichen Lebenszyklus wie Aktivitäten. Der Code für NotePad scheint niemals clos() zu heißen, also würde ich gerne annehmen, dass es von SQLiteOpenHelper oder einem anderen Teil des Puzzles gehandhabt wird, aber ich würde es gerne wissen. Ich traue dem Beispielcode nicht so viel, entweder ...

Fragezusammenfassung: Wann sollten wir die Datenbank in einem Anbieter schließen, wenn überhaupt?

+11

Dianne Hackborn sagte, es gibt [keine Notwendigkeit, die db zu schließen] (http://groups.google.com/d/msg/android-developers/NwDRpHUXt0U/jIam4Q8-cqQJ). – bigstones

+1

Dies ist die wichtigste Information zu diesem Thema. Ich habe es zur Antwort gemacht. – philipp

Antwort

88

According to Dianne Hackborn (Android Framework-Ingenieur), gibt es keine Notwendigkeit, die Datenbank in einem Content-Provider zu schließen.

Ein Content-Provider erstellt wird, wenn seine Hosting-Prozess erstellt wird, und bleibt so lange um, als der Prozess der Fall ist, so gibt es keine Notwendigkeit, schließen Sie die Datenbank - es wird als Teil der geschlossen erhalten Kernel Bereinigen der Ressourcen des Prozesses, wenn der Prozess beendet wird.

Dank @bigstones für das Aufzeigen.

+5

Danke. Btw, sie (Android-Team) sollten diese "einfache" Sache in [Richtlinie] (http://developer.android.com/guide/topics/providers/content-provider-creating.html) oder zumindest in der Probe kommentieren Code, anstatt Coders im Internet danach suchen zu lassen. –

+14

fange mich nicht an, was fehlt :) – philipp

+3

Ok, aber was macht die Shutdown-Funktion. Der Code lautet: public void shutdown() { Log.w (TAG, "implementieren ContentProvider shutdown() um sicherzustellen, dass alle Datenbank" + "Verbindungen ordnungsgemäß heruntergefahren werden"); } – ata

1

Schließen Sie es, wenn Sie damit fertig sind, vorzugsweise in einem endgültigen Block, so dass Sie sicherstellen können, dass es passiert. Ich weiß, das hört sich etwas banal und aus dem Stegreif an, aber es ist wirklich die einzige Antwort, die ich kenne. Wenn Sie die Datenbank öffnen und eine Aktion ausführen, schließen Sie sie, wenn Sie mit dieser Aktion fertig sind, es sei denn, Sie wissen, dass sie erneut benötigt wird (in diesem Fall müssen Sie sie schließen, sobald sie nicht mehr benötigt wird).

+2

Der einzige Weg, wie wir wissen können, ob wir ihn wieder brauchen, kommt von außerhalb des Anbieters (im Code, der ihn benutzt). Ich denke, dass innerhalb des Providers auf die Datenbank jedes Mal zugegriffen wird, wenn getWriteableDatabase() oder getReadableDatabase() auf dem SQLiteOpenHelper aufgerufen wird. Sollte ich nach Ihrem Vorschlag in jeder Methode, in der diese aufgerufen werden, eine close() hinzufügen? Es sieht so aus, als würden mehrere Abfragen hintereinander ausgeführt werden, dann würde eine ganze Reihe von Datenbank-Öffnungs- und Schließvorgängen stattfinden. Ich bin mir nicht sicher, aber ich würde mir vorstellen, dass dies die Leistung beeinträchtigen würde. – SilithCrowe

0

Wenn Sie Ihren Inhaltsanbieter innerhalb einer Aktivität verwenden, glaube ich nicht, dass Sie die Verbindung des Inhaltsanbieters aufrechterhalten müssen. Sie können einfach das Cursor-Objekt verwalten, das mit startManagingCursor zurückgegeben wird. In der onPause-Methode der Aktivität können Sie den Inhaltsanbieter freigeben. (Sie können es in onResume neu laden). Unter der Annahme, dass der Aktivitätslebenszyklus normalerweise begrenzt ist, würde dies ausreichen. (Zumindest nach mir;))

+0

Natürlich, wenn Sie sql lite verwenden, können Sie die Verbindung nach dem Abrufen der Ergebnisse schließen. (Stellen Sie sicher, dass der Lebenszyklus des Cursors durch Aktivität mit startmanagingcursor behandelt wird. –

7

Wenn Sie Ihre Datenbank automatisch geschlossen und Sie können CursorFactory bieten, wenn es zu öffnen:

mContext.openOrCreateDatabase(DB_NAME, SQLiteDatabase.OPEN_READWRITE, new LeaklessCursorFactory()); 

Hier sind die Klassen:

public class LeaklessCursorFactory implements CursorFactory { 
    @Override 
    public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery, 
     String editTable, SQLiteQuery query) { 
     return new LeaklessCursor(db,masterQuery,editTable,query); 
    } 
} 


public class LeaklessCursor extends SQLiteCursor { 
    static final String TAG = "LeaklessCursor"; 
    final SQLiteDatabase mDatabase; 

    public LeaklessCursor(SQLiteDatabase database, SQLiteCursorDriver driver, String table, SQLiteQuery query) { 
     super(database, driver, table, query); 
     mDatabase = database; 
    } 

    @Override 
    public void close() { 
     Log.d(TAG, "Closing LeaklessCursor: " + mDatabase.getPath()); 
     super.close(); 
     if (mDatabase != null) { 
      mDatabase.close(); 
     } 
    } 
} 
+1

Hinweis für andere: Bitte beachten Sie auch die Antwort von Pleerock, da diese Antwort auf eine kleine, aber wichtige Weise aktualisiert wird. Dies ist übrigens eine großartige Lösung - I immer ein bisschen schwindlig, wenn ich guten Gebrauch von Entwurfsmustern sehe.: P – SilithCrowe

13

Ive folgen Mannaz Antwort und sah, dass SQLiteCursor(database, driver, table, query); Konstruktor veraltet ist. Dann fand ich getDatabase() Methode und verwendete es anstelle von mDatabase Zeiger; und gehalten Konstruktor für Rückwärts Fähigkeit

public class MyOpenHelper extends SQLiteOpenHelper { 
    public static final String TAG = "MyOpenHelper"; 

    public static final String DB_NAME = "myopenhelper.db"; 
    public static final int DB_VESRION = 1; 

    public MyOpenHelper(Context context) { 
     super(context, DB_NAME, new LeaklessCursorFactory(), DB_VESRION); 
    } 

    //... 
} 

public class LeaklessCursor extends SQLiteCursor { 
    static final String TAG = "LeaklessCursor"; 

    public LeaklessCursor(SQLiteDatabase db, SQLiteCursorDriver driver, 
      String editTable, SQLiteQuery query) { 
     super(db, driver, editTable, query); 
    } 

    @Override 
    public void close() { 
     final SQLiteDatabase db = getDatabase(); 
     super.close(); 
     if (db != null) { 
      Log.d(TAG, "Closing LeaklessCursor: " + db.getPath()); 
      db.close(); 
     } 
    } 
} 


public class LeaklessCursorFactory implements CursorFactory { 
    @Override 
    public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery, 
     String editTable, SQLiteQuery query) { 
     return new LeaklessCursor(db,masterQuery,editTable,query); 
    } 
} 
+1

Ich liebe es, wenn Leute sich die Zeit nehmen, ältere Fragen zu beantworten/zu aktualisieren. Danke Pleorock! Sieht so aus, als müsste ich dieses ... – SilithCrowe

+0

YW So fand ich ein "Leck" in unserem LeaklessCursor. Es ist nicht effektiv, wenn Sie Inhaltsanbieter verwenden. Wenn Sie beispielsweise Daten in der Datenbank aktualisieren, wird auch der Cursor aktualisiert. Dieser Cursor wird also geschlossen und neu Der Cursor wird geöffnet. Wenn unser Cursor geschlossen wird, wird auch unsere Datenbank geschlossen, was zu einem Fehler führen kann.Zum Beispiel: ContentProvider (öffnet die Datenbank) -> query (für den Cursor, verwendet db) -> update (alle Daten, verwendet db) -> benachrichtigt-> schließt einen alten Cursor (schließt auch db) -> erstellt neuen Cursor (Abfrage, verwendet db) und POOOOW error -> db wurde geschlossen, kann den Cursor nicht öffnen – pleerock

+0

Wenn der getDatabase() - Aufruf null zurückgeben kann, dann könnte der Log.d. (...) abstürzen, habe ich einen Edit eingefügt, der das Log in den if-Anweisung und setzt getDatabase() in eine Referenz, da Sie sie dreimal verwenden. Sie können ein Protokoll außerhalb der if-Anweisung hinzufügen, das Sie darüber informiert, dass Sie eine Datenbank "schließen" sollten, unabhängig davon, ob getDatabase() null war, wenn das nützlich ist. –

21

Diese Frage ist ein bisschen alt, aber immer noch ziemlich relevant. Beachten Sie, dass Sie, wenn Sie Dinge auf "moderne" Art und Weise tun (z. B. LoaderManager verwenden und CursorLoaders erstellen, um einen ContentProvider in einem Hintergrundthread abzufragen), sicherstellen, dass Sie db.close() NICHT in Ihrer ContentProvider-Implementierung aufrufen. Ich habe alle Arten von Abstürzen im Zusammenhang mit CursorLoader/AsyncTaskLoader bekommen, als ich versuchte, auf den ContentProvider in einem Hintergrundthread zuzugreifen, die durch Entfernen der db.close() - Aufrufe gelöst wurden.

Also, wenn Sie in Abstürze laufen lassen, die wie folgt aussehen (Jelly Bean 4.1.1):

Caused by: java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed. 
    at android.database.sqlite.SQLiteConnectionPool.throwIfClosedLocked(SQLiteConnectionPool.java:962) 
    at android.database.sqlite.SQLiteConnectionPool.waitForConnection(SQLiteConnectionPool.java:677) 
    at android.database.sqlite.SQLiteConnectionPool.acquireConnection(SQLiteConnectionPool.java:348) 
    at android.database.sqlite.SQLiteSession.acquireConnection(SQLiteSession.java:894) 
    at android.database.sqlite.SQLiteSession.executeForCursorWindow(SQLiteSession.java:834) 
    at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:62) 
    at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:143) 
    at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:133) 
    at android.content.ContentResolver.query(ContentResolver.java:388) 
    at android.content.ContentResolver.query(ContentResolver.java:313) 
    at com.hindsightlabs.paprika.loaders.GroceryListLoader.loadInBackground(GroceryListLoader.java:147) 
    at com.hindsightlabs.paprika.loaders.GroceryListLoader.loadInBackground(GroceryListLoader.java:1) 
    at android.support.v4.content.AsyncTaskLoader.onLoadInBackground(AsyncTaskLoader.java:240) 
    at android.support.v4.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:51) 
    at android.support.v4.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:40) 
    at android.support.v4.content.ModernAsyncTask$2.call(ModernAsyncTask.java:123) 
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305) 
    ... 4 more 

Oder diese (ICS 4.0.4):

Caused by: java.lang.IllegalStateException: database /data/data/com.hindsightlabs.paprika/databases/Paprika.db (conn# 0) already closed 
    at android.database.sqlite.SQLiteDatabase.verifyDbIsOpen(SQLiteDatabase.java:2215) 
    at android.database.sqlite.SQLiteDatabase.lock(SQLiteDatabase.java:436) 
    at android.database.sqlite.SQLiteDatabase.lock(SQLiteDatabase.java:422) 
    at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:79) 
    at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:164) 
    at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:156) 
    at android.content.ContentResolver.query(ContentResolver.java:318) 
    at android.support.v4.content.CursorLoader.loadInBackground(CursorLoader.java:49) 
    at android.support.v4.content.CursorLoader.loadInBackground(CursorLoader.java:35) 
    at android.support.v4.content.AsyncTaskLoader.onLoadInBackground(AsyncTaskLoader.java:240) 
    at android.support.v4.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:51) 
    at android.support.v4.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:40) 
    at android.support.v4.content.ModernAsyncTask$2.call(ModernAsyncTask.java:123) 
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305) 
    ... 4 more 

Oder wenn Sie Fehlermeldungen in LogCat sind zu sehen, die wie folgt aussehen:

Cursor: invalid statement in fillWindow() 

Dann Ihre Content Implementierung überprüfen und sicherzustellen, dass Sie die Datenbank nicht vorzeitig geschlossen wird. Gemäß this wird der ContentProvider automatisch bereinigt, wenn der Prozess trotzdem beendet wird, sodass Sie die Datenbank nicht vorzeitig schließen müssen.

Das heißt, stellen Sie sicher, Sie sind immer noch richtig:

  1. Ihre Cursoren Closing, die von ContentProvider.query() zurückgegeben werden. (CursorLoader/LoaderManager erledigt dies automatisch für Sie. Wenn Sie jedoch direkte Abfragen außerhalb des LoaderManager-Frameworks durchführen oder eine benutzerdefinierte CursorLoader/AsyncTaskLoader-Unterklasse implementiert haben, müssen Sie sicherstellen, dass Sie Ihre Cursor bereinigen richtig.)
  2. Implementieren Sie Ihren ContentProvider auf thread-sichere Weise. (Der einfachste Weg, dies zu tun ist, Ihre Datenbank-Zugriffsmethoden, um sicherzustellen, eingewickelt in einem synchronisiert Block.)