【问题标题】:SQLite memory issue with singleton approach单例方法的 SQLite 内存问题
【发布时间】:2017-09-21 15:07:34
【问题描述】:

我有一个 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))

采用单例方法后,我有点不明白为什么这仍然会导致内存泄漏。

任何帮助将不胜感激。

【问题讨论】:

  • 也许检查一下您是否有未关闭的游标。我确实记得有一个类似的问题,但我不记得它表明 1024 太多了。我会试着找到我发的关于这个的帖子。
  • SQLite unable to open database file (code 14) on frequent “SELECT” query 是帖子。消息不同,但我认为很明显,光标会导致文件被打开。
  • 我查看了这个游标问题,发现我有很多打开的游标。还没有全部关闭,看来问题已经解决了。不过,我还没有进行广泛的测试,所以我不能 100% 确定。它肯定让我越过了我以前的障碍点。我可以请您根据光标输入一个信息丰富的答案,以便将其留给其他人查看吗?对于我来说,这个特殊错误是正确的答案!

标签: java android sqlite android-sqlite


【解决方案1】:

如果您收到指示打开的文件过多的消息,原因很可能是有太多的光标仍处于打开状态。

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

在这种情况下,消息是(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.

上面的链接还指向我发表的一篇文章,该文章非常清楚地表明创建光标会导致打开一个(或多个文件)。

该示例循环了大约 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,然后在 Activity 的 onDestroy 方法中关闭光标。

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

【讨论】:

    【解决方案2】:

    我真的不知道为什么,真的很好奇“应用程序打开了两个很多文件”错误,想知道是什么原因造成的。

    但是,我使用单例和数据库,一年多没有任何问题。我用这个 sn-p 在 14 个应用程序中获取带有单例的数据库,从未遇到任何问题。

    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(); 关闭它

    【讨论】:

    • 和我的有点不一样,你有怎么使用的代码吗?
    • @sark9012 我也添加了使用方法。
    【解决方案3】:

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

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

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

    是否关闭连接取决于使用频率和应用的特定需求。

    但是,如果您从新上下文访问并共享先前创建的 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;
    
    }
    

    然后我的单例类有打开和关闭,如果上下文发生变化,我会在打开之前新建一个新的助手实例。

    然后我每次都使用 CloseUtil 进行尝试/捕获关闭。 如果您返回的是 Cursor Object 而不是 ArrayList,这仍然是相同的,因为您可能正在获取处理分页的动态数据或太大而无法填充列表。

    但在我看来,您的连接已经过度工作,因此您可能需要重新审视您的模型。

    【讨论】:

      猜你喜欢
      • 2011-12-07
      • 1970-01-01
      • 1970-01-01
      • 2010-09-16
      • 1970-01-01
      • 1970-01-01
      • 2013-10-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多