来自 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 【问题描述】:我在Fragment
s 中有一系列ListView
对象,这些对象由CursorAdapter
填充,该Cursor
从LoaderManager
获取活动的Cursor
。据我了解,所有数据库和Cursor
关闭操作都完全由LoaderManager
和ContentProvider
处理,因此我在任何代码中都没有在任何地方调用.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
中,但被ContentProvider
或Loader
错误关闭。这可能吗?我是否应该根据应用程序/活动/片段生命周期事件对 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
刚刚曝光的重要新信息
我在项目的其他地方发现了一些其他代码,它们没有使用Loader
s,也没有正确管理它的Cursor
s。我刚刚切换了这段代码以使用与上面相同的模式;但是,如果这可以解决问题,则表明项目某一部分中的非托管 Cursor
可能会在其他地方杀死正确管理的 Cursor
。
坚持。
新信息的结果
这并没有解决问题。
新想法
@Override
onDestroyView()
getActivity().getLoaderManager().destroyLoader(mFragmentId);
//any other destroy-time code
super.onDestroyView()
也就是说,可能是的,我应该在CursorAdapter
(或者更确切地说CursorLoader
与生命周期事件一致)上做家务。
新想法的结果
没有。
以前的想法
我添加了一个小调整后就可以正常工作了!但是它太复杂了,我可能应该重写整个问题。
【问题讨论】:
给我们看一些代码...ContentProvider.query
和OnLoadCompleteListener.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(...)
。但是,您通常会期望Loader
在LoaderCallbacks
中命中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的数据存储