来自 ContentProvider 的 SimpleCursorAdapter 中的 IllegalStateException“尝试重新打开已经关闭的对象”

Posted

技术标签:

【中文标题】来自 ContentProvider 的 SimpleCursorAdapter 中的 IllegalStateException“尝试重新打开已经关闭的对象”【英文标题】:IllegalStateException "attempt to re-open an already-closed object" in SimpleCursorAdapter from ContentProvider 【发布时间】:2013-02-04 01:58:29 【问题描述】:

我在Fragments 中有一系列ListView 对象,这些对象由CursorAdapter 填充,该CursorLoaderManager 获取活动的Cursor。据我了解,所有数据库和Cursor 关闭操作都完全由LoaderManagerContentProvider 处理,因此我在任何代码中都没有在任何地方调用.close()

但是,有时我会遇到以下异常:

02-19 11:07:12.308 E/androidRuntime(18777): java.lang.IllegalStateException: attempt to re-open an already-closed object: android.database.sqlite.SQLiteQuery (mSql = SELECT * FROM privileges WHERE uuid!=?) 
02-19 11:07:12.308 E/AndroidRuntime(18777): at android.database.sqlite.SQLiteClosable.acquireReference(SQLiteClosable.java:33)
02-19 11:07:12.308 E/AndroidRuntime(18777): at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:82)
02-19 11:07:12.308 E/AndroidRuntime(18777): at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:164)
02-19 11:07:12.308 E/AndroidRuntime(18777): at android.database.sqlite.SQLiteCursor.onMove(SQLiteCursor.java:147)
02-19 11:07:12.308 E/AndroidRuntime(18777): at android.database.AbstractCursor.moveToPosition(AbstractCursor.java:178)
02-19 11:07:12.308 E/AndroidRuntime(18777): at android.database.CursorWrapper.moveToPosition(CursorWrapper.java:162)
02-19 11:07:12.308 E/AndroidRuntime(18777): at android.widget.CursorAdapter.getView(CursorAdapter.java:241)

我将一些日志代码放入我的CursorAdapter 中,告诉我何时调用getView(...)getItem(...)getItemId(...),并且对于给定适配器之后的第一个getView(...) 似乎发生这种情况很多getView(...)s 用于另一个适配器。用户在应用中浏览了很多次后也会发生这种情况。

这让我想知道适配器的Cursor 是否保留在CursorAdapter 中,但被ContentProviderLoader 错误关闭。这可能吗?我是否应该根据应用程序/活动/片段生命周期事件对 CursorAdapter 进行任何内务管理?

ContentProvider查询方法:

class MyContentProvider extends ContentProvider 
//...

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
            String[] selectionArgs, String sortOrder) 
        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
        Cursor query = db.query(getTableName(uri), projection, selection, selectionArgs, null, null, sortOrder);
        query.setNotificationUri(getContext().getContentResolver(), uri);
        return query;
    

//...

典型的LoaderCallbacks:

LoaderCallbacks<Cursor> mCallbacks = new LoaderCallbacks<Cursor>() 

    @Override
    public void onLoaderReset(Loader<Cursor> loader) 
        mArticleAdapter.swapCursor(null);
    

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) 
        if(cursor.isClosed()) 
            Log.d(TAG, "CURSOR RETURNED CLOSED");
            Activity activity = getActivity();
            if(activity!=null) 
                activity.getLoaderManager().restartLoader(mFragmentId, null, mCallbacks);
            
            return;
        
        mArticleAdapter.swapCursor(cursor);
    

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) 
        triggerArticleFeed();
        CursorLoader cursorLoader = null;

        if(id == mFragmentId) 
            cursorLoader = new CursorLoader(getActivity(),
                                            MyContentProvider.ARTICLES_URI,
                                            null,
                                            ArticlesContentHelper.ARTICLES_WHERE,
                                            ArticlesContentHelper.ARTICLES_WHEREARGS,
                                            null);
        
        return(cursorLoader);
    
;

CursorAdapter构造函数:

public ArticlesCursorAdapter(Context context, Cursor c) 
    super(context, c, 0);
    mImageloader = new ImageLoader(context);

我已经阅读了这个问题,但不幸的是它没有得到我的问题的答案,因为它只是建议使用 ContentProvider,我就是。

IllegalStateException: attempt to re-open an already-closed object. SimpleCursorAdapter problems

