2010-12-16 4 views
38

¿Dónde debo llamar a close() en el código?Error de Android - close() nunca fue llamado explícitamente en la base de datos

El LogCat devuelve este error:

close() was never explicitly called on database android.database.sqlite.DatabaseObjectNotClosedException: Application did not close the cursor or database object that was opened here

El error es la siguiente:

> 12-16 17:24:50.886: ERROR/Database(10982): close() was never explicitly called on database '/data/data/com.psyhclo/databases/calls.db' 
12-16 17:24:50.886: ERROR/Database(10982): android.database.sqlite.DatabaseObjectNotClosedException: Application did not close the cursor or database object that was opened here 
12-16 17:24:50.886: ERROR/Database(10982):  at android.database.sqlite.SQLiteDatabase.<init>(SQLiteDatabase.java:1827) 
12-16 17:24:50.886: ERROR/Database(10982):  at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:820) 
12-16 17:24:50.886: ERROR/Database(10982):  at android.database.sqlite.SQLiteDatabase.openOrCreateDatabase(SQLiteDatabase.java:854) 
12-16 17:24:50.886: ERROR/Database(10982):  at android.database.sqlite.SQLiteDatabase.openOrCreateDatabase(SQLiteDatabase.java:847) 
12-16 17:24:50.886: ERROR/Database(10982):  at android.app.ContextImpl.openOrCreateDatabase(ContextImpl.java:541) 
12-16 17:24:50.886: ERROR/Database(10982):  at android.content.ContextWrapper.openOrCreateDatabase(ContextWrapper.java:203) 
12-16 17:24:50.886: ERROR/Database(10982):  at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:118) 
12-16 17:24:50.886: ERROR/Database(10982):  at com.psyhclo.CallDataHelper.<init>(CallDataHelper.java:27) 
12-16 17:24:50.886: ERROR/Database(10982):  at com.psyhclo.RatedCalls.fillList(RatedCalls.java:66) 
12-16 17:24:50.886: ERROR/Database(10982):  at com.psyhclo.RatedCalls.access$0(RatedCalls.java:63) 
12-16 17:24:50.886: ERROR/Database(10982):  at com.psyhclo.RatedCalls$RatedCallsContentObserver.onChange(RatedCalls.java:58) 
12-16 17:24:50.886: ERROR/Database(10982):  at android.database.ContentObserver$NotificationRunnable.run(ContentObserver.java:43) 
12-16 17:24:50.886: ERROR/Database(10982):  at android.os.Handler.handleCallback(Handler.java:587) 
12-16 17:24:50.886: ERROR/Database(10982):  at android.os.Handler.dispatchMessage(Handler.java:92) 
12-16 17:24:50.886: ERROR/Database(10982):  at android.os.Looper.loop(Looper.java:123) 
12-16 17:24:50.886: ERROR/Database(10982):  at android.app.ActivityThread.main(ActivityThread.java:3647) 
12-16 17:24:50.886: ERROR/Database(10982):  at java.lang.reflect.Method.invokeNative(Native Method) 
12-16 17:24:50.886: ERROR/Database(10982):  at java.lang.reflect.Method.invoke(Method.java:507) 
12-16 17:24:50.886: ERROR/Database(10982):  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839) 
12-16 17:24:50.886: ERROR/Database(10982):  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597) 
12-16 17:24:50.886: ERROR/Database(10982):  at dalvik.system.NativeStart.main(Native Method) 

aquí es mi código.

La Actividad:

public class RatedCalls extends ListActivity { 

private static final String LOG_TAG = "RATEDCALLSOBSERVER"; 
private Handler handler = new Handler(); 
private SQLiteDatabase db; 
private CallDataHelper cdh; 
StringBuilder sb = new StringBuilder(); 
OpenHelper openHelper = new OpenHelper(RatedCalls.this); 

@Override 
public void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    registerContentObservers(); 
    Log.d("FILLLIST", "calling from onCreate()"); 

} 

class RatedCallsContentObserver extends ContentObserver { 
    public RatedCallsContentObserver(Handler h) { 
     super(h); 
    } 

