Cursor,CursorAdapter,LoaderManager,Loader

时间:2016-04-26 15:14:21

标签: java android android-contentprovider loader greendao

所以我在过去几个月一直在使用IOS开发,并广泛使用了Core Data和NSFetchedResultsController。

Core Data + NSFetchedResultsController:自动检测对数据库的更改并相应地更新表元素。

现在我已经切换到Android开发,我一直在寻找相同的上述内容。我看过各种不同的课程,有点困惑。

不同类型的类:

  1. Cursor:提供对数据库查询结果的访问。
  2. CursorAdapter:使用光标链接(list,recycler)视图并显示对象。
  3. ContentProvider:提供对数据库对象的访问。使用LoaderManager时需要。
  4. LoaderManager:由Activity或Fragment实现以加载数据而不阻止UI
  5. Loader:内容更改后生成游标对象的Loader。
  6. 我认为值得一提的是我使用的是greendao,我正在使用它生成的ContentProvider。

    更新流程

    这是我有点不确定的地方。这是我的假设。

    1. Content Provider维护一个游标 - 由LoaderManager使用,也可能是CursorAdapter。
    2. 当数据库发生更改时,Loader会有一个ForceLoadContentObserver,它会观察游标并在内容更改时调用onLoadFinished。
    3. 通常,游标适配器会调用swap()内的onLoadFinished(),从而导致表的更新。
    4. 记住上面的内容,我创建了一个ContentProvider并实现了LoaderManager,但是当我持久化一个新对象(使用greenDao)时,函数onLoadFinished()没有被调用 - 这让我开始质疑我是不是正确理解过程。这是一段代码,用于显示到目前为止我通常编码的内容。

      片段类

      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
      }
      

      如果有人能够确认我是否正确地考虑了这个过程和/或了解可能出现的问题,我会非常感激。

      编辑1

      以下是greenDao生成的ContentProvider子类中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实现上述所有类。让我知道是否有更好的方法来做到这一点,但我觉得这已经足够了。

      1. 通过将以下代码片段添加到GreenDaoGenerator类来生成ContentProvider子类

        Entity entity = new Entity();
        entity.addContentProvider();
        
      2. 检查是否正确调用了ContentProvider子类方法。将javadoc部件添加到AndroidManifest.xml。对我来说,我还必须将生成的ContentProvider类BASE_PATH变量更改为另一个变量,如下所示。

        public static final String BASE_PATH = "MYTABLENAME";
        
      3. 将LoaderManager,Loader添加到Activity / Fragment类,就像我上面写的那样。您还必须在使用ContentProvider之前设置daoSession对象。我已将下面的代码段移动到另一个初始化程序类,以便其他Activity / Fragment类也可以使用它。

        ContentProvider.daoSession = Main.daoSession;
        
      4. 我正在使用RecyclerView,所以我将https://gist.github.com/skyfishjy/443b7448f59be978bc59提供的CursorRecyclerViewAdapter类子类化了一些自定义。如果使用ListView,则应该能够使用简单的CursorAdapter。由于Loader正在侦听更新,因此Adapter无需侦听更新。

      5. 现在ContentProvider子类能够根据此行更新视图

        getContext().getContentResolver().notifyChange(uri, null);
        

        由GreenDao自动生成。这意味着您可以将ContentProvider用于所有CRUD操作,并且视图将相应地自动更新。这似乎打败了使用ORM的目的。因此,我现在进行正常的GreenDao操作,并在每次插入,删除,更新后手动调用notifyChange

        GreenDaoObj obj = new GreenDaoObj();
        obj.insert();
        getContext.getContentResolver().notifyChange(MyContentProvider.CONTENT_URI, null);
        
      6. 我认为没有比这更好的方法,因为我真的不想触摸GreenDao为Model和Dao对象生成的代码。我想可以添加自定义CRUD函数来嵌套生成的CRUD方法,但这应该是微不足道的。

        我一直在环顾四周,并没有一个记录良好的方法来使用GreenDao和Loader / LoaderManager,所以我想我会在这里组织这个。希望这有助于任何计划实施此项目的人。

1 个答案:

答案 0 :(得分:3)

只有一些事情你没有完全正确。

  1. ContentProvider是提供数据访问权限的Android组件。该数据可以存储在关系数据库,平面文件,远程服务器等中。提供者具有类似REST的通用接口,并且可以作为您拥有的不同数据存储选项的抽象层。 ContentProvider也可以从外部应用程序访问(如果配置正确),这使它们成为在应用程序之间共享数据的默认方式。访问它们并不需要LoaderManager。应通过ContentResolver
  2. 访问它们
  3. LoaderManager负责Loader生命周期并与ActivityFragment生命周期协调。
  4. Loader提供了一种生命周期感知方式,可将数据加载到活动或片段中。 CursorLoader是一个特殊的实现,带有一系列功能 - 它使用工作线程来保持UI线程的加载,使用ContentResolver抽象来访问ContentProvider和还会设置ContentObserverContentObserver将侦听查询URL的更新,并在需要时重新加载数据。
  5. 为了让ContentObserver通知正常工作,您必须确保有一些事情到位。首先,您的ContentProvider应该将更改传播给观察者。这是使用getContext().getContentResolver().notifyChange(url, observer)完成的。然后,如果您有CursorLoader对通知的网址执行了查询,则会自动重新加载。