17

ambiente (Linux/Eclipse Dev per Xoom tablet con sistema operativo HoneyComb 3.0.1)errore Android: java.lang.IllegalStateException: cercando di requery un cursore già chiuso

Nella mia app sto usando la macchina fotografica (startIntentForResult ()) fare una foto. Dopo aver scattato la foto, ottengo il callback onActivityResult() e sono in grado di caricare una Bitmap usando un Uri passato tramite l'intento di "scattare foto". A quel punto la mia attività è ripresa ed ottengo un errore nel tentativo di ricaricare le immagini in una galleria:

FATAL EXCEPTION: main 
ERROR/AndroidRuntime(4148): java.lang.RuntimeException: Unable to resume activity {...}: 
java.lang.IllegalStateException: trying to requery an already closed cursor 
    at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:2243) 
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1019) 
    at android.os.Handler.dispatchMessage(Handler.java:99) 
    at android.os.Looper.loop(Looper.java:126) 
    at android.app.ActivityThread.main(ActivityThread.java:3997) 
    at java.lang.reflect.Method.invokeNative(Native Method) 
    at java.lang.reflect.Method.invoke(Method.java:491) 
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:841) 
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:599) 
    at dalvik.system.NativeStart.main(Native Method) 
Caused by: java.lang.IllegalStateException: trying to requery an already closed cursor 
    at android.app.Activity.performRestart(Activity.java:4337) 
    at android.app.Activity.performResume(Activity.java:4360) 
    at android.app.ActivityThread.performResumeActivity(ActivityThread.java:2205) 
    ... 10 more 

La logica unica del cursore che sto utilizzando è che dopo aver scattato l'immagine converte l'Uri in un file usando la seguente logica

String [] projection = { 
    MediaStore.Images.Media._ID, 
    MediaStore.Images.ImageColumns.ORIENTATION, 
    MediaStore.Images.Media.DATA 
}; 

Cursor cursor = activity.managedQuery( 
     uri, 
     projection, // Which columns to return 
     null,  // WHERE clause; which rows to return (all rows) 
     null,  // WHERE clause selection arguments (none) 
     null);  // Order-by clause (ascending by name) 

int fileColumnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); 
if (cursor.moveToFirst()) { 
    return new File(cursor.getString(fileColumnIndex)); 
} 
return null; 

Qualche idea su cosa sto facendo male?

risposta

22

Sembra che la chiamata managedQuery() sia deprecata nell'API Honeycomb.

Doc per managedQuery() si legge:

This method is deprecated. 
Use CursorLoader instead. 

Wrapper around query(android.net.Uri, String[], String, String[], String) 
that the resulting Cursor to call startManagingCursor(Cursor) so that the 
activity will manage its lifecycle for you. **If you are targeting HONEYCOMB 
or later, consider instead using LoaderManager instead, available via 
getLoaderManager()**. 

Inoltre ho notato che mi stava chiamando cursor.close() dopo che la query che credo sia un no-no. Trovato questo really helpful link pure. Dopo alcune letture ho trovato questo cambiamento che sembra funzionare.

// causes problem with the cursor in Honeycomb 
Cursor cursor = activity.managedQuery( 
     uri, 
     projection, // Which columns to return 
     null,  // WHERE clause; which rows to return (all rows) 
     null,  // WHERE clause selection arguments (none) 
     null);  // Order-by clause (ascending by name) 

// ------------------------------------------------------------------- 

// works in Honeycomb 
String selection = null; 
String[] selectionArgs = null; 
String sortOrder = null; 

CursorLoader cursorLoader = new CursorLoader(
     activity, 
     uri, 
     projection, 
     selection, 
     selectionArgs, 
     sortOrder); 

Cursor cursor = cursorLoader.loadInBackground(); 
+0

State di mira specificamente a nido d'ape. Ho appena scoperto che la mia app Eclair + è stata interrotta in Honeycomb, a causa di startManagingCursor(). La mia app è destinata ai telefoni, ma non aspetta i clienti arrabbiati quando esce Ice Cream Sandwich. – Tenfour04

+0

Sì, sono specificamente mirato al favo (compresse). –

+0

@ cyber-monaco Ciao - Sto avendo un problema. Non riesco ad implementarlo perché il mio codice non riconosce CursorLoader. L'ho importato correttamente ma non riconosce l'importazione. Come posso risolvere questo? – Mxyk

5

FIX: utilizzare context.getContentResolver().query invece di activity.managedQuery.

Cursor cursor = null; 
try { 
    cursor = context.getContentResolver().query(uri, PROJECTION, null, null, null); 
} catch(Exception e) { 
    e.printStackTrace(); 
} 
return cursor; 
+0

