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 и соответствующим образом управляет их жизненным циклом, следите за тем, чтобы они были отменены всякий раз, когда это необходимо и что-то еще.

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

Solutions Collecting From Web of "Android: Фрагменты, SQLite и Loaders"

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

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

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

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

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

И если вы хотите использовать 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(); } } 

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

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

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