    @Override 
    public boolean deliverSelfNotifications() { 
     return true; 

    } 

    @Override 
    public void onChange(boolean selfChange) { 
     Log.d(LOG_TAG, "RatedCallsContentObserver.onChange(" + selfChange 
       + ")"); 
     super.onChange(selfChange); 
     fillList(); 
     onStop(); 
    } 
} 

private void fillList() { 

    db = openHelper.getWritableDatabase(); 
    cdh = new CallDataHelper(this); 
    String lastDate = cdh.selectDate(); 

    Cursor cursor = getContentResolver().query(
      android.provider.CallLog.Calls.CONTENT_URI, null, 
      android.provider.CallLog.Calls.DATE + " < ?", 
      new String[] { lastDate }, 
      android.provider.CallLog.Calls.DATE + " DESC "); 

    Log.d("FILLLIST", "Calling from filllist"); 

    startManagingCursor(cursor); 
    int numberColumnId = cursor 
      .getColumnIndex(android.provider.CallLog.Calls.NUMBER); 
    int durationId = cursor 
      .getColumnIndex(android.provider.CallLog.Calls.DURATION); 
    int contactNameId = cursor 
      .getColumnIndex(android.provider.CallLog.Calls.CACHED_NAME);   
    int numTypeId = cursor 
      .getColumnIndex(android.provider.CallLog.Calls.CACHED_NUMBER_TYPE); 

    Date dt = new Date(); 
    int hours = dt.getHours(); 
    int minutes = dt.getMinutes(); 
    int seconds = dt.getSeconds(); 
    String currTime = hours + ":" + minutes + ":" + seconds; 

    SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy"); 
    Date date = new Date(); 

    ArrayList<String> callList = new ArrayList<String>(); 
    cursor.moveToFirst(); 

    String contactNumber = cursor.getString(numberColumnId); 
    String contactName = cursor.getString(contactNameId); 
    String duration = cursor.getString(durationId); 
    // String callDate = DateFormat.getDateInstance().format(dateId); 
    String numType = cursor.getString(numTypeId); 

    ContentValues values = new ContentValues(); 

    values.put("contact_id", 1); 
    values.put("contact_name", contactName); 
    values.put("number_type", numType); 
    values.put("contact_number", contactNumber); 
    values.put("duration", duration); 
    values.put("date", dateFormat.format(date)); 
    values.put("current_time", currTime); 
    values.put("cont", 1); 
    getBaseContext().getContentResolver().notifyChange(
      android.provider.CallLog.Calls.CONTENT_URI, 
      new RatedCallsContentObserver(handler)); 
    db.insert(CallDataHelper.TABLE_NAME, null, values); 
    Toast.makeText(getBaseContext(), "Inserted!", Toast.LENGTH_LONG); 
    callList.add("Contact Number: " + contactNumber + "\nContact Name: " 
      + contactName + "\nDuration: " + duration + "\nDate: " 
      + dateFormat.format(date)); 

    setListAdapter(new ArrayAdapter<String>(this, R.layout.listitem, 
      callList)); 
    ListView lv = getListView(); 
    lv.setTextFilterEnabled(true); 

    lv.setOnItemClickListener(new android.widget.AdapterView.OnItemClickListener() { 

     @Override 
     public void onItemClick(AdapterView<?> parent, View view, 
       int position, long id) { 

      Toast.makeText(getApplicationContext(), 
        ((TextView) view).getText(), Toast.LENGTH_SHORT).show(); 

     } 
    }); 
} 

public void registerContentObservers() { 

    this.getApplicationContext() 
      .getContentResolver() 
      .registerContentObserver(
        android.provider.CallLog.Calls.CONTENT_URI, true, 
        new RatedCallsContentObserver(handler)); 

} 

public void unregisterContentObservers() { 

    this.getApplicationContext() 
      .getContentResolver() 
      .unregisterContentObserver(
        new RatedCallsContentObserver(handler)); 

} 
    } 

Y el DatabaseHelper

