Skip to content Skip to sidebar Skip to footer

Sqlite Memory Issue With Singleton Approach

I have a SQLite database powering all the content in an Android app. I have a DatabaseHelper class that extends SQLiteAssetHelper. I have been having problems with too many instanc

Solution 1:

If you are getting a message indicating too many files open, a cause may well be that there are too many Cursor that are still open.

However, the message returned may not always be the same and is probably specific to the task/call being called.

In this case the message was (unable to open database file (code 2062)), yet in another case (from a SELECT the message was unable to open database file (code 14)). SQLite unable to open database file (code 14) on frequent “SELECT” query.

The above link also points to a post I made what quite clearly shows that creating a Cursor results in a file (or files) being opened.

The example was looping through about 500 rows and for each row it was creating/recreating 3 cursors for each row (so potentially 1500+ cursors even though only using 4 cursor objects).

Initially it was only closing the 3 cursors at the end (last row of the parent of all) resulting in the unable to open database File (code 14). Closing the 3 cursors for each iteration resolved the issue.

The code that failed was :-

        SQLiteDatabase db = getWritableDatabase();
        Cursor shoplistcursor = getAllRowsFromTable(SHOPLIST_TABLE_NAME);
        Cursor productcsr;
        Cursor aislecsr;
        Cursor prdusecsr;
        while(shoplistcursor.moveToNext()) {
            productcsr = getProductFromProductId(shoplistcursor.getLong(shoplistcursor.getColumnIndex(SHOPLIST_COLUMN_PRODUCTREF)));
            aislecsr = getAisleFromAisleId(shoplistcursor.getLong(shoplistcursor.getColumnIndex(SHOPLIST_COLUMN_AISLEREF)));
            prdusecsr = getProductUsage(shoplistcursor.getLong(shoplistcursor.getColumnIndex(SHOPLIST_COLUMN_AISLEREF)),
                    shoplistcursor.getLong(shoplistcursor.getColumnIndex(SHOPLIST_COLUMN_PRODUCTREF)));
            if (productcsr.getCount() < 1 | aislecsr.getCount() < 1 | prdusecsr.getCount() < 1) {
                deleteShopListEntry(shoplistcursor.getLong(shoplistcursor.getColumnIndex(SHOPLIST_COLUMN_ID)));
            } 
            if(shoplistcursor.isLast()) {
                prdusecsr.close();
                aislecsr.close();
                productcsr.close();
            }
        }
        shoplistcursor.close();
        db.close();
}

Whilst the fixed code was :-

        SQLiteDatabase db = getWritableDatabase();
        Cursor shoplistcursor = getAllRowsFromTable(SHOPLIST_TABLE_NAME);
        Cursor productcsr;
        Cursor aislecsr;
        Cursor prdusecsr;
        while(shoplistcursor.moveToNext()) {
            productcsr = getProductFromProductId(shoplistcursor.getLong(shoplistcursor.getColumnIndex(SHOPLIST_COLUMN_PRODUCTREF)));
            aislecsr = getAisleFromAisleId(shoplistcursor.getLong(shoplistcursor.getColumnIndex(SHOPLIST_COLUMN_AISLEREF)));
            prdusecsr = getProductUsage(shoplistcursor.getLong(shoplistcursor.getColumnIndex(SHOPLIST_COLUMN_AISLEREF)),
                    shoplistcursor.getLong(shoplistcursor.getColumnIndex(SHOPLIST_COLUMN_PRODUCTREF)));
            if (productcsr.getCount() < 1 | aislecsr.getCount() < 1 | prdusecsr.getCount() < 1) {
                productcsr.close();
                aislecsr.close();
                prdusecsr.close();
                deleteShopListEntry(shoplistcursor.getLong(shoplistcursor.getColumnIndex(SHOPLIST_COLUMN_ID)));
            } else {
                productcsr.close();
                aislecsr.close();
                prdusecsr.close();
            }
        }
        shoplistcursor.close();
        db.close();
    }

I tend to now follow the following rule/practice :-

  • If just getting the result e.g. getting the number of rows, close the Cursor in the method.

  • If using the Cursor for a display e.g. a ListView, then close the cursor in the activity's onDestroy method.

  • If using the Cursor for what I'll call more complex processing e.g. deleting rows with underlying references then close the cursors as soon as they are done with, within the processing loop(s).

Solution 2:

I really don't know why and really got curious about "Application has opened two many files" error and would like to know what causes it.

