Android: Fragments, SQLite and Loaders

2022-09-04 08:17:35

因此,我已经到了需要为我的应用程序实现SQLite数据库的地步。在“繁忙的编码器的Android开发指南”之后,我创建了一个扩展的DatabaseHelper类。SQLiteOpenHelper

我的一个用例是对数据库运行查询,并在 a 中显示结果(我使用来自支持库的片段)。ListViewFragment

据我所知,使用并不是很合适,即使它是,也不推荐使用,因为此方法中封装的一些逻辑实际上是在主线程上执行的,特别是在我的理解中,在重新启动时执行。managedQuery()reQuery()Activity

所以我一直试图第一次熟悉这门课,结果却看到了这一点:Loader

"The only supplied concrete implementation of a Loader is CursorLoader, and that is only for use with a ContentProvider"

我最初的想法是实现我自己的内容提供商,并可能阻止其他应用程序访问它,然后我通过 developer.android.com 阅读了文档中的以下内容:ContentProvider

"You don't need a provider to use an SQLite database if the use is entirely within your own application."

我也一直在玩这个:

https://github.com/commonsguy/cwac-loaderex

然而,我不熟悉这个项目,也不确定它是否可以在生产环境中使用。

所以,现在我能想到的就是在我的范围内创建一堆实例,并适当地管理它们的生命周期,确保在需要时取消它们等等。AsyncTaskFragment

还有其他选择吗?


答案 1

我认为实现内容提供程序是一个好主意,无论数据在应用程序之外都无法访问。它提供了非常现代的界面,根据我的经验,使您的应用程序错误容易出现数据库锁定问题和其他特定于数据库的问题。

我已经在我的最新项目中实现了它,我很高兴使用它。


答案 2

您可以扩展 Loader 类以执行其他异步工作,例如直接从数据库加载。

这是一个例子


编辑:添加了加载程序用法的更好示例。

终于设法找到了真正帮助我理解事情如何运作的教程。

通过扩展加载器类,您可以避免弄乱内容观察器,并且很容易实现(最后)需要在代码中进行的修改

  • 添加 的实现,其中是您的数据列表(代码段 1)LoaderManager.LoaderCallbacks<D>D
  • 创建加载程序类,复制代码段 2,然后添加从数据库加载数据
  • 最后调用加载程序 1 调用 init,然后刷新重新启动调用。(片段 2 和 3)

片段 1:如何将加载程序与片段“链接”:

public static class AppListFragment extends ListFragment implements
      LoaderManager.LoaderCallbacks<List<SampleItem>> {

  public Loader<List<SampleItem>> onCreateLoader(int id, Bundle args) { 
     //...
     return new SampleLoader (getActivity());
  }

  public void onLoadFinished(Loader<List<SampleItem>> loader, List<SampleItem> data) {
    // ... 
     mAdapter.setData(data);

     if (isResumed()) {
       setListShown(true);
     } else {
       setListShownNoAnimation(true);
     }
    // ... 
 }

  public void onLoaderReset(Loader<List<SampleItem>> loader) { 
    // ... 
    mAdapter.setData(null);
    // ... 
  }

  /* ... */
}

片段 2:自定义加载器的模式:(我已经对观察者的事情进行了双重注释,因为我相信从一开始就很难实现它,您可以简单地调用加载程序而不会弄乱自动刷新)

public class SampleLoader extends AsyncTaskLoader<List<SampleItem>> {

  // We hold a reference to the Loader’s data here.
  private List<SampleItem> mData;

  public SampleLoader(Context ctx) {
    // Loaders may be used across multiple Activitys (assuming they aren't
    // bound to the LoaderManager), so NEVER hold a reference to the context
    // directly. Doing so will cause you to leak an entire Activity's context.
    // The superclass constructor will store a reference to the Application
    // Context instead, and can be retrieved with a call to getContext().
    super(ctx);
  }

  /****************************************************/
  /** (1) A task that performs the asynchronous load **/
  /****************************************************/

  @Override
  public List<SampleItem> loadInBackground() {
    // This method is called on a background thread and should generate a
    // new set of data to be delivered back to the client.
    List<SampleItem> data = new ArrayList<SampleItem>();

    // TODO: Perform the query here and add the results to 'data'.

    return data;
  }