public class CallDataHelper { 

private static final String DATABASE_NAME = "calls.db"; 
private static final int DATABASE_VERSION = 1; 
protected static final String TABLE_NAME = "contact_data"; 

private Context context; 
private SQLiteDatabase db; 

public CallDataHelper(Context context) { 
    this.context = context; 
    OpenHelper openHelper = new OpenHelper(this.context); 
    this.db = openHelper.getWritableDatabase(); 
} 

public boolean insert(Integer cId, String cName, String numType, 
     String cNum, String dur, String date, String currTime) { 
    this.db.execSQL("insert into " 
      + TABLE_NAME 
      + " (contact_id, contact_name, number_type, contact_number, duration, date, current_time, cont) " 
      + " values(" + cId + ", " + cName + ", " + numType + ", " 
      + cNum + ", " + dur + ", " + date + ", " + currTime + ", 1)"); 
    return true;   
} 

public void update(String word) { 
    this.db.execSQL("UPDATE words SET cont = cont + 1 WHERE (word= ?)", 
      new String[] { word }); 
} 

public void deleteAll() { 
    this.db.delete(TABLE_NAME, null, null); 
} 

public String selectDate() { 

    String date = ""; 
    Cursor cursor = this.db.query(TABLE_NAME, new String[] { "date" }, 
      "id = ?", new String[] { "max(id)" }, null, null, null); 
    if (cursor.moveToFirst()) { 
     do { 
      date = cursor.getString(0); 
     } while (cursor.moveToNext()); 
    } 
    if (cursor != null && !cursor.isClosed()) { 
     cursor.close(); 
     return date; 
    } else { 
     return ""; 
    } 
} 

public List<String> selectAll() { 
    List<String> list = new ArrayList<String>(); 
    Cursor cursor = this.db.query(TABLE_NAME, new String[] { "word" }, 
      null, null, null, null, "cont desc"); 
    if (cursor.moveToFirst()) { 
     do { 
      list.add(cursor.getString(0).toUpperCase()); 
     } while (cursor.moveToNext()); 
    } 
    if (cursor != null && !cursor.isClosed()) { 
     cursor.close(); 
    } 
    return list; 
} 

public static class OpenHelper extends SQLiteOpenHelper { 

    OpenHelper(Context context) { 
     super(context, DATABASE_NAME, null, DATABASE_VERSION); 
    } 

    @Override 
    public void onCreate(SQLiteDatabase db) { 
     db.execSQL("CREATE TABLE " 
       + TABLE_NAME 
       + "(id INTEGER PRIMARY KEY AUTOINCREMENT, contact_id INTEGER, contact_name VARCHAR(50), number_type VARCHAR(50), contact_number VARCHAR(50), duration TIME, date DATE, current_time TIME, cont INTEGER)"); 

    } 

    @Override 
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 
     Log.w("RatedCalls Database", 
       "Upgrading database, this will drop tables and recreate."); 
     db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME); 
     onCreate(db); 
    } 
} 
    } 

_ ___ (Edited) __ _ ____

Esto es lo que obtengo ahora. Cuando realizo una nueva llamada, el Logcat lo devuelve.