Sto affrontando il problema simile sul sistema operativo Android versione 3.0 e successive, sto prendendo di mira l'app per smartphone e tablet. Ho provato le soluzioni suggerite sopra, non sembravano risolvere questo problema. Se tutti hanno un approccio alternativo per risolvere questo problema, si prega di postarlo. – Rise

+0

@Rupesh. Immagino che tu abbia risolto il tuo problema ormai, ma da quando hai chiesto, ho pubblicato la mia soluzione in una nuova risposta. Speriamo che gli altri ne trarranno beneficio. –

7

Per la cronaca, ecco come ho risolto questo nel mio codice (che gira su Android 1.6 e superiori): Il problema nel mio caso era che stavo chiudendo inavvertitamente cursori gestite chiamando CursorAdapter.changeCursor(). Chiamata Activity.stopManagingCursor() sul cursore della scheda prima di cambiare il cursore risolto il problema:

// changeCursor() will close current one for us: we must stop managing it first. 
Cursor currentCursor = ((SimpleCursorAdapter)getListAdapter()).getCursor(); // *** adding these lines 
stopManagingCursor(currentCursor);           // *** solved the problem 
Cursor c = db.fetchItems(selectedDate); 
startManagingCursor(c); 
((SimpleCursorAdapter)getListAdapter()).changeCursor(c); 
3

Ho creato questa domanda qui come non ho potuto commentare l'ultima risposta (commenti disabili per qualche motivo). Pensavo che aprire una nuova discussione su questo avrebbe solo complicato le cose.

ottengo crash dell'applicazione quando vado da Activity Un a attività B e poi tornare a Attività A. Questo non succede sempre - solo qualche volta e sto avendo difficoltà a trovare ESATTAMENTE dove ciò accade. Tutto accade sullo stesso dispositivo (Nexus S) ma non credo che questo sia un problema del dispositivo.

