Cursor、CursorAdapter、LoaderManager、Loader的关系
Relationship between Cursor, CursorAdapter, LoaderManager, Loader
所以在过去的几个月里,我一直在进行 IOS 开发工作,并且广泛使用了 Core Data 和 NSFetchedResultsController。
Core Data + NSFetchedResultsController:自动检测数据库的变化并相应地更新 table 元素。
现在我已经切换到 Android 开发并且我一直在寻找与上述相同的东西。我查看了各种可用的 classes,但有点困惑。
classes 的不同类型:
- 游标:提供对数据库查询结果的访问。
- CursorAdapter:将(列表、回收器)视图与光标链接并显示对象。
- ContentProvider:提供对数据库对象的访问。需要使用 LoaderManager。
- LoaderManager:由 Activity 或 Fragment 实现以无阻塞地加载数据 UI
- 加载器:当内容改变时生成游标对象的加载器。
我认为还值得一提的是我正在使用 greendao,我正在使用它生成的 ContentProvider。
更新流程
这是我有点疑惑的地方。这是我的假设。
- Content Provider 维护一个游标 -- 由 LoaderManager 使用,也可能由 CursorAdapter 使用。
- 当数据库发生变化时,Loader 有一个 ForceLoadContentObserver,它会观察游标并在其内容发生变化时调用 onLoadFinished。
- 通常游标适配器会在
onLoadFinished()
中调用 swap()
,从而更新 table。
牢记以上内容,我创建了一个 ContentProvider 并实现了 LoaderManager,但是当我持久化一个新对象(使用 greenDao)时函数 onLoadFinished()
没有被调用——这让我开始质疑是否我正确地理解了这个过程。这是一段代码,展示了到目前为止我通常编写的代码。
片段class
public class MissionPageFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {
// Various initialization methods
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getLoaderManager().initLoader(0, savedInstanceState, this);
}
@Override
public Loader onCreateLoader(int id, Bundle args) {
ContentProvider.daoSession = Main.daoSession;
Uri uri = ContentProvider.CONTENT_URI;
// Other initializations
CursorLoader cursorLoader = new CursorLoader(getContext(), uri, projections, null, null, null);
return cursorLoader;
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
System.out.println("Should be called when a new item is persisted... but not called =(");
}
// Other methods
}
如果有人可以确认我是否正确地考虑了这个过程and/or阐明可能出了什么问题,我将不胜感激。
编辑 1
这是 greenDao 生成的 ContentProvider subclass 中 query()
函数的片段。
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
int uriType = sURIMatcher.match(uri);
switch (uriType) {
case MISSION_DIR:
queryBuilder.setTables(TABLENAME);
break;
case MISSION_ID:
queryBuilder.setTables(TABLENAME);
queryBuilder.appendWhere(PK + "="
+ uri.getLastPathSegment());
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
SQLiteDatabase db = getDatabase();
Cursor cursor = queryBuilder.query(db, projection, selection,
selectionArgs, null, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
解决方案
我只是想分享一下我是如何使用 GreenDao 实现上述所有 classes 的。让我知道是否有更好的方法来执行此操作,但我觉得这已经足够了。
通过将以下代码片段添加到 GreenDaoGenerator class
来生成一个 ContentProvider subclass
Entity entity = new Entity();
entity.addContentProvider();
检查是否正确调用了 ContentProvider subclass 方法。将 javadoc 部分添加到 AndroidManifest.xml。对我来说,我还必须将生成的 ContentProvider class BASE_PATH
变量更改为另一个变量,如下所示。
public static final String BASE_PATH = "MYTABLENAME";
在Activity/Fragmentclass中添加LoaderManager、Loader,就像我上面写的那样。在使用 ContentProvider 之前,您还必须设置 daoSession
对象。我已将下面的代码片段移至另一个初始化程序 class,以便其他 Activity/Fragment classes 也可以使用它。
ContentProvider.daoSession = Main.daoSession;
我使用的是 RecyclerView,因此我对此处 https://gist.github.com/skyfishjy/443b7448f59be978bc59 提供的 CursorRecyclerViewAdapter class 进行了子class 并进行了一些自定义。如果您使用的是 ListView,则应该能够使用简单的 CursorAdapter。由于 Loader
正在监听更新,因此 Adapter
不需要监听更新。
现在 ContentProvider subclass 由于这条线
能够更新视图
getContext().getContentResolver().notifyChange(uri, null);
由GreenDao自动生成。这意味着您可以将 ContentProvider 用于所有 CRUD 操作,并且视图将相应地自动更新。这似乎违背了使用 ORM 的目的。因此,我现在执行正常的 GreenDao 操作并在每次插入、删除、更新后手动调用 notifyChange
。
GreenDaoObj obj = new GreenDaoObj();
obj.insert();
getContext.getContentResolver().notifyChange(MyContentProvider.CONTENT_URI, null);
我认为没有比这更好的方法了,因为我真的不想接触 GreenDao 为 Model 和 Dao 对象生成的代码。我想可以添加嵌套生成的 CRUD 方法的自定义 CRUD 函数,但这应该是微不足道的。
我一直在四处寻找,并没有一个很好的文档记录方法来使用 GreenDao 和 Loader/LoaderManager 所以我想我会在这里组织这个。希望这对计划实施此计划的任何人有所帮助。
有几件事您没有完全正确。
ContentProvider
是 Android 提供数据访问的组件。该数据可以存储在关系数据库、平面文件、远程服务器等中。提供程序具有类似 REST 的通用接口,并作为您拥有的不同数据存储选项的抽象层。 ContentProvider
s 也可以从外部应用程序访问(如果配置正确),这使它们成为应用程序之间共享数据的默认方式。访问它们不需要 LoaderManager
。应通过 ContentResolver
. 访问它们
LoaderManager
负责 Loader
生命周期并将其与 Activity
和 Fragment
生命周期协调。
Loader
s 提供了一种生命周期感知方式来将数据加载到活动或片段中。 CursorLoader
是一个特殊的实现,带有一些特性——它使用工作线程来保持 UI 线程的加载,它抽象使用 ContentResolver
来访问 ContentProvider
并且还设置了一个ContentObserver
。 ContentObserver
将侦听查询 URL 的更新并在需要时重新加载数据。
为了让 ContentObserver
通知正常工作,您必须确保准备好几件事。首先,您的 ContentProvider
应该将更改传播给观察者。这是使用 getContext().getContentResolver().notifyChange(url, observer)
完成的。然后,如果您有一个 CursorLoader
在通知的 URL 上执行查询,它将自动重新加载。
所以在过去的几个月里,我一直在进行 IOS 开发工作,并且广泛使用了 Core Data 和 NSFetchedResultsController。
Core Data + NSFetchedResultsController:自动检测数据库的变化并相应地更新 table 元素。
现在我已经切换到 Android 开发并且我一直在寻找与上述相同的东西。我查看了各种可用的 classes,但有点困惑。
classes 的不同类型:
- 游标:提供对数据库查询结果的访问。
- CursorAdapter:将(列表、回收器)视图与光标链接并显示对象。
- ContentProvider:提供对数据库对象的访问。需要使用 LoaderManager。
- LoaderManager:由 Activity 或 Fragment 实现以无阻塞地加载数据 UI
- 加载器:当内容改变时生成游标对象的加载器。
我认为还值得一提的是我正在使用 greendao,我正在使用它生成的 ContentProvider。
更新流程
这是我有点疑惑的地方。这是我的假设。
- Content Provider 维护一个游标 -- 由 LoaderManager 使用,也可能由 CursorAdapter 使用。
- 当数据库发生变化时,Loader 有一个 ForceLoadContentObserver,它会观察游标并在其内容发生变化时调用 onLoadFinished。
- 通常游标适配器会在
onLoadFinished()
中调用swap()
,从而更新 table。
牢记以上内容,我创建了一个 ContentProvider 并实现了 LoaderManager,但是当我持久化一个新对象(使用 greenDao)时函数 onLoadFinished()
没有被调用——这让我开始质疑是否我正确地理解了这个过程。这是一段代码,展示了到目前为止我通常编写的代码。
片段class
public class MissionPageFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {
// Various initialization methods
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getLoaderManager().initLoader(0, savedInstanceState, this);
}
@Override
public Loader onCreateLoader(int id, Bundle args) {
ContentProvider.daoSession = Main.daoSession;
Uri uri = ContentProvider.CONTENT_URI;
// Other initializations
CursorLoader cursorLoader = new CursorLoader(getContext(), uri, projections, null, null, null);
return cursorLoader;
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
System.out.println("Should be called when a new item is persisted... but not called =(");
}
// Other methods
}
如果有人可以确认我是否正确地考虑了这个过程and/or阐明可能出了什么问题,我将不胜感激。
编辑 1
这是 greenDao 生成的 ContentProvider subclass 中 query()
函数的片段。
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
int uriType = sURIMatcher.match(uri);
switch (uriType) {
case MISSION_DIR:
queryBuilder.setTables(TABLENAME);
break;
case MISSION_ID:
queryBuilder.setTables(TABLENAME);
queryBuilder.appendWhere(PK + "="
+ uri.getLastPathSegment());
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
SQLiteDatabase db = getDatabase();
Cursor cursor = queryBuilder.query(db, projection, selection,
selectionArgs, null, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
解决方案
我只是想分享一下我是如何使用 GreenDao 实现上述所有 classes 的。让我知道是否有更好的方法来执行此操作,但我觉得这已经足够了。
通过将以下代码片段添加到 GreenDaoGenerator class
来生成一个 ContentProvider subclassEntity entity = new Entity(); entity.addContentProvider();
检查是否正确调用了 ContentProvider subclass 方法。将 javadoc 部分添加到 AndroidManifest.xml。对我来说,我还必须将生成的 ContentProvider class
BASE_PATH
变量更改为另一个变量,如下所示。public static final String BASE_PATH = "MYTABLENAME";
在Activity/Fragmentclass中添加LoaderManager、Loader,就像我上面写的那样。在使用 ContentProvider 之前,您还必须设置
daoSession
对象。我已将下面的代码片段移至另一个初始化程序 class,以便其他 Activity/Fragment classes 也可以使用它。ContentProvider.daoSession = Main.daoSession;
我使用的是 RecyclerView,因此我对此处 https://gist.github.com/skyfishjy/443b7448f59be978bc59 提供的 CursorRecyclerViewAdapter class 进行了子class 并进行了一些自定义。如果您使用的是 ListView,则应该能够使用简单的 CursorAdapter。由于
Loader
正在监听更新,因此Adapter
不需要监听更新。现在 ContentProvider subclass 由于这条线
能够更新视图getContext().getContentResolver().notifyChange(uri, null);
由GreenDao自动生成。这意味着您可以将 ContentProvider 用于所有 CRUD 操作,并且视图将相应地自动更新。这似乎违背了使用 ORM 的目的。因此,我现在执行正常的 GreenDao 操作并在每次插入、删除、更新后手动调用
notifyChange
。GreenDaoObj obj = new GreenDaoObj(); obj.insert(); getContext.getContentResolver().notifyChange(MyContentProvider.CONTENT_URI, null);
我认为没有比这更好的方法了,因为我真的不想接触 GreenDao 为 Model 和 Dao 对象生成的代码。我想可以添加嵌套生成的 CRUD 方法的自定义 CRUD 函数,但这应该是微不足道的。
我一直在四处寻找,并没有一个很好的文档记录方法来使用 GreenDao 和 Loader/LoaderManager 所以我想我会在这里组织这个。希望这对计划实施此计划的任何人有所帮助。
有几件事您没有完全正确。
ContentProvider
是 Android 提供数据访问的组件。该数据可以存储在关系数据库、平面文件、远程服务器等中。提供程序具有类似 REST 的通用接口,并作为您拥有的不同数据存储选项的抽象层。ContentProvider
s 也可以从外部应用程序访问(如果配置正确),这使它们成为应用程序之间共享数据的默认方式。访问它们不需要LoaderManager
。应通过ContentResolver
. 访问它们
LoaderManager
负责Loader
生命周期并将其与Activity
和Fragment
生命周期协调。Loader
s 提供了一种生命周期感知方式来将数据加载到活动或片段中。CursorLoader
是一个特殊的实现,带有一些特性——它使用工作线程来保持 UI 线程的加载,它抽象使用ContentResolver
来访问ContentProvider
并且还设置了一个ContentObserver
。ContentObserver
将侦听查询 URL 的更新并在需要时重新加载数据。
为了让 ContentObserver
通知正常工作,您必须确保准备好几件事。首先,您的 ContentProvider
应该将更改传播给观察者。这是使用 getContext().getContentResolver().notifyChange(url, observer)
完成的。然后,如果您有一个 CursorLoader
在通知的 URL 上执行查询,它将自动重新加载。