  /********************************************************/
  /** (2) Deliver the results to the registered listener **/
  /********************************************************/

  @Override
  public void deliverResult(List<SampleItem> data) {
    if (isReset()) {
      // The Loader has been reset; ignore the result and invalidate the data.
      releaseResources(data);
      return;
    }

    // Hold a reference to the old data so it doesn't get garbage collected.
    // We must protect it until the new data has been delivered.
    List<SampleItem> oldData = mData;
    mData = data;

    if (isStarted()) {
      // If the Loader is in a started state, deliver the results to the
      // client. The superclass method does this for us.
      super.deliverResult(data);
    }

    // Invalidate the old data as we don't need it any more.
    if (oldData != null && oldData != data) {
      releaseResources(oldData);
    }
  }

  /*********************************************************/
  /** (3) Implement the Loader’s state-dependent behavior **/
  /*********************************************************/

  @Override
  protected void onStartLoading() {
    if (mData != null) {
      // Deliver any previously loaded data immediately.
      deliverResult(mData);
    }

    // Begin monitoring the underlying data source.
    ////if (mObserver == null) {
      ////mObserver = new SampleObserver();
      // TODO: register the observer
    ////}

    //// takeContentChanged() can still be implemented if you want 
    ////     to mix your refreshing in that mechanism 
    if (takeContentChanged() || mData == null) {
      // When the observer detects a change, it should call onContentChanged()
      // on the Loader, which will cause the next call to takeContentChanged()
      // to return true. If this is ever the case (or if the current data is
      // null), we force a new load.
      forceLoad();
    }
  }

  @Override
  protected void onStopLoading() {
    // The Loader is in a stopped state, so we should attempt to cancel the 
    // current load (if there is one).
    cancelLoad();

    // Note that we leave the observer as is. Loaders in a stopped state
    // should still monitor the data source for changes so that the Loader
    // will know to force a new load if it is ever started again.
  }

  @Override
  protected void onReset() {
    // Ensure the loader has been stopped.
    onStopLoading();

    // At this point we can release the resources associated with 'mData'.
    if (mData != null) {
      releaseResources(mData);
      mData = null;
    }

    // The Loader is being reset, so we should stop monitoring for changes.
    ////if (mObserver != null) {
      // TODO: unregister the observer
     //// mObserver = null;
    ////}
  }

  @Override
  public void onCanceled(List<SampleItem> data) {
    // Attempt to cancel the current asynchronous load.
    super.onCanceled(data);

    // The load has been canceled, so we should release the resources
    // associated with 'data'.
    releaseResources(data);
  }

  private void releaseResources(List<SampleItem> data) {
    // For a simple List, there is nothing to do. For something like a Cursor, we 
    // would close it in this method. All resources associated with the Loader
    // should be released here.
  }

  /*********************************************************************/
  /** (4) Observer which receives notifications when the data changes **/
  /*********************************************************************/

  // NOTE: Implementing an observer is outside the scope of this post (this example
  // uses a made-up "SampleObserver" to illustrate when/where the observer should 
  // be initialized). 

  // The observer could be anything so long as it is able to detect content changes
  // and report them to the loader with a call to onContentChanged(). For example,
  // if you were writing a Loader which loads a list of all installed applications
  // on the device, the observer could be a BroadcastReceiver that listens for the
  // ACTION_PACKAGE_ADDED intent, and calls onContentChanged() on the particular 
  // Loader whenever the receiver detects that a new application has been installed.
  // Please don’t hesitate to leave a comment if you still find this confusing! :)
  ////private SampleObserver mObserver;
}

片段 3:如何首次调用加载程序(仅限)

  // Initialize a Loader with an id. If the Loader with this id is not 
  // initialized before
  getLoaderManager().initLoader(LOADER_ID, null, this);

片段 4:用于刷新数据(调用查询)

 // Check if the loader exists and then restart it.
 if (getLoaderManager().getLoader(LOADER_ID) != null)
     getLoaderManager().restartLoader(LOADER_ID, null, this);

参考:

  • 片段1:从这里提取的加载程序的用法
  • 片段2:此处了解更多信息和逻辑,请阅读整个孔文章
  • 片段 3 和 4:只是加载器用法。

这些完整的代码也由创建者上传到github


推荐