Android:Fragments,SQLite和Loaders

时间:2014-01-05 16:18:17

标签: java android sqlite android-fragments

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

我的一个用例是对数据库运行查询并在ListView内的Fragment上显示结果(我使用支持库中的片段)。

根据我的理解,使用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

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

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

还有其他选择吗?

5 个答案:

答案 0 :(得分:8)

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

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

答案 1 :(得分:7)

我推荐OrmLite库,这是一个适用于Android的轻量级对象关系映射。这个图书馆将让您的生活更轻松。您不需要手动创建或更新数据库,您不需要专注于管理数据库连接,使用DAO方法选择,插入,更新所有查询都会更容易(通常您不需要编写自己的sql查询)和很多功能。他们可以some examples开始。

如果您想使用Loadergithub上有ORMLite Extras个ORMLite附加功能(您可以使用与支持android库兼容的支持包) )。以下是我之前项目的一个示例用法:

public class EventsFragment extends Fragment implements LoaderCallbacks<Cursor>{
   private static final int LOADER_ID = EventsFragment.class.getName().hashCode();
   @Override
   public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getLoaderManager().initLoader(LOADER_ID, null, this);
   }

    @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
    View layoutRoot = inflater.inflate(
            R.layout.fragment_events, null);
    lvEvents = (ListView) layoutRoot.findViewById(R.id.lvEvents);   

    adapter = new EventAdapter(getActivity(), null, null);
    lvEvents.setAdapter(adapter);

    return layoutRoot;
}

    @Override
    public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
       try {
           PreparedQuery<Event> query = getDatabaseHelper().getEventDao().getQuery();
           return getDatabaseHelper().getEventDao().getSQLCursorLoader(query );
        } catch (Exception e) {
        e.printStackTrace();
    }

    return null;
}

@Override
public void onLoadFinished(Loader<Cursor> arg0, Cursor cursor) {
    adapter.swapCursor(cursor);
    try {
        adapter.setQuery(getDatabaseHelper().getEventDao().getQuery());
    } catch (SQLException e) {
        e.printStackTrace();
    }
      }

@Override
public void onLoaderReset(Loader<Cursor> arg0) {
    adapter.swapCursor(null);
}

    private OrmliteDatabaseHelper getDatabaseHelper(){
         return ((MainActivity)getActivity()).getDatabaseHelper();
    }
 }

适配器

 public class EventAdapter extends OrmliteCursorAdapter<Event>{

public EventAdapter(Context context, Cursor c, PreparedQuery<Event> query) {
    super(context, c, query);
}

@Override
public void bindView(View itemView, Context context, Event item) {
    TextView tvEventTitle = (TextView) itemView.findViewById(R.id.tvEventTitle); 
    TextView tvEventStartDate = (TextView) itemView.findViewById(R.id.tvEventStartDate);

    tvEventTitle.setText(item.getTitle());
    tvEventStartDate.setText(item.getFormatStartDate());
}

@Override
public View newView(Context context, Cursor arg1, ViewGroup arg2) {
    LayoutInflater inflater = LayoutInflater.from(context);
    View retView = inflater.inflate(R.layout.event_item_row, arg2, false);
    return retView;
}
 }

一个自定义Dao,为上面的游标适配器提供PreparedQuery

public interface IEventDao extends Dao<Event, Integer>{
    PreparedQuery<Event> getQuery() throws SQLException;
    OrmliteCursorLoader<Event> getSQLCursorLoader(Context context, PreparedQuery<Event> query) throws SQLException;
}

public class EventDao extends AndroidBaseDaoImpl<Event, Integer> implements IEventDao{

public EventDao(ConnectionSource connectionSource) throws SQLException {
    super(connectionSource, Event.class);
}

public EventDao(ConnectionSource connectionSource,
        DatabaseTableConfig<Event> tableConfig) throws SQLException {
    super(connectionSource, tableConfig);
}

@Override
public PreparedQuery<Event> getQuery() throws SQLException{
    return queryBuilder().prepare();
}
}

希望这有帮助!

答案 2 :(得分:6)

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

Here就是一个例子


编辑添加了Loader使用的更好示例。

最后设法找到了真正帮助我理解工作原理的教程。

通过扩展loader类,你可以避免搞乱内容观察者并且很容易实现(最后) 您的代码中需要进行的修改是

  • 添加LoaderManager.LoaderCallbacks<D>的实施,其中D是您的数据列表(摘录1)
  • 创建您的加载器类,复制代码段2,并从数据库
  • 添加数据加载
  • 最后调用加载器1调用init,然后刷新重启调用。 (摘录2&amp; 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);
    // ... 
  }

  /* ... */
}

Snippet 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:从here
  • 中提取的加载程序的用法
  • Snippet 2:here了解更多信息和逻辑阅读整篇文章
  • Snippet 3&amp; 4:只是装载机的使用。

创作者也会在github

上传这些完整代码

答案 3 :(得分:2)

如果您的数据库包含数千条记录,请考虑madlymad的答案 如果不保持愚蠢和简单,请使用SQLiteOpenHelper并创建一个方法,将数据作为字符串数组返回或定义您的一个对象。
还可以使用自定义/常规CursorAdapterArrayAdapter

答案 4 :(得分:1)

我使用SQLiteOpenHelper来创建我的数据库。我已经为所有表创建了Java类,当我从数据库中获取数据时,我把它放在一个ArrayList中。然后将ArrayList加载到Listview的适配器中。