12-16 18:55:27.365: ERROR/AndroidRuntime(767): FATAL EXCEPTION: main 
12-16 18:55:27.365: ERROR/AndroidRuntime(767): java.lang.IllegalStateException: database not open 
12-16 18:55:27.365: ERROR/AndroidRuntime(767):  at android.database.sqlite.SQLiteDatabase.queryWithFactory(SQLiteDatabase.java:1230) 
12-16 18:55:27.365: ERROR/AndroidRuntime(767):  at android.database.sqlite.SQLiteDatabase.query(SQLiteDatabase.java:1189) 
12-16 18:55:27.365: ERROR/AndroidRuntime(767):  at android.database.sqlite.SQLiteDatabase.query(SQLiteDatabase.java:1271) 
12-16 18:55:27.365: ERROR/AndroidRuntime(767):  at com.psyhclo.CallDataHelper.selectDate(CallDataHelper.java:61) 
12-16 18:55:27.365: ERROR/AndroidRuntime(767):  at com.psyhclo.RatedCalls.fillList(RatedCalls.java:98) 
12-16 18:55:27.365: ERROR/AndroidRuntime(767):  at com.psyhclo.RatedCalls.access$0(RatedCalls.java:96) 
12-16 18:55:27.365: ERROR/AndroidRuntime(767):  at com.psyhclo.RatedCalls$RatedCallsContentObserver.onChange(RatedCalls.java:91) 
12-16 18:55:27.365: ERROR/AndroidRuntime(767):  at android.database.ContentObserver$NotificationRunnable.run(ContentObserver.java:43) 
12-16 18:55:27.365: ERROR/AndroidRuntime(767):  at android.os.Handler.handleCallback(Handler.java:587) 
12-16 18:55:27.365: ERROR/AndroidRuntime(767):  at android.os.Handler.dispatchMessage(Handler.java:92) 
12-16 18:55:27.365: ERROR/AndroidRuntime(767):  at android.os.Looper.loop(Looper.java:123) 
12-16 18:55:27.365: ERROR/AndroidRuntime(767):  at android.app.ActivityThread.main(ActivityThread.java:3647) 
12-16 18:55:27.365: ERROR/AndroidRuntime(767):  at java.lang.reflect.Method.invokeNative(Native Method) 
12-16 18:55:27.365: ERROR/AndroidRuntime(767):  at java.lang.reflect.Method.invoke(Method.java:507) 
12-16 18:55:27.365: ERROR/AndroidRuntime(767):  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839) 
12-16 18:55:27.365: ERROR/AndroidRuntime(767):  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597) 
12-16 18:55:27.365: ERROR/AndroidRuntime(767):  at dalvik.system.NativeStart.main(Native Method) 

Y esta es mi actividad:

public class RatedCalls extends ListActivity { 

private static final String LOG_TAG = "RATEDCALLSOBSERVER"; 
private Handler handler = new Handler(); 
private SQLiteDatabase db; 
private CallDataHelper cdh; 
StringBuilder sb = new StringBuilder(); 
OpenHelper openHelper = new OpenHelper(RatedCalls.this); 

@Override 
public void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    registerContentObservers(); 
    Log.d("FILLLIST", "calling from onCreate()"); 
    cdh = new CallDataHelper(this); 
    db = openHelper.getWritableDatabase(); 

} 

@Override 
protected void onDestroy() { 
    super.onDestroy(); 
    if (openHelper != null) { 
     openHelper.close(); 
    } 
    if (cdh != null) { 
     cdh.close(); 
    } 
} 

@Override 
protected void onPause() { 
    super.onPause(); 
    if (openHelper != null) { 
     openHelper.close(); 
    } 
    if (cdh != null) { 
     cdh.close(); 
    } 
} 

@Override 
public void onResume() { 

    super.onResume(); 
    openOrCreateDatabase("calls.db", SQLiteDatabase.CREATE_IF_NECESSARY, 
      null); 

} 

class RatedCallsContentObserver extends ContentObserver { 
    public RatedCallsContentObserver(Handler h) { 
     super(h); 
    } 

    @Override 
    public boolean deliverSelfNotifications() { 
     return true; 

    } 

    @Override 
    public void onChange(boolean selfChange) { 
     Log.d(LOG_TAG, "RatedCallsContentObserver.onChange(" + selfChange 
       + ")"); 
     super.onChange(selfChange); 
     fillList(); 

    } 
} 

