【问题标题】:Android: Fragments, SQLite and LoadersAndroid:片段、SQLite 和加载器
【发布时间】:2014-01-23 00:38:38
【问题描述】:

所以我已经到了需要为我的应用程序实现 SQLite 数据库的地步。 按照“The Busy Coder's guide to Android Development”,我创建了一个扩展 SQLiteOpenHelper 的 DatabaseHelper 类。

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

据我了解,使用managedQuery() 并不是很合适,即使这样也不推荐,因为封装在此方法中的一些逻辑实际上是在主线程上执行的,特别是@987654326 @ 我的理解是在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

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

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

还有其他选择吗?

【问题讨论】:

  • 我会使用 CommonsWare 的 SQLiteCursorLoader,它似乎完全符合您的要求。在生产应用程序中使用它没有任何问题。
  • 您需要只显示数据库中的数据还是需要对数据进行操作?
  • @NickF 在这种情况下,我想要的只是检索数据..

标签: java android sqlite android-fragments


【解决方案1】:

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

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

【讨论】:

  • 您的意思是说实现内容提供程序使您的应用程序“免疫”锁定问题,而不是说它使您的应用程序“容易出错”出现锁定问题?
【解决方案2】:

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

如果你想使用Loader,有一个ORMLite Extrasgithub 上提供了 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;
}
 }

还有一个为上面的光标适配器提供PreparedQuery的自定义Dao:

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

希望这能有所帮助!

【讨论】:

    【解决方案3】:

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

    Here 就是一个例子


    编辑:添加了一个更好的加载器使用示例。

    终于找到了真正帮助我理解工作原理的教程。

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

    • 添加LoaderManager.LoaderCallbacks&lt;D&gt; 的实现,其中D 是您的数据列表(sn-p 1)
    • 创建您的加载器类,复制 sn-p 2,并添加从 DB 加载您的数据
    • 最后调用加载程序 1 调用 init 然后刷新重新启动调用。 (sn-p 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:从here 中提取的加载程序的用法
    • 片段 2:here 了解更多信息和逻辑,请阅读整个漏洞文章
    • 代码段 3 和 4:只是加载程序的使用。

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

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

        我使用 SQLiteOpenHelper 创建我的数据库。我为所有表创建了 Java 类,当我从数据库中获取数据时,我将其放入 ArrayList 中。然后我将 ArrayList 加载到 Listview 的适配器中。

        【讨论】:

        • 如其他地方所述,这可能对较小的记录集有好处,但可能会出现问题。它在运行时会阻塞 UI 线程吗?如果您有在后台线程中使用它的策略,您能否更新您的答案以反映这一点?我也有兴趣在答案中看到一个很好的示例参考。 - 谢谢
        猜你喜欢
        • 1970-01-01
        • 2016-11-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-06-16
        • 2018-08-14
        • 1970-01-01
        • 2017-02-20
        相关资源
        最近更新 更多