单例方法的SQLite内存问题

时间:2017-09-21 15:07:35

标签: java android sqlite android-sqlite

我有一个SQLite数据库支持Android应用中的所有内容。

我有一个扩展SQLiteAssetHelper的DatabaseHelper类。

我的数据库实例太多,然后遇到SQLiteCantOpenDatabaseException。

为了解决这个问题,我改变了我的类以维护DatabaseHelper对象的单个实例。

我有以下内容:

private static DatabaseHelper databaseHelper;

public static synchronized DatabaseHelper getInstance(Context context, boolean singleRow, boolean showLoader){
    if(databaseHelper == null) {
        databaseHelper = new DatabaseHelper(context, singleRow, showLoader);
    }
    return databaseHelper;
}

public DatabaseHelper(Context context, boolean singleRow, boolean showLoader){
    super(context, (new File(DatabaseManager.getDatabasePath(context))).getName(), (new File(DatabaseManager.getDatabasePath(context))).getParentFile().getAbsolutePath(), null, DATABASE_VERSION);

    this.context = context;
    this.singleRow = singleRow;
    this.showLoader = showLoader;

}

然后我按如下方式调用getInstance静态方法:

DatabaseHelper databaseHelper = DatabaseHelper.getInstance(activity.getApplicationContext(), false, false);

在一定数量的数据库活动之后,应用程序仍然因内存原因而崩溃。

然后我收到此错误:

Error Code : 2062 (SQLITE_CANTOPEN_EMFILE)

Caused By : Application has opened two many files. Maximum of available file descriptors in one process is 1024 in default.

(unable to open database file (code 2062))

采取单例方法后,我有点迷失了为什么这仍然导致内存泄漏。

任何帮助都将不胜感激。

3 个答案:

答案 0 :(得分:2)

我真的不知道为什么并且真的很好奇“应用程序已经打开了两个文件”错误,并且想知道是什么导致它。

但是,我使用单例数据库,一年内没有任何问题。我使用这个片段在14个应用程序中使用singleton获取数据库,从未遇到任何问题。

public class DatabaseManager {

    private AtomicInteger mOpenCounter = new AtomicInteger();

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

    private DatabaseManager() {

    }

    public static synchronized DatabaseManager getDatabaseManager(SQLiteOpenHelper helper) {
        if (instance == null) {
            instance = new DatabaseManager();
            mDatabaseHelper = helper;
        }
        return instance;
    }

    public static synchronized DatabaseManager getDatabaseManager(Context context) {
        if (instance == null) {
            instance = new DatabaseManager();
            mDatabaseHelper = new DatabaseOpenHelper(context.getApplicationContext());
        }
        return instance;
    }

    /**
     * Get a writable database
     */
    public synchronized 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;
    }

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

onCreate()我得到mDatabaseManager = DatabaseManager.getDatabaseManager(getActivity().getApplicationContext());的实例,onStart()我用mDatabaseManager.openDatabase();打开数据库,onStop()用mDatabaseManager.closeDatabase();

关闭它

答案 1 :(得分:2)

如果您收到一条消息,指出打开的文件太多,原因可能是有太多的Cursor仍处于打开状态。

但是,返回的消息可能并不总是相同,并且可能特定于被调用的任务/调用。

在这种情况下,消息是(unable to open database file (code 2062)),但在另一种情况下(来自SELECT的消息是unable to open database file (code 14))。 SQLite unable to open database file (code 14) on frequent “SELECT” query

上面的链接也指向我发表的一篇文章,它清楚地表明创建一个Cursor会导致打开一个或多个文件。

示例循环了大约500行,每行为每行创建/重新创建3个游标(即使只使用4个游标对象,也可能有1500多个游标)。

最初它只关闭了最后3个游标(所有父元素的最后一行),导致unable to open database File (code 14)。关闭每次迭代的3个游标解决了这个问题。

失败的代码是: -

        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();
}

虽然固定代码是: -

        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();
    }

我现在倾向于遵循以下规则/惯例: -

  • 如果只是获得结果,例如获取行数,关闭方法中的Cursor。

  • 如果使用光标进行显示,例如一个ListView,然后在活动的onDestroy方法中关闭光标。

  • 如果将光标用于我称之为更复杂的处理,例如删除带有基础引用的行,然后在处理循环中完成后立即关闭游标。

答案 2 :(得分:1)

我也采用单身方法。显然有两种方法可以访问您的数据。

您可以使用光标填充对象列表,然后关闭光标,然后关闭数据库。

除非您因为列表较大而返回光标以进行动态内容分页。

是否关闭您的连接取决于使用频率和您应用的具体需求。

但是,如果您从新上下文访问并共享以前创建的SQLHelper类,则可能会因为构造函数需要上下文而创建内存泄漏问题。

听起来我觉得你在这个单一的连接上打开的文件太多了。您是否考虑在每次交互后关闭数据库连接。例如:

public static ArrayList<OrderModel> getOrders(Context context){
    ArrayList<OrderModel> orderList = new ArrayList<OrderModel>();
    SQLiteDatabase db = null;

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

        Cursor cursor = db.query(OrdersTable.TABLE_NAME, columns, OrdersTable.COLUMN_PRIMARY_ID, null, null, null, null);
        if (cursor != null) {
            for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()){
                OrderModel order = new OrderModel();
                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;

}

然后我的单例类具有open和close,如果上下文已经改变,我在打开之前新建一个帮助器实例。

然后我每次都使用CloseUtil进行try / catch关闭。 如果您返回Cursor对象而不是ArrayList,这可能仍然是相同的,因为您可能获得处理分页的动态数据或要填充列表的大数据。

但听起来我觉得你的联系已经过度了,所以你可能需要重温你的模型。