private void fillList() { 

    String lastDate = cdh.selectDate(); 

    Cursor cursor = getContentResolver().query(
      android.provider.CallLog.Calls.CONTENT_URI, null, 
      android.provider.CallLog.Calls.DATE + " < ?", 
      new String[] { lastDate }, 
      android.provider.CallLog.Calls.DATE + " DESC "); 

    Log.d("FILLLIST", "Calling from filllist"); 

    startManagingCursor(cursor); 
    int numberColumnId = cursor 
      .getColumnIndex(android.provider.CallLog.Calls.NUMBER); 
    int durationId = cursor 
      .getColumnIndex(android.provider.CallLog.Calls.DURATION); 
    int contactNameId = cursor 
      .getColumnIndex(android.provider.CallLog.Calls.CACHED_NAME); 
    int numTypeId = cursor 
      .getColumnIndex(android.provider.CallLog.Calls.CACHED_NUMBER_TYPE); 

    Date dt = new Date(); 
    int hours = dt.getHours(); 
    int minutes = dt.getMinutes(); 
    int seconds = dt.getSeconds(); 
    String currTime = hours + ":" + minutes + ":" + seconds; 

    SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy"); 
    Date date = new Date(); 

    ArrayList<String> callList = new ArrayList<String>(); 
    cursor.moveToFirst(); 

    String contactNumber = cursor.getString(numberColumnId); 
    String contactName = cursor.getString(contactNameId); 
    String duration = cursor.getString(durationId); 
    // String callDate = DateFormat.getDateInstance().format(dateId); 
    String numType = cursor.getString(numTypeId); 
    stopManagingCursor(cursor); 
    ContentValues values = new ContentValues(); 

    values.put("contact_id", 1); 
    values.put("contact_name", contactName); 
    values.put("number_type", numType); 
    values.put("contact_number", contactNumber); 
    values.put("duration", duration); 
    values.put("date", dateFormat.format(date)); 
    values.put("current_time", currTime); 
    values.put("cont", 1); 
    getBaseContext().getContentResolver().notifyChange(
      android.provider.CallLog.Calls.CONTENT_URI, 
      new RatedCallsContentObserver(handler)); 
    db.insert(CallDataHelper.TABLE_NAME, null, values); 
    Toast.makeText(getBaseContext(), "Inserted!", Toast.LENGTH_LONG); 
    callList.add("Contact Number: " + contactNumber + "\nContact Name: " 
      + contactName + "\nDuration: " + duration + "\nDate: " 
      + dateFormat.format(date)); 

    setListAdapter(new ArrayAdapter<String>(this, R.layout.listitem, 
      callList)); 
    ListView lv = getListView(); 
    lv.setTextFilterEnabled(true); 

    lv.setOnItemClickListener(new android.widget.AdapterView.OnItemClickListener() { 

     @Override 
     public void onItemClick(AdapterView<?> parent, View view, 
       int position, long id) { 

      Toast.makeText(getApplicationContext(), 
        ((TextView) view).getText(), Toast.LENGTH_SHORT).show(); 

     } 
    }); 
} 

public void registerContentObservers() { 

    this.getApplicationContext() 
      .getContentResolver() 
      .registerContentObserver(
        android.provider.CallLog.Calls.CONTENT_URI, true, 
        new RatedCallsContentObserver(handler)); 

} 

public void unregisterContentObservers() { 

    this.getApplicationContext() 
      .getContentResolver() 
      .unregisterContentObserver(
        new RatedCallsContentObserver(handler)); 

} 
} 

Respuesta

46

Creo que el problema es que se necesita para cerrar la base de datos cuando se destruye su actividad. Trate de añadir esto a su actividad:

@Override 
protected void onDestroy() { 
    super.onDestroy(); 
    if (openHelper != null) { 
     openHelper.close(); 
    } 
    if (cdh != null) { 
     cdh.close(); 
    } 
} 

y añadir esto a su clase CallDataHelper:

public void close() { 
    // NOTE: openHelper must now be a member of CallDataHelper; 
    // you currently have it as a local in your constructor 
    if (openHelper != null) { 
     openHelper.close(); 
    } 
} 

EDIT 2: también cambiar el constructor CallDataHelper a:

// Declare openHelper as a member variable 
OpenHelper openHelper = null; 

public CallDataHelper(Context context) { 
    this.context = context; 
    openHelper = new OpenHelper(this.context); 
    this.db = openHelper.getWritableDatabase(); 
} 

Esto debería cierre con éxito la conexión de la base de datos creada por sus dos instancias OpenHelper.

