我有一个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))
采取单例方法后,我有点迷失了为什么这仍然导致内存泄漏。
任何帮助都将不胜感激。
答案 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,这可能仍然是相同的,因为您可能获得处理分页的动态数据或要填充列表的大数据。
但听起来我觉得你的联系已经过度了,所以你可能需要重温你的模型。