如何使用CursorLoader在安卓中读取SQLite数据库?

34
我正在设置我的应用程序,让用户可以创建他们朋友的群组。当一个群组被创建时,它会向SQL数据库中写入2个表。第一个表有一个群组名称和群组ID。第二个表有两列,一个是群组ID和另一个是用户ID。这部分已经正常运行。
然而,现在我想能够从数据库中读取数据。我正在使用一个带有 cursorloader 的 listview Fragment,但我无法让信息正确显示。我想在列表视图中列出第一个表中所有群组的名称。
我的问题是,在我最初使用 cursorloader 列出我的联系人时,我使用了 content provider 中 onCreateLoader 方法中的 Uri。具体来说,我使用了 ContactsContracts.Contacts 类中的 CONTENT_URI。
具有 contentprovider 的 cursorloader 示例:
@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
    Uri contentUri = ContactsContract.Contacts.CONTENT_URI;
    return new CursorLoader(getActivity(),contentUri,PROJECTION,SELECTION,ARGS,ORDER);
}

然而,如果不使用内容提供程序,我不知道在onCreateLoader方法中该放什么,因为return new CursorLoader(...)需要第二个参数中的Uri

有没有建议可以让我如何在列表视图中显示数据库数据?

片段类代码:

public class GroupListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Cursor> {

CursorAdapter mAdapter;
private OnItemSelectedListener listener;
private static final String[] PROJECTION ={GroupContract.GroupDetails.COLUMN_NAME_GROUP_NAME};
private static final String SELECTION = null;
final String[] FROM = {GroupContract.GroupDetails.COLUMN_NAME_GROUP_NAME};
final int[] TO = {android.R.id.text1};
private static final String[] ARGS = null;
private static final String ORDER = null;
private Cursor c;


@Override
public void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    mAdapter = new SimpleCursorAdapter(getActivity(), android.R.layout.simple_list_item_1,null,FROM,TO,0 );
    ReadDBAsync readDB = new ReadDBAsync();
    readDB.execute();
}

@Override
public void onActivityCreated(Bundle savedInstanceState){
    super.onActivityCreated(savedInstanceState);
    setListAdapter(mAdapter);
    getLoaderManager().initLoader(0,null,this);
}

@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
    Uri contenturi = Uri.parse("content://preamble.oneapp");
    Uri tableuri = Uri.withAppendedPath(contenturi,GroupContract.GroupDetails.TABLE_NAME);
    return new CursorLoader(getActivity(),tableuri,PROJECTION,SELECTION,ARGS,ORDER);
}

@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
    mAdapter.swapCursor(cursor);
}

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


private class ReadDBAsync extends AsyncTask<Void,Void,String> {

    @Override
    protected String doInBackground(Void... voids) {

        ContractDBHelpers mDBHelper = new ContractDBHelpers(getActivity());
        SQLiteDatabase db = mDBHelper.getReadableDatabase();
        String returnvalue = "database read";
        c = db.query(GroupContract.GroupDetails.TABLE_NAME,PROJECTION,null,null,null,null,null);
        return returnvalue;
    }

    @Override
    protected void onPostExecute(String result){
        Toast.makeText(getActivity(), result, Toast.LENGTH_LONG).show();
    }
}

}

你是否创建了一个内容提供者来管理你的数据库? - user936414
@user936414 不需要,这是必要的吗?这样做有什么好处/坏处?编辑:刚刚查阅了一下,我不需要在多个应用程序之间共享数据,因此我不需要内容提供程序。 - Terence Chow
2个回答

65

Android指南建议在您想与其他应用程序共享数据时创建ContentProvider。如果您不需要此功能,可以直接覆盖CursorLoader类的loadInBackground()方法。例如,在您的onCreateLoader中编写如下内容:

return new CursorLoader( YourContext, null, YourProjection, YourSelection, YourSelectionArgs, YourOrder )
           {
               @Override
               public Cursor loadInBackground()
               {
                   // You better know how to get your database.
                   SQLiteDatabase DB = getReadableDatabase();
                   // You can use any query that returns a cursor.
                   return DB.query( YourTableName, getProjection(), getSelection(), getSelectionArgs(), null, null, getSortOrder(), null );
               }
           };

12
这是我使用的原因,因为我不需要共享我的数据,也不想重构我的数据库API成为一个ContentProvider。缺点是,没有ContentProvider部分,我的列表视图无法自动知道数据何时发生了更改,这是ContentProviders的一个优点。为了解决这个问题,当我知道有更改发生时,我重新创建我的加载器,即现在使用getLoaderManager().restartLoader(MY_LOADER_ID, null, this);而不是调用Cursor.requery()。这很麻烦,但这是我发现可以替代startManagingCursor()方法最简单的方式。 - Fish
@Fish,谢谢你提供这个信息,但是现在我想知道的是 restartLoader() 有多昂贵? 这很重要吧? - eRaisedToX
如果我不关心指定 YourProjection、YourSelection、YourSelectionArgs 和 YourOrder,有什么理由不返回 AsyncTaskLoader 呢? - steamer25
回复自己,看起来CursorLoader管理CancellationSignal,但如果你覆盖loadInBackground,则不会。不过,将该功能复制到自定义的AsyncTaskLoader<Cursor>中非常容易。 - steamer25
1
Android Studio 提醒我 The Loader class should be static or leaks might occur...(黄色警告)。有人知道我该怎么做吗?虽然这不是错误,但还是想解决掉它。 - zeroDivider

35

以下是在列表片段中创建CursorLoader的步骤:

1)创建一个继承SQLiteOpenHelper类的类,并覆盖onCreateonUpgrade方法来创建您的表。

2)创建一个继承ContentProvider类的类,并创建用于访问数据库的URI。参考http://developer.android.com/guide/topics/providers/content-providers.html。将您的URI添加到您在onCreateonUpdate、query等(已重写的方法)中使用的URIMatcher中,以匹配URI。参考http://developer.android.com/reference/android/content/UriMatcher.html

3)在插入方法中调用getContext().getContentResolver().notifyChange(uri, null)。在查询方法中,在返回内容提供程序之前调用setNotificationUri(ContentResolver cr, Uri uri),以便将插入更改自动反映到您的加载器中。(https://dev59.com/7msz5IYBdhLWcg3wbHAw#7915117)。

4)在onCreateLoader中给出该URI。

注意: 在当前的Android版本中,如果没有内容提供程序,列表的更改自动刷新是不可行的。如果您不想让您的内容提供程序可见,请将清单中的导出属性设置为false。或者您可以像https://dev59.com/rmw05IYBdhLWcg3wiyRJ#7422343中那样实现自定义CursorLoader来从数据库中检索数据。但在这种情况下,自动刷新数据是不可能的。


9
谢谢你的信息,我会研究一下。我发现我的选择是使用 ContentProvider(我不需要它)或者创建自己的自定义 CursorLoader(这似乎需要额外的工作)...难道在没有 ContentProvider 的情况下查询数据库不够常见吗?他们难道没有早就想出一种方法来避免额外的工作吗? - Terence Chow
拥有ContentProvider的原因之一是,它们可以在数据库中的内容更改时通知所有已注册的观察者(在CursorLoader中注册)。 - user936414
5
如果这是一种不会改变的数据类型,那该怎么办呢?我也觉得被强制使用“ContentProvider”很奇怪。对于静态Sqlite数据,难道没有更快、更轻量级的解决方案吗? - Daniele B
最后一部分不是真的:即使没有ContentProvider,你仍然可以使用notificationUri。 - njzk2
1
对于notificationUri,您需要一个内容URI,它是标识提供程序中数据的URI(http://developer.android.com/guide/topics/providers/content-provider-basics.html),这意味着内部涉及内容提供程序。此外,每当数据库发生更改时,都应该在该URI上调用notifyChange。只有这样,您的游标才会被通知。 - user936414

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接