However, i use singleton with database without any issues over a year. I used this snippet to get database with singleton in 14 apps and never had any problems.

publicclassDatabaseManager {

    privateAtomicIntegermOpenCounter=newAtomicInteger();

    privatestatic DatabaseManager instance;
    privatestatic SQLiteOpenHelper mDatabaseHelper;
    private SQLiteDatabase mDatabase;

    privateDatabaseManager() {

    }

    publicstaticsynchronized DatabaseManager getDatabaseManager(SQLiteOpenHelper helper) {
        if (instance == null) {
            instance = newDatabaseManager();
            mDatabaseHelper = helper;
        }
        return instance;
    }

    publicstaticsynchronized DatabaseManager getDatabaseManager(Context context) {
        if (instance == null) {
            instance = newDatabaseManager();
            mDatabaseHelper = newDatabaseOpenHelper(context.getApplicationContext());
        }
        return instance;
    }

    /**
     * Get a writable database
     */publicsynchronized SQLiteDatabase openDatabase() {
        if (mOpenCounter.incrementAndGet() == 1) {
            // Opening new database
            mDatabase = mDatabaseHelper.getWritableDatabase();
            // System.out.println("DataBaseManager: Database Opened");
        } else {
            // System.out.println("DataBaseManager: Database Already Open");
        }
        return mDatabase;
    }

    publicsynchronizedvoidcloseDatabase() {
        if (mOpenCounter.decrementAndGet() == 0) {
            // Closing database
            mDatabase.close();
            // System.out.println("DataBaseManager: Database Closed");
        } else {
            // System.out.println("DataBaseManager: Database is NOT Closed");
        }
    }
}

onCreate() i get instance with mDatabaseManager = DatabaseManager.getDatabaseManager(getActivity().getApplicationContext());, onStart() i open database with mDatabaseManager.openDatabase();, and onStop() close it with mDatabaseManager.closeDatabase();

Solution 3:

I do the singleton approach as well. There are two ways to access your Data obviously.

You can use a Cursor to populate a list of objects and then close your cursor and then close your DB.

Unless you are returning the cursor for dynamic paging of content because of larger list.

Whether or not to close your connection is contingent upon frequency of usage and your app's specific needs.

However, if you are accessing from new context and sharing the previously created SQLHelper class you could be creating memory leak issues as the constructor requires the context.

It sounds to me like you have too many files open on that single connection though. Have you considered closing your db connection after each interaction. Example:

publicstatic ArrayList<OrderModel> getOrders(Context context){
    ArrayList<OrderModel> orderList = newArrayList<OrderModel>();
    SQLiteDatabasedb=null;

    try{
        db = A35DBHelper.openDatabase(context);
        String columns[] = {
                "*"
        };

        Cursorcursor= db.query(OrdersTable.TABLE_NAME, columns, OrdersTable.COLUMN_PRIMARY_ID, null, null, null, null);
        if (cursor != null) {
            for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()){
                OrderModelorder=newOrderModel();
                order.setLocalDatabaseId(cursor.getLong(cursor.getColumnIndex(OrdersTable.COLUMN_PRIMARY_ID)));
                order.setID(cursor.getString(cursor.getColumnIndex(OrdersTable.COLUMN_REPAIR_ORDER_NUMBER)));
                order.setOrderNumber(cursor.getString(cursor.getColumnIndex(OrdersTable.COLUMN_ORDER_NUMBER)));
                order.setCreatedAtDate(cursor.getString(cursor.getColumnIndex(OrdersTable.COLUMN_CREATED_AT_DATE)));
                order.setImageCount(MediaDataContext.getAllMediaForOrderId(context, order.getID()).size());
                order.setDefaultThumbnailUrl(cursor.getString(cursor.getColumnIndex(OrdersTable.COLUMN_DEFAULT_THUMBNAIL_URL)));
                orderList.add(order);

            }

            cursor.close();

        }

    } catch (Exception ex) {
        A35Log.e(TAG, "Failed to get orders: " + ex.getMessage());

    }

    A35DBHelper.closeDatabase(db);
    return orderList;

}

Then my singleton class has the open and close where if the context has changed I new up a new instance of the helper before opening.

Then I use the CloseUtil for try/catch closing each time. This can still be the same if you are returning the Cursor Object instead of an ArrayList as you are possibly getting dynamic data that handles paging or is to big to fill a list.

But sounds like to me your connection is getting over worked so you may need to revisit your model.

Post a Comment for "Sqlite Memory Issue With Singleton Approach"