(editado para reflejar el hecho de que utiliza dos instancias OpenHelper.)

+0

lo hice, y después de un tiempo la aplicación se ejecuta, el error es: 12-16 18: 09: 47.406: ERROR/Base de datos (21184): android.database.sqlite.DatabaseObjectNotClosedException: La aplicación no se cerró el cursor o el objeto de la base de datos que se abrió aquí Creo que es el cursor el que debe cerrarse. ¿Cómo puedo cerrarlo? – rogcg

+0

@psyhclo: ¿Has visto mi edición? Tiene dos instancias de OpenHelper, cada una de las cuales se usa para obtener una conexión de base de datos, por lo que ambas deben estar cerradas. (Es posible que desee combinar esos dos OpenHelpers; esa es su llamada). –

+0

El método close() debe estar dentro de qué clase, OpenHelper o CallDataHelper. El campo openHelper está en CallDataHelper, pero cuando trato de declarar el método close, dice esto: openHelper no se puede resolver en una variable. Y ofrece crear el campo. Obs: Estoy declarando el método close() en CallDataHelper. Y si trato de hacer esto: public void close() { \t \t // NOTA: openHelper ahora debe ser miembro de CallDataHelper; \t // actualmente lo tiene como local en su constructor \t if (this.db! = Null) { \t \t this.db.close(); \t} – rogcg

1

Sospecho fillList() se llama varias veces durante una "sesión" RatedCalls. Por lo tanto, cada vez que cree una nueva instancia Cursor y llame al startManagingCursor() en ella. Al hacerlo, se acumula una lista de Cursores - ArrayList<ManagedCursor> para ser precisos, que se declara en ListActivity. Normalmente, (los cursores que se están administrando) se cerrarán automáticamente en ListActivity.onDestroy(). ¿Podría confirmar que su RatedCalls realmente pasa onDestroy().

Anula de alguna manera el ListActivity.onDestroy() en RatedCalls. En caso afirmativo, ¿llama al super.onDestroy() (debe llamarlo)?

No consigo el propósito de llamar al onStop() en RatedCallsContentObserver.onChange() después del fillList();. Creo que el sistema operativo OS debe llamar a las devoluciones de ciclo de vida de la actividad solo. Entonces, tal vez llamar manualmente una devolución de llamada de ciclo de vida impide de alguna manera que onDestroy() sea llamado por SO (solo una suposición).

ACTUALIZACIÓN:

Dan Breslau es correcto - es db objeto que no está cerrada. Lo siento por ser engañoso.

+0

Los rastreos de la pila muestran que el objeto no cerrado es la conexión de la base de datos en sí misma, que se devuelve mediante 'SQLiteOpenHelper.getWritableDatabase'. Aunque 'startManagingCursor' asegura que el cursor se eliminará de manera adecuada, no estoy al tanto de nada que cierre automágicamente las conexiones de la base de datos cuando se destruye la actividad. (Mi falta de conciencia no es definitiva, sin embargo :-) –

+0

@Dan Breslau: ¡Dios mío, tienes razón! es 'call.db' que no está cerrado! La primera vez que abrí la pregunta, el rastro de la pila no estaba muy bien formateado, así que probablemente me lo perdí. –

+0

@Arhimed su respuesta es totalmente correcta. Sabía que eso estaba sucediendo, simplemente no sabía cómo resolver eso. De hecho, la pregunta original es esta http://stackoverflow.com/questions/4450778/android-contentobserver-never-stoped, y esta pregunta de la que ahora venimos proviene de esa respuesta para esa pregunta. ¿Qué debo hacer para detener que fillList() se invoque varias veces durante una "sesión" de RatedCalls? Hombre, ¡has abierto mi mente! Gracias. – rogcg

2

Literalmente la base de datos no está normalmente cerrada, la actual porque la instanciación repetida de su base de datos, o la conexión que se ha configurado, y usted y tratar de abrir otra conexión no habrá ninguna excepción. Entonces la solución es, asegúrese de abrir solo una conexión.

Cuestiones relacionadas