Ho alcune domande riguardanti la risposta di @Martin Stine.

  • Nella documentazione che dice di changeCursor(c);: "Cambia il cursore sottostante ad un nuovo cursore Se c'è un cursore esistente sarà chiuso.". Quindi, perché devo stopManagingCursor(currentCursor); - non è ridondante?
  • Quando uso il codice offerto da @Martin Stine ottengo un'eccezione di puntatore nullo. Il motivo è che nella prima "esecuzione" dell'applicazione ((SimpleCursorAdapter)getListAdapter()) verrà valutata NULL perché non è stato ancora creato alcun cursore.Certo ho potuto verificare se non ottengo un nullo e solo allora provare ad arrestare per gestire il cursore ma alla fine ho deciso di mettere la mia `stopManagingCursor (currentCursor); nel metodo onPause() di questa attività. Ho pensato che in questo modo avrò sicuramente un cursore per smettere di gestire e dovrei farlo appena prima di lasciare l'attività su un'altra. Il problema - sto usando diversi cursori (uno per riempire un testo di un campo EditText e l'altro per una visualizzazione elenco) nella mia attività io non immagino tutti loro sono legati a un cursore ListAdapter -
    • Come faccio sapere quale smettere di gestire? Se ho 3 diverse visualizzazioni di lista?
    • Dovrei chiudere tutti loro durante onPause()?
    • Come ottengo una lista di tutti i miei cursori aperti?

Tante domande ... La speranza che qualcuno potesse aiutare.

Quando arrivo a onPause() ho un cursore per interrompere la gestione ma devo ancora determinare se questo risolve il problema in quanto questo errore si presenta sporadicamente.

Grazie mille!


Dopo alcune indagini:

ho trovato qualcosa di interessante che potrebbe dare la risposta al lato "misterioso" di questo numero:

Attività A utilizza due cursori: uno di riempire un EditText campo. L'altro è popolare un ListView.

Quando si passa da Activity A alla attività B e tornando, il campo + ListView in attività A deve essere riempito di nuovo. Sembra che il campo EditText non abbia mai un problema con quello. Non ho trovato un modo per ottenere il cursore corrente del campo EditText (come in Cursor currentCursor = ((SimpleCursorAdapter)getListAdapter()).getCursor();) e la ragione mi dice che il campo EditText non lo manterrà. D'altra parte il ListView "ricorderà" il suo cursore dall'ultima volta (da prima dell'attività A -> Attività B). Inoltre, e questa è la cosa strana, il Cursor currentCursor = ((SimpleCursorAdapter)getListAdapter()).getCursor(); avrà un ID diverso dopo l'attività B -> Attività A e tutto questo SENZA me mai chiamare

Cursor currentCursor = ((SimpleCursorAdapter)getListAdapter()).getCursor(); 
stopManagingCursor(currentCursor); 

Credo che in alcuni casi, quando le esigenze del sistema per liberare risorse, il cursore verrà ucciso e quando Attività B -> Attività A, il sistema tenterà comunque di utilizzare questo vecchio cursore morto, che genererà un'eccezione. E in altri casi il sistema visualizzerà un nuovo cursore che è ancora vivo e quindi non si verificherà alcuna eccezione. Questo potrebbe spiegare perché questo si presenta solo a volte. Immagino che sia difficile eseguire il debug a causa della differenza di velocità dell'applicazione durante l'esecuzione o il debug dell'applicazione. Durante il debug, ci vuole più tempo e quindi potrebbe dare al sistema il tempo di creare un nuovo cursore o viceversa.

Nella mia comprensione questo rende l'utilizzo di

Cursor currentCursor = ((SimpleCursorAdapter)currentListAdapter).getCursor(); 
stopManagingCursor(currentCursor); 

Come raccomandato dal @ Martin Stine un must in alcuni casi e ridondante negli altri: se torno al metodo e il sistema sta cercando per utilizzare un cursore morto, è necessario creare un nuovo cursore e sostituirlo in ListAdapter, altrimenti mi arrabbio con gli utenti dell'app con un'app bloccata.In un altro caso quando il sistema si troverà un nuovo cursore - le linee sopra sono ridondanti in quanto invalideranno un buon cursore e ne creeranno uno nuovo.

immagino per evitare questa ridondanza avrei bisogno di qualcosa di simile:

ListAdapter currentListAdapter = getListAdapter(); 
Cursor currentCursor = null; 
Cursor c = null; 

//prevent Exception in case the ListAdapter doesn't exist yet 
if(currentListAdapter != null) 
    { 
     currentCursor = ((SimpleCursorAdapter)currentListAdapter).getCursor(); 

        //make sure cursor is really dead to prevent redundancy 
        if(currentCursor != null) 
        { 
         stopManagingCursor(currentCursor); 

         c = db.fetchItems(selectedDate); 

         ((SimpleCursorAdapter)getListAdapter()).changeCursor(c); 
        } 
        else 
        { 
         c = db.fetchItems(selectedDate); 

        } 
    } 
      else 
      { 
       c = db.fetchItems(selectedDate); 

      } 

startManagingCursor(c); 

Mi piacerebbe sentire cosa ne pensate di questo!

0

Questo problema mi ha tormentato per molto tempo e alla fine ho trovato una soluzione semplice che funziona come un fascino su tutte le versioni di Android. Innanzitutto, non utilizzare startManagingCursor() poiché è evidentemente bug e deprecato in ogni caso. In secondo luogo, chiudi un Cursore il più presto possibile dopo aver finito con esso. Io cerco di provare e infine di assicurare che il Cursore si chiuda in tutte le circostanze. Se il tuo metodo deve restituire un cursore, la routine chiamante è responsabile della sua chiusura il prima possibile.

Ho usato lasciare i cursori aperti per tutta la durata di un'attività, ma da allora l'ho abbandonato per questo approccio transazionale. Ora la mia app è molto stabile e non presenta "Errore Android: java.lang.IllegalStateException: prova a richiedere un cursore già chiuso" quando si passa a Attività anche se accede allo stesso database.

static public Boolean musicReferencedByOtherFlash(NotesDB db, long rowIdImage) 
    { 
     Cursor dt = null; 
     try 
     { 
     dt = db.getNotesWithMusic(rowIdImage); 
     if ( (dt != null) 
      && (dt.getCount() > 1)) 
      return true; 
     } 
     finally 
     { 
     if (dt != null) 
      dt.close(); 
     } 
     return false; 
    } 
2

Basta aggiungere il seguente codice alla fine del blocco del cursore ..

try { 
       Cursor c = db.displayName(number); 

       startManagingCursor(c); 
       if (!c.moveToFirst()) { 
        if (logname == null) 
         logname = "Unknown"; 
        System.out.println("Null " + logname); 
       } else { 
        logname = c.getString(c 
          .getColumnIndex(DataBaseHandler.KEY_NAME)); 
        logdp = c.getBlob(c 
          .getColumnIndex(DataBaseHandler.KEY_IMAGE)); 
        // tvphoneno_oncall.setText(logname); 
        System.out.println("Move name " + logname); 
        System.out.println("Move number " + number); 
        System.out.println("Move dp " + logdp); 
       } 

       stopManagingCursor(c); 
      } 
+1

stopManagingCursor (c); alla fine ha funzionato per me! Semplice! :) –

Problemi correlati