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

时间:2018-02-05 12:30:23

标签: android database sqlite database-design

我在自己的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)

1 个答案:

答案 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)来计算计数。

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