代码之家  ›  专栏  ›  技术社区  ›  Shawn Wong

Java语言lang.IllegalStateException:无法执行此操作,因为连接池已关闭

  •  0
  • Shawn Wong  · 技术社区  · 7 年前

    我在自己的db框架中遇到了这个问题。我在StackOverflow中搜索了这个问题,并尝试了前面提到的许多方法,但这个问题并没有得到解决。当我尝试更新列表项时,它在单线程中工作得很好,这通常是应用程序小部件中的occor问题。

    解释如下:

    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、接下来是用于查询数据并将数据保存到数据库的存储区。它是抽象的:

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

    然后我重写这个类来表示这样的具体对象。正如您所看到的,它也是单例的:

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

    它在活动和片段中运行良好,但当我将其包含在应用程序小部件中时。当我尝试删除任何实体时,通常是两次,然后它崩溃了。

    例外情况如下:

    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)
    

    我确实将同步化添加到了重要的方法中,而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)
    
    1 回复  |  直到 7 年前
        1
  •  0
  •   Shawn Wong    7 年前

    我的问题解决了。原因很简单,我只在一个地方使用了SQLiteDatabase。close()使其崩溃。 尽管如此,这仍然是一个很好的机会让我了解更多关于Android数据库连接池的信息。这里有一些对我解决这个问题很有用的东西。

    我们称之为SQLiteOpenHelper。getWrteableDatabase()和SQLiteOpenHelper。getReadableDatabase()获取SQLiteDatabase对象。逻辑相当简单,如果SQLiteDatabase对象已经用于业务,它将返回它,否则它将创建一个新对象并为业务做好准备,然后返回它。因此,这意味着,如果SQLiteOpenHelper是singleton,那么SQLiteDatabase对象也是singleton。如果我们调用SQLiteOpenHelper。close()或SQLiteDatabase。close(),它们都将调用SQLiteDatabase。关闭()。在SQLiteDatabase中。close(),它将判断当前引用数,如果所有引用都被释放,则SQLiteConnectionPool将被关闭。

    因此,这意味着,如果所有存储(用于存储和查询db中的数据)对象都使用相同的SQLiteDatabaseHelper,那么它们也将使用相同的SQLiteDatabase。如果我在一个存储对象中关闭了SQLiteDatabase,它将影响另一个存储对象。即使我在一个Store方法中添加了synchronized关键字,它也只会锁定同一对象中的方法。这就是导致这一异常的真正原因。

    然后,我删除了SQLiteDatabase。close()逻辑,工作正常。

    但是,如果我想关闭SQLiteDatabase怎么办。我为我的案子找到了一条出路。OpenHelperManager用于计算当前连接数,如果该数为零,则可以关闭db连接。

    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中的实例数。添加use closeDatabase()方法,而不是SQLiteDatabase。close()关闭数据库。在closeDatabase()方法中,我调用了OpenHelperManager。releaseHelper(mPalmDatabase),这将减少OpenHelperManager中的实例数。如果实例计数为零,则关闭SQLiteOpenHelper,这将关闭SQLiteDatabase对象。这对我的案子很有效。您还可以使用一些原子类(如AtomicInteger)来计算计数。

    这就是我问题的答案,希望对你有所帮助。