Подтвердить что ты не робот

Android: Фрагменты, SQLite и Loaders

Итак, я пришел к тому моменту, когда мне нужно реализовать базу данных SQLite для моего приложения. Следуя "Руководству Busy Coder для Android Development", я создал класс 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 и правильно управлять их жизненным циклом, убедиться, что они отменены, когда это необходимо и что-то еще.

Есть ли другие варианты?

4b9b3361

Ответ 1

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

Здесь приведен пример этого


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

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

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

  • Добавить реализацию LoaderManager.LoaderCallbacks<D>, где D - ваш список данных (фрагмент 1)
  • Создайте свой класс загрузчика, скопируйте фрагмент 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);
    // ... 
  }

  /* ... */
}

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 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: использование загрузчика, извлеченного из здесь
  • Snippet 2: здесь для получения дополнительной информации и логики, прочитанной в статье с отверстиями.
  • Фрагменты 3 и 4: это просто использование загрузчика.

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

Ответ 2

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

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

Ответ 3

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

И если вы хотите использовать Loader, есть ORMLite Extras, дополнительные возможности для ORMLite доступны на github (вы можете использовать пакет поддержки который совместим с поддержкой андроидной библиотеки). Вот пример использования моего предыдущего проекта:

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();
}
}

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

Ответ 4

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

Ответ 5

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