我在自己的db框架中遇到了这个问题。我在StackOverflow中搜索过这个问题并尝试了很多方法,但是这个问题还没有解决。它在单线程中运行良好,当我尝试更新列表项时,问题通常是App Widget中的问题。
以下是解释:
1.这是用于获取SQLiteDatabase对象的SQLiteOpenHelper,因为你看到它的单例:
public class PalmDB extends SQLiteOpenHelper {
public static PalmDB getInstance(final Context context){
if (sInstance == null){
synchronized (PalmDB.class) {
if (sInstance == null) {
sInstance = new PalmDB(context.getApplicationContext());
}
}
}
return sInstance;
}
}
2.Next是用于查询数据并将数据保存到数据库的商店。这是抽象的:
public abstract class BaseStore<T extends Model> {
private PalmDB mPalmDatabase = null;
@SuppressWarnings("unchecked")
public BaseStore(Context context) {
this.mPalmDatabase = PalmDB.getInstance(context);
}
protected SQLiteDatabase getWritableDatabase() {
return mPalmDatabase.getWritableDatabase();
}
protected synchronized void closeCursor(Cursor cursor) {
if (cursor == null || cursor.isClosed()) return;
try {
cursor.close();
} catch (Exception e){
LogUtils.d("Couldn't close cursor correctly");
}
}
public synchronized List<T> get(String whereSQL, String orderSQL, Status status, boolean exclude) {
Cursor cursor = null;
List<T> models = null;
SQLiteDatabase database = getWritableDatabase();
try {
cursor = database.rawQuery(" SELECT * FROM " + tableName
+ " WHERE " + BaseSchema.USER_ID + " = " + userId
+ (TextUtils.isEmpty(whereSQL) ? "" : " AND " + whereSQL)
+ (status == null ? "" : " AND " + BaseSchema.STATUS + (exclude ? " != " : " = ") + status.id)
+ (TextUtils.isEmpty(orderSQL) ? "" : " ORDER BY " + orderSQL),
new String[]{});
models = getList(cursor);
} finally {
closeCursor(cursor);
}
return models;
}
protected synchronized List<T> getList(Cursor cursor){
LogUtils.d(this); // print the hash code of this object
LogUtils.d(Thread.currentThread());
List<T> models = new LinkedList<>();
if (cursor != null && !cursor.isClosed() && cursor.moveToFirst()){ // exception here
do {
models.add(getModel(cursor));
} while (cursor.moveToNext());
} else if (cursor != null && cursor.isClosed()) {
LogUtils.e("cursor is closed : " + cursor);
}
return models;
}
private T getModel(Cursor cursor) {
T model = StoreHelper.getBaseModel(cursor, entityClass);
fillModel(model, cursor);
return model;
}
3.然后我为这样的具体对象覆盖此类。你可以看到它也是单身人士:
public class MindSnaggingStore extends BaseStore<MindSnagging> {
private static MindSnaggingStore sInstance = null;
public static MindSnaggingStore getInstance(Context context){
if (sInstance == null){
synchronized (MindSnaggingStore.class) {
if (sInstance == null) {
sInstance = new MindSnaggingStore(context.getApplicationContext());
}
}
}
return sInstance;
}
private MindSnaggingStore(Context context) {
super(context);
}
}
它在活动和片段中运行良好,但是当我将它包含在App Widget中时。当我尝试删除任何实体时,通常是两次,然后它崩溃了。
以下是例外:
02-05 20:01:58.497 19317-19330/? E/AndroidRuntime: FATAL EXCEPTION: Binder:19317_1
Process: me.shouheng.notepal, PID: 19317
java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed.
at android.database.sqlite.SQLiteConnectionPool.throwIfClosedLocked(SQLiteConnectionPool.java:962)
at android.database.sqlite.SQLiteConnectionPool.waitForConnection(SQLiteConnectionPool.java:599)
at android.database.sqlite.SQLiteConnectionPool.acquireConnection(SQLiteConnectionPool.java:348)
at android.database.sqlite.SQLiteSession.acquireConnection(SQLiteSession.java:894)
at android.database.sqlite.SQLiteSession.executeForCursorWindow(SQLiteSession.java:834)
at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:62)
at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:143)
at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:132)
at android.database.AbstractCursor.moveToPosition(AbstractCursor.java:219)
at android.database.AbstractCursor.moveToFirst(AbstractCursor.java:258)
at me.shouheng.notepal.provider.BaseStore.getList(BaseStore.java:330)
at me.shouheng.notepal.provider.BaseStore.get(BaseStore.java:127)
at me.shouheng.notepal.provider.BaseStore.get(BaseStore.java:101)
at me.shouheng.notepal.widget.desktop.ListRemoteViewsFactory.getNotes(ListRemoteViewsFactory.java:72)
at me.shouheng.notepal.widget.desktop.ListRemoteViewsFactory.setupModels(ListRemoteViewsFactory.java:63)
at me.shouheng.notepal.widget.desktop.ListRemoteViewsFactory.onDataSetChanged(ListRemoteViewsFactory.java:86)
at android.widget.RemoteViewsService$RemoteViewsFactoryAdapter.onDataSetChanged(RemoteViewsService.java:142)
at com.android.internal.widget.IRemoteViewsFactory$Stub.onTransact(IRemoteViewsFactory.java:49)
at android.os.Binder.execTransact(Binder.java:565)
我确实将sychronized添加到了重要的方法,而db类是singleton,我想知道为什么我仍然会遇到问题。
更多信息。下面是我的日志,我打印了线程,数据库和存储对象:
02-05 22:41:53.119 15712-15712/me.shouheng.notepal D/colorful: ThemeDelegate fetched theme in 1 ms
02-05 22:41:53.305 3030-3149/system_process I/ActivityManager: Displayed me.shouheng.notepal/.activity.TrashedActivity: +235ms
02-05 22:41:53.648 15712-15712/me.shouheng.notepal D/NotePal: [ (BaseStore.java:327)#GetList ] me.shouheng.notepal.provider.NotebookStore@76f9b87
02-05 22:41:53.649 15712-15712/me.shouheng.notepal D/NotePal: [ (BaseStore.java:328)#GetList ] Thread[main,5,main]
02-05 22:41:53.656 15712-15712/me.shouheng.notepal D/NotePal: [ (BaseStore.java:327)#GetList ] me.shouheng.notepal.provider.NotesStore@f4252
02-05 22:41:53.656 15712-15712/me.shouheng.notepal D/NotePal: [ (BaseStore.java:328)#GetList ] Thread[main,5,main]
02-05 22:41:58.018 15712-15712/me.shouheng.notepal D/NotePal: [ (BaseStore.java:41)#<init> ] me.shouheng.notepal.provider.PalmDB@a34edfa
02-05 22:41:58.057 15712-15712/me.shouheng.notepal D/NotePal: [ (AppWidgetUtils.java:20)#NotifyAppWidgets ] Notifies AppWidget data changed for widgets [87, 85]
02-05 22:41:58.061 15712-15712/me.shouheng.notepal D/NotePal: [ (BaseStore.java:327)#GetList ] me.shouheng.notepal.provider.NotebookStore@76f9b87
02-05 22:41:58.062 15712-15712/me.shouheng.notepal D/NotePal: [ (BaseStore.java:328)#GetList ] Thread[main,5,main]
02-05 22:41:58.071 15712-15712/me.shouheng.notepal D/NotePal: [ (BaseStore.java:327)#GetList ] me.shouheng.notepal.provider.NotesStore@f4252
02-05 22:41:58.072 15712-15712/me.shouheng.notepal D/NotePal: [ (BaseStore.java:328)#GetList ] Thread[main,5,main]
02-05 22:41:58.155 15712-15712/me.shouheng.notepal D/NotePal: [ (ListRemoteViewsFactory.java:51)#OnCreate ] Created widget 87
02-05 22:41:58.157 15712-15712/me.shouheng.notepal D/NotePal: [ (BaseStore.java:41)#<init> ] me.shouheng.notepal.provider.PalmDB@a34edfa
02-05 22:41:58.158 15712-15712/me.shouheng.notepal D/NotePal: [ (ListRemoteViewsFactory.java:78)#GetMinds ] me.shouheng.notepal.provider.MindSnaggingStore@f341cfd
02-05 22:41:58.158 15712-15712/me.shouheng.notepal D/NotePal: [ (ListRemoteViewsFactory.java:79)#GetMinds ] Thread[main,5,main]
02-05 22:41:58.160 15712-15712/me.shouheng.notepal D/NotePal: [ (BaseStore.java:327)#GetList ] me.shouheng.notepal.provider.MindSnaggingStore@f341cfd
02-05 22:41:58.161 15712-15712/me.shouheng.notepal D/NotePal: [ (BaseStore.java:328)#GetList ] Thread[main,5,main]
02-05 22:41:58.169 15712-15712/me.shouheng.notepal D/NotePal: [ (ListRemoteViewsFactory.java:51)#OnCreate ] Created widget 85
02-05 22:41:58.171 15712-15712/me.shouheng.notepal D/NotePal: [ (BaseStore.java:327)#GetList ] me.shouheng.notepal.provider.NotesStore@f4252
02-05 22:41:58.172 15712-15712/me.shouheng.notepal D/NotePal: [ (BaseStore.java:328)#GetList ] Thread[main,5,main]
02-05 22:41:58.172 15712-16198/me.shouheng.notepal D/NotePal: [ (ListRemoteViewsFactory.java:85)#OnDataSetChanged ] onDataSetChanged widget 87
02-05 22:41:58.173 15712-16198/me.shouheng.notepal D/NotePal: [ (ListRemoteViewsFactory.java:78)#GetMinds ] me.shouheng.notepal.provider.MindSnaggingStore@f341cfd
02-05 22:41:58.173 15712-16198/me.shouheng.notepal D/NotePal: [ (ListRemoteViewsFactory.java:79)#GetMinds ] Thread[Binder:15712_3,5,main]
02-05 22:41:58.174 15712-16198/me.shouheng.notepal D/NotePal: [ (BaseStore.java:327)#GetList ] me.shouheng.notepal.provider.MindSnaggingStore@f341cfd
02-05 22:41:58.175 15712-16198/me.shouheng.notepal D/NotePal: [ (BaseStore.java:328)#GetList ] Thread[Binder:15712_3,5,main]
02-05 22:41:58.177 15712-15725/me.shouheng.notepal D/NotePal: [ (ListRemoteViewsFactory.java:85)#OnDataSetChanged ] onDataSetChanged widget 85
02-05 22:41:58.178 15712-15725/me.shouheng.notepal D/NotePal: [ (BaseStore.java:327)#GetList ] me.shouheng.notepal.provider.NotesStore@f4252
02-05 22:41:58.178 15712-15725/me.shouheng.notepal D/NotePal: [ (BaseStore.java:328)#GetList ] Thread[Binder:15712_1,5,main]
02-05 22:41:58.179 3030-4466/system_process W/InputMethodManagerService: Window already focused, ignoring focus gain of: com.android.internal.view.IInputMethodClient$Stub$Proxy@86f53ab attribute=null, token = android.os.BinderProxy@8dfbb89
02-05 22:41:58.483 15712-16191/me.shouheng.notepal D/OpenGLRenderer: endAllActiveAnimators on 0xc8ecfc00 (MenuPopupWindow$MenuDropDownListView) with handle 0xc8eb0540
02-05 22:41:58.933 3030-3485/system_process D/AudioService: Stream muted, skip playback
02-05 22:41:59.775 3030-3485/system_process D/AudioService: Stream muted, skip playback
02-05 22:41:59.804 15712-15712/me.shouheng.notepal D/NotePal: [ (AppWidgetUtils.java:20)#NotifyAppWidgets ] Notifies AppWidget data changed for widgets [87, 85]
02-05 22:41:59.807 15712-15725/me.shouheng.notepal D/NotePal: [ (ListRemoteViewsFactory.java:85)#OnDataSetChanged ] onDataSetChanged widget 87
02-05 22:41:59.807 15712-16198/me.shouheng.notepal D/NotePal: [ (ListRemoteViewsFactory.java:85)#OnDataSetChanged ] onDataSetChanged widget 85
02-05 22:41:59.808 15712-15725/me.shouheng.notepal D/NotePal: [ (ListRemoteViewsFactory.java:78)#GetMinds ] me.shouheng.notepal.provider.MindSnaggingStore@f341cfd
02-05 22:41:59.808 15712-15712/me.shouheng.notepal D/NotePal: [ (BaseStore.java:327)#GetList ] me.shouheng.notepal.provider.NotebookStore@76f9b87
02-05 22:41:59.808 15712-15725/me.shouheng.notepal D/NotePal: [ (ListRemoteViewsFactory.java:79)#GetMinds ] Thread[Binder:15712_1,5,main]
02-05 22:41:59.808 15712-15712/me.shouheng.notepal D/NotePal: [ (BaseStore.java:328)#GetList ] Thread[main,5,main]
02-05 22:41:59.808 15712-16198/me.shouheng.notepal D/NotePal: [ (BaseStore.java:327)#GetList ] me.shouheng.notepal.provider.NotesStore@f4252
02-05 22:41:59.808 15712-15725/me.shouheng.notepal D/NotePal: [ (BaseStore.java:327)#GetList ] me.shouheng.notepal.provider.MindSnaggingStore@f341cfd
02-05 22:41:59.810 15712-16198/me.shouheng.notepal D/NotePal: [ (BaseStore.java:328)#GetList ] Thread[Binder:15712_3,5,main]
02-05 22:41:59.810 15712-15712/me.shouheng.notepal I/SQLiteConnectionPool: The connection pool for /data/user/0/me.shouheng.notepal/databases/NotePal.db has been closed but there are still 1 connections in use. They will be closed as they are released back to the pool.
02-05 22:41:59.811 15712-15725/me.shouheng.notepal D/NotePal: [ (BaseStore.java:328)#GetList ] Thread[Binder:15712_1,5,main]
02-05 22:41:59.828 15712-15712/me.shouheng.notepal D/NotePal: [ (BaseStore.java:327)#GetList ] me.shouheng.notepal.provider.NotesStore@f4252
02-05 22:41:59.829 15712-15712/me.shouheng.notepal D/NotePal: [ (BaseStore.java:328)#GetList ] Thread[main,5,main]
02-05 22:41:59.867 15712-15717/me.shouheng.notepal I/art: Do full code cache collection, code=97KB, data=125KB
02-05 22:41:59.868 15712-15717/me.shouheng.notepal I/art: Starting a blocking GC JitCodeCache
02-05 22:41:59.868 15712-15717/me.shouheng.notepal I/art: After code cache collection, code=79KB, data=87KB
02-05 22:41:59.899 3030-14418/system_process W/InputMethodManagerService: Window already focused, ignoring focus gain of: com.android.internal.view.IInputMethodClient$Stub$Proxy@9aae3c6 attribute=null, token = android.os.BinderProxy@8dfbb89
02-05 22:41:59.957 15712-15725/me.shouheng.notepal E/AndroidRuntime: FATAL EXCEPTION: Binder:15712_1
Process: me.shouheng.notepal, PID: 15712
java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed.
at android.database.sqlite.SQLiteConnectionPool.throwIfClosedLocked(SQLiteConnectionPool.java:962)
at android.database.sqlite.SQLiteConnectionPool.waitForConnection(SQLiteConnectionPool.java:599)
at android.database.sqlite.SQLiteConnectionPool.acquireConnection(SQLiteConnectionPool.java:348)
at android.database.sqlite.SQLiteSession.acquireConnection(SQLiteSession.java:894)
at android.database.sqlite.SQLiteSession.executeForCursorWindow(SQLiteSession.java:834)
at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:62)
at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:143)
at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:132)
at android.database.AbstractCursor.moveToPosition(AbstractCursor.java:219)
at android.database.AbstractCursor.moveToFirst(AbstractCursor.java:258)
at me.shouheng.notepal.provider.BaseStore.getList(BaseStore.java:330)
at me.shouheng.notepal.provider.BaseStore.get(BaseStore.java:127)
at me.shouheng.notepal.provider.BaseStore.get(BaseStore.java:101)
at me.shouheng.notepal.widget.desktop.ListRemoteViewsFactory.getMinds(ListRemoteViewsFactory.java:80)
at me.shouheng.notepal.widget.desktop.ListRemoteViewsFactory.setupModels(ListRemoteViewsFactory.java:65)
at me.shouheng.notepal.widget.desktop.ListRemoteViewsFactory.onDataSetChanged(ListRemoteViewsFactory.java:86)
at android.widget.RemoteViewsService$RemoteViewsFactoryAdapter.onDataSetChanged(RemoteViewsService.java:142)
at com.android.internal.widget.IRemoteViewsFactory$Stub.onTransact(IRemoteViewsFactory.java:49)
at android.os.Binder.execTransact(Binder.java:565)
答案 0 :(得分:0)
我的问题解决了。原因很简单,它只是在一个地方,我使用了SQLiteDatabase.close(),所以它崩溃了。 即便如此,我仍然有机会了解有关Android数据库连接池的更多信息。这是解决这个问题对我有用的东西。
我们调用SQLiteOpenHelper.getWrteableDatabase()和SQLiteOpenHelper.getReadableDatabase()来获取SQLiteDatabase对象。逻辑很简单,如果SQLiteDatabase对象已经用于业务,它将返回它,否则它将创建一个新的并准备业务然后返回它。因此,这意味着,如果SQLiteOpenHelper是单例,则SQLiteDatabase对象也是单例。如果我们调用SQLiteOpenHelper.close()或SQLiteDatabase.close(),它们都将调用SQLiteDatabase.close()。在SQLiteDatabase.close()中,它将判断当前引用号,如果所有引用都被释放,则SQLiteConnectionPool将被关闭。
因此,这意味着,如果所有Store(用于存储和查询来自db的数据)对象使用相同的SQLiteDatabaseHelper,它们也将使用相同的SQLiteDatabase。如果我在一个Store对象中关闭SQLiteDatabase,它将影响另一个Store对象。即使我在一个Store方法中添加了sychronized关键字,它也只会锁定同一对象中的方法。这是造成这种异常的真正原因。
然后我删除了SQLiteDatabase.close()逻辑,它工作正常。
但是,如果我想关闭SQLiteDatabase怎么办?我找到了一个适合我案例的方法。 OpenHelperManager用于计算当前连接数,如果数字为零,则可以关闭数据库连接。
public class OpenHelperManager {
@SuppressLint("StaticFieldLeak")
private static boolean isClosed = false;
private static int instanceCount = 0;
public static synchronized void releaseHelper(PalmDB helper) {
instanceCount--;
LogUtils.e(String.format("releasing helper %s, instance count = %s", helper, instanceCount));
if (instanceCount <= 0) {
if (helper != null) {
LogUtils.e(String.format("zero instances, closing helper %s", helper));
helper.close();
isClosed = true;
}
if (instanceCount < 0) {
LogUtils.e(String.format("too many calls to release helper, instance count = %s", instanceCount));
}
}
}
public static synchronized void requireConnection() {
isClosed = false;
instanceCount++;
}
public static boolean isClosed() {
return isClosed;
}
}
这里,PalmDB扩展了SQLiteOpenHelper:
public class PalmDB extends SQLiteOpenHelper {
// ...
}
在商店对象中:
protected SQLiteDatabase getWritableDatabase() {
OpenHelperManager.requireConnection();
return mPalmDatabase.getWritableDatabase();
}
protected void closeDatabase(SQLiteDatabase database) {
OpenHelperManager.releaseHelper(mPalmDatabase);
}
我使用getWritableDatabase()方法获取SQLiteDatabase,同时增加OpenHelperManager中实例的数量。添加使用closeDatabase()方法而不是SQLiteDatabase.close()来关闭数据库。在closeDatabase()方法中,我调用了OpenHelperManager.releaseHelper(mPalmDatabase),这将减少OpenHelperManager中实例的数量。如果实例的计数为零,则关闭SQLiteOpenHelper,这将关闭SQLiteDatabse对象。它对我的情况很好。您也可以使用一些原子类(如AtomicInteger)来计算计数。
这是我的问题的答案,希望它对你有所帮助。