CursorWrapperInner - Unable to close database


Last Updated:

  1. SFLeBrun

    SFLeBrun Member This Topic's Starter

    Joined:
    Feb 11, 2010
    Messages:
    9
    Likes Received:
    0
    My application has a ContentProvider that handles the direct SQLiteDatabase access. The activities that query the ContentProvider are returned a Cursor. Even though the activities close the cursor, the application is throwing an IllegalStateException when the ContentProvider exits (or possibly when garbage collection is done) because the activities are not closing the SQLiteDatabase.

    The Activity has no direct way to close database. The Cursor returned is an android.content.ContentResolver$CursorWrapperInner type. This type encapsulates the actual SQLiteCursor returned from the ContentProvider.

    If the returned Cursor could be cast into its original SQLiteCursor, the SQLiteDatabase used by the Cursor would be accessible and could be closed by the Activity. Unfortunately, the CursorWrapperInner cannot be cast.

    This looks like it should be a common problem but I cannot find any references to this issue in any of the forums that I have looked at or by googling. Any help resolving this issue will be appreciated.

    Sequence of Events:

    1) Activity uses ContentResolver to run a query through a ContentProvider.

    2) Content Provider receives the query request through a call to its query() method.

    3) Content Provider opens a SQLiteDatabase, performs the query and obtains a SQLiteCursor.

    4) Content Provider exits the query() method, returning the SQLiteCursor.

    5) Activity receives a CursorWrapperInner object from the ContentResolver.query() call.

    6) Activity uses the cursor and invokes the Cursor.close() method.At some later time, either the ContentProvider is deleted or garbage collection occurs. (I am not sure which is the trigger to the Exception)

    7) An IllegalStateException is thrown because a SQLiteDatabase remains open and is a leak.

    * Closing the SQLiteDatabase in the ContentProvider invalidates the Cursor before the Activity has a chance to use.

    * Invoking close() on the Cursor, which is suppose to release all resources held by the Cursor, is not closing the SQLiteDatabase.

    * The CursorWrapperInner class prevents the Activity from direct access to the SQLiteCursor which could be used to close the database.


    What am I missing?

    The following is a snippet from the LogCat:
    Code (Text):
    1. D/dalvikvm(  722): GC freed 3058 objects / 180664 bytes in 143ms
    2. E/Database(  722): Leak found
    3. E/Database(  722): java.lang.IllegalStateException: /data/data/com.lebruns.android.BookManager/databases/BMMasterCatalog.db SQLiteDatabase created and never closed
    4. E/Database(  722):     at android.database.sqlite.SQLiteDatabase.<init>(SQLiteDatabase.java:1580)
    5. E/Database(  722):     at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:638)
    6. E/Database(  722):     at android.database.sqlite.SQLiteDatabase.openOrCreateDatabase(SQLiteDatabase.java:659)
    7. E/Database(  722):     at android.database.sqlite.SQLiteDatabase.openOrCreateDatabase(SQLiteDatabase.java:652)
    8. E/Database(  722):     at android.app.ApplicationContext.openOrCreateDatabase(ApplicationContext.java:463)
    9. E/Database(  722):     at android.content.ContextWrapper.openOrCreateDatabase(ContextWrapper.java:181)
    10. E/Database(  722):     at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:98)
    11. E/Database(  722):     at android.database.sqlite.SQLiteOpenHelper.getReadableDatabase(SQLiteOpenHelper.java:158)
    12. E/Database(  722):     at com.lebruns.android.BookManager.BookCaseProvider.QueryMasterCatalog(BookCaseProvider.java:714)
    13. E/Database(  722):     at com.lebruns.android.BookManager.BookCaseProvider.query(BookCaseProvider.java:273)
    14. E/Database(  722):     at android.content.ContentProvider$Transport.query(ContentProvider.java:129)
    15. E/Database(  722):     at android.content.ContentResolver.query(ContentResolver.java:149)
    16. E/Database(  722):     at com.lebruns.android.BookManager.Catalog.Refresh(Catalog.java:124)
    17. E/Database(  722):     at com.lebruns.android.BookManager.MainActivity.onStart(MainActivity.java:52)
    18. E/Database(  722):     at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1205)
    19. E/Database(  722):     at android.app.Activity.performStart(Activity.java:3490)
    20. E/Database(  722):     at android.app.Activity.performRestart(Activity.java:3518)
    21. E/Database(  722):     at android.app.Activity.performResume(Activity.java:3523)
    22. E/Database(  722):     at android.app.ActivityThread.performResumeActivity(ActivityThread.java:2619)
    23. E/Database(  722):     at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:2647)
    24. E/Database(  722):     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1717)
    25. E/Database(  722):     at android.os.Handler.dispatchMessage(Handler.java:99)
    26. E/Database(  722):     at android.os.Looper.loop(Looper.java:123)
    27. E/Database(  722):     at android.app.ActivityThread.main(ActivityThread.java:3948)
    28. E/Database(  722):     at java.lang.reflect.Method.invokeNative(Native Method)
    29. E/Database(  722):     at java.lang.reflect.Method.invoke(Method.java:521)
    30. E/Database(  722):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:782)
    31. E/Database(  722):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:540)
    32. E/Database(  722):     at dalvik.system.NativeStart.main(Native Method)
    33.  
     

    Advertisement
  2. SFLeBrun

    SFLeBrun Member This Topic's Starter

    Joined:
    Feb 11, 2010
    Messages:
    9
    Likes Received:
    0
    While I did not find the answer to my original question, I did create a work around that appears to solve my Cursor/Database leak issue. The problem that I am seeing appears to be a general one but there is almost nothing written anywhere that I have been able to find.

    The problem:
    ContentProvider leak Cursor.

    The result is that you need to leave the database open when the ContentProvider returns a Cursor object during a query() call. When garbage collection occurs, it finds a database that has not been closed and throws an InvalidStateException. When the Activity invokes the close() method on the cursor it is provided, the database does not get closed. Closing the database in the ContentProvider results in the cursor containing no data. The SQLiteCursor could close the databse through its SQLiteCursor.getDatabase() call but the cursor returned from the ContentProvider is wrapped in a class that does not provide access to the actual cursor object.

    There are two solutions that I have discovered. The first is to make sure that the ContentProvider hangs on to every database that it opens for a query() call. If your ContentProvider only deals with a single database, this is an easy solution to implement using a private data member in your ContentProvider.

    My ContentProvider deals with multiple databases, most of which have the same schema but contain different data. My first attempt was to create a container object and placed each database from a query() into it and then closing the database in the finalize(). This works but is not a great solution since each query() call can result in another database object being created. So I opted for a different solution. Use a Cursor that closes the database when the Cursor itself is closed.

    Basically, I extended the SQLiteCursor class, overriding the close() method and adding a closeForReuse() method that allows the cursor to be closed without closing the attached database. The Activity that invokes the ContentProvider query method is responsible for insuring the cursor object it receives gets closed.

    Only one book out of about half a dozen even mentioned that there was a problem here. That book is "Unlocking Android" from Manning.

    The following code is a sample of my solution, with only the relative parts being present.

    Code (Text):
    1.  
    2. public class LeaklessProvider extends android.content.ContentProvider
    3. {
    4.  
    5.     // Used for debugging, to insure that every cursor created
    6.     // is closed.  Tracked through LogCat.
    7.     static private int CursorID = 0;
    8.  
    9.  
    10.     //=================================================
    11.      // Nested class that extends SQLiteOpenHelper used for
    12.     // opening and creating databases.
    13.     public class LeaklessDatabase extends SQLiteOpenHelper
    14.     {
    15.         public LeaklessDatabase (Context context,
    16.                                  String  databaseName,
    17.                                  String  databaseFileName,
    18.                                  int     dbVersion)
    19.         {
    20.             super(context,
    21.                   databaseFileName,
    22.                   new LeaklessCursorFactory(),
    23.                   dbVersion);
    24.         }
    25.  
    26.         // Fill in rest of class...
    27.     }   // end of LeaklessDatabase class
    28.  
    29.  
    30.     //=================================================
    31.     // Nested Class that are LeaklessCursor for queries
    32.     public class LeaklessCursor extends SQLiteCursor
    33.     {
    34.         static final String LogTag =
    35.             "BookManager.LeaklessProvider.LeaklessCursor";
    36.  
    37.         final  SQLiteDatabase mDatabase;
    38.         final  int            mID;
    39.  
    40.  
    41.         // CTor - same signature as the SQLiteCursor with an extra parameter
    42.         //        that is used for debugging/tracking
    43.         public LeaklessCursor(SQLiteDatabase      database,
    44.                               SQLiteCursorDriver  driver,
    45.                               String              table,
    46.                               SQLiteQuery         query,
    47.                               int                 cursorID)
    48.         {
    49.             super(database, driver, table, query);
    50.  
    51.             mDatabase = database;
    52.             mID       = cursorID;
    53.         }
    54.  
    55.         /**
    56.          * Closes the database used to generate the cursor when the
    57.          * cursor is closed.  Hopefully, plugging the GC Leak detected
    58.          * when using pure SQLiteCursor that are wrapped when returned
    59.          * to an Activity and therefore unreachable.
    60.          */
    61.         @Override
    62.         public void close()
    63.         {
    64.             Log.d(".close()", "Closing LeaklessCursor #" + mID
    65.                      + " and database. " + mDatabase.getPath());
    66.             super.close();
    67.             if ( mDatabase != null )
    68.             {
    69.                 mDatabase.close();
    70.             }
    71.         }
    72.  
    73.         /**
    74.          * Closes cursor without closing database.
    75.          */
    76.         public void closeForReuse()
    77.         {
    78.             Log.d(".close()", "Closing LeaklessCursor #" + mID
    79.                      + " but not database. " + mDatabase.getPath());
    80.             super.close();
    81.         }
    82.  
    83.         /**
    84.          * Override toString() to add the ID value to the output.
    85.          */
    86.         @Override
    87.         public String toString()
    88.         {
    89.             return super.toString() + ", ID# " + mID;
    90.         }
    91.  
    92.     }   // end of LeaklessCursor class
    93.  
    94.     //=================================================
    95.     // Nested Class to create the LeaklessCursor for queries
    96.  
    97.     class LeaklessCursorFactory implements SQLiteDatabase.CursorFactory
    98.     {
    99.         /**
    100.          * Creates and returns a new Cursor of LeaklessCursor type.
    101.          */
    102.         public Cursor newCursor ( SQLiteDatabase      database,
    103.                                   SQLiteCursorDriver  driver,
    104.                                   String              editTable,
    105.                                   SQLiteQuery         query )
    106.         {
    107.             int  cursorID = LeaklessProvider.CursorID++;
    108.             Log.d(".LeaklessCursorFactory.newCursor()",
    109.                     "Creating new Cursor.  ID: " + cursorID
    110.                     + ", Database: " + database.getPath());
    111.  
    112.             return new LeaklessCursor(database,
    113.                                       driver,
    114.                                       editTable,
    115.                                       query,
    116.                                       cursorID);
    117.         }
    118.  
    119.     }   // end of LeaklessCursorFactory class
    120.  
    121. }  // end of class LeaklessProvider
    122.  
    123.  
     
  3. o_r

    o_r New Member

    Joined:
    Jan 13, 2010
    Messages:
    4
    Likes Received:
    0
    hi, thanks for the good post
    i am new to java
    how do i implement the needed overridden functions in the contentprovider class that you created for example:

    @Override
    public boolean onCreate() {
    // TODO Auto-generated method stub
    return false;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
    String[] selectionArgs, String sortOrder) {
    // TODO Auto-generated method stub
    return null;
    }


    thanks in advance
     
  4. kojacked

    kojacked New Member

    Joined:
    Jan 15, 2011
    Messages:
    1
    Likes Received:
    0
    SFLeBrun:

    Thank you for saving my ass. I inherited an android app whose DAL is built like your original -- leaky cursors and all. I'm implementing your solution as we speak.
     

Share This Page

Loading...