2009-09-04 10 views
19

Sto scrivendo un provider di contenuti per questa applicazione e nel provider di contenuti sto aprendo una connessione al database, eseguendo una query e restituendo il cursore dei risultati al programma chiamante. Se chiudo questa connessione al database nel provider, il cursore non ha risultati. Se lo lascio aperto, ricevo errori "leak found" nel mio registro DDMS. Cosa mi manca qui? Qual è il modo pulito e corretto per restituire un cursore dei risultati del database?Problema di perdita del database del provider di contenuti Android

+2

Sembra che non sia necessario chiudere il database. La documentazione dice che il database è memorizzato nella cache in modo da avere un'istanza di database aperta per l'intera durata dell'applicazione. http://developer.android.com/reference/android/database/sqlite/SQLiteOpenHelper.html#getWritableDatabase() –

risposta

5

È perfettamente funzionante lasciando la connessione al database aperta durante l'intero runtime della tua app, devi solo assicurarti di chiudere il cursore dopo averlo fatto.

Suppongo che stai interrogando e utilizzando i cursori in un'attività? in tal caso, assicurati di chiudere i cursori chiamando il metodo cursor.close();, noto che se non stai chiudendo i cursori in un'attività e quindi passi a un'altra attività, otterrai questi messaggi di perdita quando esegui un'altra query.

Trovo che sia consigliabile escludere il metodo onDestroy nella propria attività e chiudere tutti i cursori in esso contenuti.

+0

Sto utilizzando la chiamata managedQuery per chiamare il provider. Avevo l'impressione che gestisse il ciclo di vita del cursore per me, ma tornerò a riesaminarlo solo per essere sicuro. È sicuro quindi ignorare questi errori di perdita? – MattC

+0

Anche io ho fatto le stesse supposizioni sul ciclo di vita, ho iniziato a notare che i messaggi di errore sparivano una volta che ho iniziato a chiudere i cursori. A volte ottenevo eccezioni generate a causa di cursori non chiusi durante il debug, quando ciò accadeva uccideva la mia sessione di debug, quasi mai immediatamente lanciava immediatamente questi errori ed Eccezioni. –

13

Non ti manca nulla AFAIK. Android manca uno onDestroy() (o l'equivalente) per ContentProvider. Non c'è nemmeno nulla nel codice sorgente in quest'area che suggerisce che ci sia una sorta di onDestroy() che non è emerso nell'SDK.

Se si guarda il codice sorgente per AlarmProvider e LauncherProvider, hanno anche a creare oggetti di database su una base per-API-call (per esempio, ogni volta che ottengono insert(), aprono un manico di database scrivibile che non hanno mai vicino).

+0

argh che significa che devo vivere con il database aperto :( – Janusz

+0

"creano persino oggetti di database su una base di chiamata per API [...] che non chiudono mai", vedi 'SQLiteOpenHelper # getWritableDatabase':" Una volta aperto con successo, il database è memorizzato nella cache. "Anche se sono d'accordo, quell'istanza dovrebbe essere chiusa qualche volta ... – TWiStErRob

11

Uno dei miei fornitori di contenuti gestisce più database, stesso schema con set di dati diversi. Attraverso il Garbage Collection è stato scoperto che esisteva un database aperto nel mio provider di contenuti che non aveva più alcun riferimento a questo, anche se SQLiteCursor mi lasciava diverse scelte:

1) Lascia aperto l'oggetto SQLiteDatabase e metterlo in una collezione, per non essere mai più usato.

2) Lasciare aperto l'oggetto SQLiteDatabase e sviluppare una cache che consenta di riutilizzare l'oggetto del database quando si accede allo stesso database.

3) Chiudere il database quando il cursore è chiuso.

Soluzione 1 contrasta con il mio giudizio migliore. La soluzione 1 è solo un'altra forma di perdita di risorse; la sua unica speranza è che non sconvolge il sistema. Ho deciso questa scelta immediatamente.

Soluzione 2 era la mia idea della soluzione migliore. Ha conservato le risorse allo stesso tempo riducendo il tempo di esecuzione senza dover riaprire le connessioni al database. Il lato negativo di questa soluzione era che avrei dovuto scrivere la cache e avrebbe aumentato la dimensione dell'applicazione. La dimensione non era un problema, ma il tempo per scrivere era. Ho passato questa soluzione per il momento presente e potrei tornare più tardi.

Soluzione 3 è quello che ha deciso di andare con. In primo luogo, ho pensato che sarebbe stato semplice da fare; Nell'attività, tutto ciò che dovevo fare era riordinare il Cursore restituito dal mio Content Provider in un SQLiteCursor. Quindi potrei invocare il suo metodo getDatabase() e richiamare close() sul database.

Questo non è possibile. Risulta che il cursore restituito dal Content Provider si trova all'interno di una classe wrapper che impedisce l'accesso diretto all'oggetto Cursor effettivo. Il wrapper delega i metodi che chiama al Cursore. Nessuna possibilità di riportare il cursore sul suo tipo derivato.

Quindi, invece di affidare la responsabilità di chiudere il database sull'attività, ho seguito un percorso diverso e più semplice. Ho derivato la mia classe Cursor estendendo la classe SQLiteCursor. Ho aggiunto due membri di dati alla mia classe derivata; uno per fare riferimento al database e l'altro era un ID per l'uso nel debug. Il Ctor della classe ha avuto la stessa firma di SQLiteCursor Ctor con un parametro aggiuntivo aggiunto alla fine per l'impostazione del valore ID. Il ctor imposta il membro dei dati del database per assicurare che qualcosa stia facendo riferimento al database se si è verificato un Garbage Collection prima che il cursore fosse chiuso.

Sovrascrivo il metodo close() in modo che chiudesse il cursore [super.close()] e chiudesse il database se il riferimento non era nullo.

Ho anche eseguito l'override del metodo toString() in modo che il numero ID sia stato aggiunto alla stringa. Questo mi ha permesso di monitorare quali cursori erano ancora aperti e quali sono stati aperti e chiusi nel file di registro.

Ho anche aggiunto un metodo closeForReuse() in modo che il provider di contenuti possa riutilizzare un database per più query senza dover aprire ogni volta una nuova connessione al database.

Ho anche creato una classe che ha implementato l'interfaccia SQLiteDatabase.CursorFactory. Ha creato la mia nuova classe cursore derivata e gestito il valore ID passato a ciascuno.

Questa soluzione richiede comunque che ogni attività chiuda i cursori passati ad esso quando hanno finito di usare il cursore. Poiché questa è una buona pratica di programmazione, non è stata una preoccupazione.

Problemi correlati