Android: фрагменты, SQLite и загрузчики


Таким образом, я пришел к точке, где мне нужно реализовать базу данных SQLite для моего приложения. Следуя "руководству занятого программиста по разработке Android", я создал класс DatabaseHelper, который расширяет SQLiteOpenHelper.

Один из моих вариантов использования-выполнить запрос к базе данных и отобразить результаты на 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"

Моя первоначальная мысль состояла в том, чтобы реализовать мой собственный контент-провайдер и, возможно, предотвратить доступ к нему других приложений, затем я прочитал следующее в ContentProvider документация через developer.android.com:

"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 13

5 ответов:

Вы можете расширить класс Loader для выполнения другой асинхронной работы, такой как загрузка непосредственно из вашей БД.

Вот пример того, что


Edit: добавлен лучший пример использования загрузчика.

Наконец-то удалось найти учебник, который действительно помог мне понять, как все работает.

Расширяя класс loader, вы можете избежать путаницы с наблюдателями контента и его довольно легко реализовать (наконец-то) модификации, которые должны быть принятых в ваш код

  • добавьте реализацию LoaderManager.LoaderCallbacks<D>, где D - ваш список данных (фрагмент 1)
  • создайте свой класс загрузчика, скопируйте фрагмент 2 и добавьте загрузку ваших данных из БД
  • , наконец, вызвать грузчиков 1 вызова для инициализации и после обновления перезагрузите звонок. (фрагмент 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: здесь для получения дополнительной информации и логики прочитайте всю статью hole
  • фрагмент 3 и 4: это просто использование загрузчика.

Полный код из них также загружен создателем на github

Я думаю, что реализация content provider-это хорошая идея, независимо от того, что данные не будут доступны за пределами приложения. Он обеспечивает очень современный интерфейс и, по моему опыту, делает вашу ошибку приложения склонной к проблемам блокировки базы данных и другим специфичным для БД проблемам.

Я реализовал его в своем последнем проекте и был очень рад его использовать.

Я рекомендуюбиблиотеку OrmLite , легкое объектное реляционное отображение, которое может работать для Android. Эта библиотека сделает вашу жизнь проще . Вам не нужно создавать или обновлять базу данных вручную, вам не нужно сосредотачиваться на управлении подключением к базе данных, все запросы select, insert, update будут проще с подходом DAO (обычно вам не нужно писать свой собственный sql-запрос) и множеством функций. У них есть некоторые примеры, с которых вы можете начать.

И если вы хотите использовать Loader, есть OrmLite Extras , дополнительная функциональность для ORMLite доступна на github (Вы можете использовать пакет поддержки, который совместим с поддержкой библиотеки 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();
}
}

Надеюсь, это поможет!

Если ваша база данных содержит тысячи записей, рассмотрите ответ madlymad
Если не держите его глупым и простым, используйте SQLiteOpenHelper и создайте метод, который возвращает вам ваши данные в виде массива строк или определяет ваши объекты one.
Также используйте custom / regular CursorAdapter или ArrayAdapter.

Я использую SQLiteOpenHelper для создания своей базы данных. Я создал классы Java для всех таблиц, и когда я получаю данные из своей базы данных, я помещаю их в ArrayList. Затем ArrayList я загружаю в адаптер Listview.