刚刚曝光的重要新信息

我在项目的其他地方发现了一些其他代码,它们没有使用Loaders,也没有正确管理它的Cursors。我刚刚切换了这段代码以使用与上面相同的模式;但是,如果这可以解决问题,则表明项目某一部分中的非托管 Cursor 可能会在其他地方杀死正确管理的 Cursor

坚持。

新信息的结果

这并没有解决问题。

新想法

@Override
onDestroyView() 
    getActivity().getLoaderManager().destroyLoader(mFragmentId);
    //any other destroy-time code
    super.onDestroyView()

也就是说,可能是的,我应该CursorAdapter(或者更确切地说CursorLoader 与生命周期事件一致)上做家务。

新想法的结果

没有。

以前的想法

我添加了一个小调整后就可以正常工作了!但是它太复杂了,我可能应该重写整个问题。

【问题讨论】:

给我们看一些代码...ContentProvider.queryOnLoadCompleteListener.onLoadComplete ok more info ... in onLoadComplete 你应该简单地使用 Adapter.swapCursor(newCursor); ... next ... 不要自己将 Cursor 实例存储在 Adapter 中,只需在 Adapter 中使用 getCursor() 。 .. 所以当 Cursor 交换后你会得到 Cursor 的新实例 ... @Selvin 这就是我正在做的事情,我只是直接使用传递给bindView(...) 的光标。当光标被CursorAdapter 代码(我没有覆盖)移动到位时,崩溃实际上就来了。 哦,没有OnLoadCompleteListener,我正在实现LoaderCallbacks 添加到ContentProvider#query(...) 和典型的LoaderCallbacks。周围有很多这样的人,但他们看起来都差不多。 【参考方案1】:

您是否更新了数据集?可能是由于通知内容解析器中的更改而重新加载了光标:

getContentResolver().notifyChange(URI, null);

如果您设置了通知 URI,这将触发您当前的光标关闭,并由光标加载器返回一个新光标。如果你已经注册了一个 onLoadCompleteListener,你就可以抓住新的光标:

mCursorLoader.registerListener(0, new OnLoadCompleteListener<Cursor>() 
    @Override
    public void onLoadComplete(Loader<Cursor> loader, Cursor cursor) 
        // Set your listview's CursorAdapter
    
);

【讨论】:

我正在使用它,并且我已将Cursor 设置为也可以接收有关该 URI 的通知。我认为CursorAdapter 应该自动处理这个问题吗?我已经在其中实现LoaderCallbacks 并调用CursorAdapter#swapCursor(...) 我认为这可能是相关的,实际上:我打电话给notifyChange(...)。但是,您通常会期望LoaderLoaderCallbacks 中命中onLoadFinished(...),如果不是,它是否会在OnLoadCompleteListener 中命中onLoadComplete(...)?这似乎有点像重复。如果我需要这样做,请告诉我。 我提出了一个新问题:***.com/questions/14963524/… 看起来注册这两种监听器是个杀手:02-19 17:46:25.139: E/AndroidRuntime(24886): java.lang.IllegalStateException: There is already a listener registered【参考方案2】:

您可以尝试将 null 路径改为 cursor 到适配器构造函数中。然后在适配器中调用 SwapCursor(Cursor c),将光标数据的初始化移到那里并在数据加载器的 OnLoadFinished(Loader loader, Cursor data) 方法中调用它。

enter code here
    @Override
    public void onActivityCreated(Bundle savedInstanceState) 
    // ... building your query here
       mSimpleCursorAdapter = new mSimpleCursorAdapter(getActivity().getApplicationContext(),
           layout, null, from, to, flags);
    

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) 
       contentAdapter.swapCursor(data);
    

【讨论】:

以上是关于来自 ContentProvider 的 SimpleCursorAdapter 中的 IllegalStateException“尝试重新打开已经关闭的对象”的主要内容,如果未能解决你的问题,请参考以下文章

为啥 ContentProvider 在 App 更新后返回空游标?

在 isabelle 的证明中打印/显示证明方法的详细步骤(如 simp)

Atitit cnchar simp best list  汉字简化方案 最简化汉字256个

四大组件之ContentProvider-ContentProvider的数据存储

四大组件之ContentProvider-ContentProvider的权限使用和监听

四大组件之ContentProvider-轻轻松松自定义ContentProvider